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:
191
modules/juce_gui_extra/native/juce_AndroidViewComponent.cpp
Normal file
191
modules/juce_gui_extra/native/juce_AndroidViewComponent.cpp
Normal file
@ -0,0 +1,191 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
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 AndroidViewComponent::Pimpl : public ComponentMovementWatcher
|
||||
{
|
||||
public:
|
||||
Pimpl (jobject v, Component& comp, bool makeSiblingRatherThanChild = false)
|
||||
: ComponentMovementWatcher (&comp),
|
||||
view (v),
|
||||
owner (comp),
|
||||
embedAsSiblingRatherThanChild (makeSiblingRatherThanChild)
|
||||
{
|
||||
if (owner.isShowing())
|
||||
componentPeerChanged();
|
||||
}
|
||||
|
||||
~Pimpl()
|
||||
{
|
||||
removeFromParent();
|
||||
}
|
||||
|
||||
void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
|
||||
{
|
||||
auto* topComp = owner.getTopLevelComponent();
|
||||
|
||||
if (topComp->getPeer() != nullptr)
|
||||
{
|
||||
auto pos = topComp->getLocalPoint (&owner, Point<int>());
|
||||
|
||||
Rectangle<int> r (pos.x, pos.y, owner.getWidth(), owner.getHeight());
|
||||
r *= Desktop::getInstance().getDisplays().getMainDisplay().scale;
|
||||
|
||||
getEnv()->CallVoidMethod (view, AndroidView.layout, r.getX(), r.getY(),
|
||||
r.getRight(), r.getBottom());
|
||||
}
|
||||
}
|
||||
|
||||
void componentPeerChanged() override
|
||||
{
|
||||
auto* peer = owner.getPeer();
|
||||
|
||||
if (currentPeer != peer)
|
||||
{
|
||||
removeFromParent();
|
||||
|
||||
currentPeer = peer;
|
||||
|
||||
addToParent();
|
||||
}
|
||||
|
||||
enum
|
||||
{
|
||||
VISIBLE = 0,
|
||||
INVISIBLE = 4
|
||||
};
|
||||
|
||||
getEnv()->CallVoidMethod (view, AndroidView.setVisibility, owner.isShowing() ? VISIBLE : INVISIBLE);
|
||||
}
|
||||
|
||||
void componentVisibilityChanged() override
|
||||
{
|
||||
componentPeerChanged();
|
||||
}
|
||||
|
||||
void componentBroughtToFront (Component& comp) override
|
||||
{
|
||||
ComponentMovementWatcher::componentBroughtToFront (comp);
|
||||
|
||||
// Ensure that the native component doesn't get obscured.
|
||||
if (embedAsSiblingRatherThanChild)
|
||||
getEnv()->CallVoidMethod (view, AndroidView.bringToFront);
|
||||
}
|
||||
|
||||
Rectangle<int> getViewBounds() const
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
int width = env->CallIntMethod (view, AndroidView.getWidth);
|
||||
int height = env->CallIntMethod (view, AndroidView.getHeight);
|
||||
|
||||
return Rectangle<int> (width, height);
|
||||
}
|
||||
|
||||
GlobalRef view;
|
||||
|
||||
private:
|
||||
void addToParent()
|
||||
{
|
||||
if (currentPeer != nullptr)
|
||||
{
|
||||
jobject peerView = (jobject) currentPeer->getNativeHandle();
|
||||
|
||||
// NB: Assuming a parent is always of ViewGroup type
|
||||
auto* env = getEnv();
|
||||
|
||||
if (embedAsSiblingRatherThanChild)
|
||||
{
|
||||
// This is a workaround for a bug in a web browser component where
|
||||
// scrolling would be very slow and occassionally would scroll in
|
||||
// opposite direction to dragging direction. In normal circumstances,
|
||||
// the native view should be a child of peerView instead.
|
||||
auto parentView = LocalRef<jobject> (env->CallObjectMethod (peerView, AndroidView.getParent));
|
||||
env->CallVoidMethod (parentView, AndroidViewGroup.addView, view.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
env->CallVoidMethod (peerView, AndroidViewGroup.addView, view.get());
|
||||
}
|
||||
|
||||
componentMovedOrResized (false, false);
|
||||
}
|
||||
}
|
||||
|
||||
void removeFromParent()
|
||||
{
|
||||
auto* env = getEnv();
|
||||
auto parentView = env->CallObjectMethod (view, AndroidView.getParent);
|
||||
|
||||
if (parentView != 0)
|
||||
{
|
||||
// Assuming a parent is always of ViewGroup type
|
||||
env->CallVoidMethod (parentView, AndroidViewGroup.removeView, view.get());
|
||||
}
|
||||
}
|
||||
|
||||
Component& owner;
|
||||
bool embedAsSiblingRatherThanChild;
|
||||
ComponentPeer* currentPeer = nullptr;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
AndroidViewComponent::AndroidViewComponent (bool makeSiblingRatherThanChild)
|
||||
: embedAsSiblingRatherThanChild (makeSiblingRatherThanChild)
|
||||
{
|
||||
}
|
||||
|
||||
AndroidViewComponent::~AndroidViewComponent() {}
|
||||
|
||||
void AndroidViewComponent::setView (void* view)
|
||||
{
|
||||
if (view != getView())
|
||||
{
|
||||
pimpl.reset();
|
||||
|
||||
if (view != nullptr)
|
||||
pimpl.reset (new Pimpl ((jobject) view, *this, embedAsSiblingRatherThanChild));
|
||||
}
|
||||
}
|
||||
|
||||
void* AndroidViewComponent::getView() const
|
||||
{
|
||||
return pimpl == nullptr ? nullptr : (void*) pimpl->view;
|
||||
}
|
||||
|
||||
void AndroidViewComponent::resizeToFitView()
|
||||
{
|
||||
if (pimpl != nullptr)
|
||||
setBounds (pimpl->getViewBounds());
|
||||
}
|
||||
|
||||
void AndroidViewComponent::paint (Graphics&) {}
|
||||
|
||||
} // namespace juce
|
1649
modules/juce_gui_extra/native/juce_android_PushNotifications.cpp
Normal file
1649
modules/juce_gui_extra/native/juce_android_PushNotifications.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,608 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
|
||||
METHOD (constructor, "<init>", "(Landroid/content/Context;)V") \
|
||||
METHOD (getSettings, "getSettings", "()Landroid/webkit/WebSettings;") \
|
||||
METHOD (goBack, "goBack", "()V") \
|
||||
METHOD (goForward, "goForward", "()V") \
|
||||
METHOD (loadDataWithBaseURL, "loadDataWithBaseURL", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V") \
|
||||
METHOD (loadUrl, "loadUrl", "(Ljava/lang/String;Ljava/util/Map;)V") \
|
||||
METHOD (postUrl, "postUrl", "(Ljava/lang/String;[B)V") \
|
||||
METHOD (reload, "reload", "()V") \
|
||||
METHOD (setWebChromeClient, "setWebChromeClient", "(Landroid/webkit/WebChromeClient;)V") \
|
||||
METHOD (setWebViewClient, "setWebViewClient", "(Landroid/webkit/WebViewClient;)V") \
|
||||
METHOD (stopLoading, "stopLoading", "()V")
|
||||
|
||||
DECLARE_JNI_CLASS (AndroidWebView, "android/webkit/WebView")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
|
||||
METHOD (constructor, "<init>", "()V")
|
||||
|
||||
DECLARE_JNI_CLASS (AndroidWebChromeClient, "android/webkit/WebChromeClient");
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
|
||||
METHOD (constructor, "<init>", "()V")
|
||||
|
||||
DECLARE_JNI_CLASS (AndroidWebViewClient, "android/webkit/WebViewClient");
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
|
||||
STATICMETHOD (getInstance, "getInstance", "()Landroid/webkit/CookieManager;")
|
||||
|
||||
DECLARE_JNI_CLASS (AndroidCookieManager, "android/webkit/CookieManager");
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
|
||||
METHOD (constructor, "<init>", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH ";J)V")
|
||||
|
||||
DECLARE_JNI_CLASS (JuceWebChromeClient, JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceWebChromeClient");
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
|
||||
METHOD (constructor, "<init>", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH ";J)V") \
|
||||
METHOD (hostDeleted, "hostDeleted", "()V")
|
||||
|
||||
DECLARE_JNI_CLASS (JuceWebViewClient, JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceWebViewClient");
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
|
||||
METHOD (setBuiltInZoomControls, "setBuiltInZoomControls", "(Z)V") \
|
||||
METHOD (setDisplayZoomControls, "setDisplayZoomControls", "(Z)V") \
|
||||
METHOD (setJavaScriptEnabled, "setJavaScriptEnabled", "(Z)V") \
|
||||
METHOD (setSupportMultipleWindows, "setSupportMultipleWindows", "(Z)V")
|
||||
|
||||
DECLARE_JNI_CLASS (WebSettings, "android/webkit/WebSettings");
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
|
||||
METHOD (toString, "toString", "()Ljava/lang/String;")
|
||||
|
||||
DECLARE_JNI_CLASS (SslError, "android/net/http/SslError")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
|
||||
STATICMETHOD (encode, "encode", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;")
|
||||
|
||||
DECLARE_JNI_CLASS (URLEncoder, "java/net/URLEncoder")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
//==============================================================================
|
||||
class WebBrowserComponent::Pimpl : public AndroidViewComponent,
|
||||
public AsyncUpdater
|
||||
{
|
||||
public:
|
||||
Pimpl (WebBrowserComponent& o)
|
||||
: AndroidViewComponent (true),
|
||||
owner (o)
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
setView (env->NewObject (AndroidWebView, AndroidWebView.constructor, android.activity.get()));
|
||||
|
||||
auto settings = LocalRef<jobject> (env->CallObjectMethod ((jobject) getView(), AndroidWebView.getSettings));
|
||||
env->CallVoidMethod (settings, WebSettings.setJavaScriptEnabled, true);
|
||||
env->CallVoidMethod (settings, WebSettings.setBuiltInZoomControls, true);
|
||||
env->CallVoidMethod (settings, WebSettings.setDisplayZoomControls, false);
|
||||
env->CallVoidMethod (settings, WebSettings.setSupportMultipleWindows, true);
|
||||
|
||||
juceWebChromeClient = GlobalRef (LocalRef<jobject> (env->NewObject (JuceWebChromeClient, JuceWebChromeClient.constructor,
|
||||
android.activity.get(),
|
||||
reinterpret_cast<jlong>(&owner))));
|
||||
env->CallVoidMethod ((jobject) getView(), AndroidWebView.setWebChromeClient, juceWebChromeClient.get());
|
||||
|
||||
juceWebViewClient = GlobalRef (LocalRef<jobject> (env->NewObject (JuceWebViewClient, JuceWebViewClient.constructor,
|
||||
android.activity.get(),
|
||||
reinterpret_cast<jlong>(&owner))));
|
||||
env->CallVoidMethod ((jobject) getView(), AndroidWebView.setWebViewClient, juceWebViewClient.get());
|
||||
}
|
||||
|
||||
~Pimpl()
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
env->CallVoidMethod ((jobject) getView(), AndroidWebView.stopLoading);
|
||||
|
||||
auto defaultChromeClient = LocalRef<jobject> (env->NewObject (AndroidWebChromeClient, AndroidWebChromeClient.constructor));
|
||||
auto defaultViewClient = LocalRef<jobject> (env->NewObject (AndroidWebViewClient, AndroidWebViewClient .constructor));
|
||||
|
||||
env->CallVoidMethod ((jobject) getView(), AndroidWebView.setWebChromeClient, defaultChromeClient.get());
|
||||
env->CallVoidMethod ((jobject) getView(), AndroidWebView.setWebViewClient, defaultViewClient .get());
|
||||
|
||||
masterReference.clear();
|
||||
|
||||
// if other Java thread is waiting for us to respond to page load request
|
||||
// wake it up immediately (false answer will be sent), so that it releases
|
||||
// the lock we need when calling hostDeleted.
|
||||
responseReadyEvent.signal();
|
||||
|
||||
env->CallVoidMethod (juceWebViewClient, JuceWebViewClient.hostDeleted);
|
||||
}
|
||||
|
||||
void goToURL (const String& url,
|
||||
const StringArray* headers,
|
||||
const MemoryBlock* postData)
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
if (headers == nullptr && postData == nullptr)
|
||||
{
|
||||
env->CallVoidMethod ((jobject) getView(), AndroidWebView.loadUrl, javaString (url).get(), 0);
|
||||
}
|
||||
else if (headers != nullptr && postData == nullptr)
|
||||
{
|
||||
auto headersMap = LocalRef<jobject> (env->NewObject (JavaHashMap,
|
||||
JavaHashMap.constructorWithCapacity,
|
||||
headers->size()));
|
||||
|
||||
for (const auto& header : *headers)
|
||||
{
|
||||
auto name = header.upToFirstOccurrenceOf (":", false, false).trim();
|
||||
auto value = header.fromFirstOccurrenceOf (":", false, false).trim();
|
||||
|
||||
env->CallObjectMethod (headersMap, JavaMap.put,
|
||||
javaString (name).get(),
|
||||
javaString (value).get());
|
||||
}
|
||||
|
||||
env->CallVoidMethod ((jobject) getView(), AndroidWebView.loadUrl,
|
||||
javaString (url).get(), headersMap.get());
|
||||
}
|
||||
else if (headers == nullptr && postData != nullptr)
|
||||
{
|
||||
auto dataStringJuce = postData->toString();
|
||||
auto dataStringJava = javaString (dataStringJuce);
|
||||
auto encodingString = LocalRef<jobject> (env->CallStaticObjectMethod (URLEncoder, URLEncoder.encode,
|
||||
dataStringJava.get(), javaString ("utf-8").get()));
|
||||
|
||||
auto bytes = LocalRef<jbyteArray> ((jbyteArray) env->CallObjectMethod (encodingString, JavaString.getBytes));
|
||||
|
||||
env->CallVoidMethod ((jobject) getView(), AndroidWebView.postUrl,
|
||||
javaString (url).get(), bytes.get());
|
||||
}
|
||||
else if (headers != nullptr && postData != nullptr)
|
||||
{
|
||||
// There is no support for both extra headers and post data in Android WebView, so
|
||||
// we need to open URL manually.
|
||||
|
||||
URL urlToUse = URL (url).withPOSTData (*postData);
|
||||
connectionThread.reset (new ConnectionThread (*this, urlToUse, *headers));
|
||||
}
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
connectionThread = nullptr;
|
||||
|
||||
getEnv()->CallVoidMethod ((jobject) getView(), AndroidWebView.stopLoading);
|
||||
}
|
||||
|
||||
void goBack()
|
||||
{
|
||||
connectionThread = nullptr;
|
||||
|
||||
getEnv()->CallVoidMethod ((jobject) getView(), AndroidWebView.goBack);
|
||||
}
|
||||
|
||||
void goForward()
|
||||
{
|
||||
connectionThread = nullptr;
|
||||
|
||||
getEnv()->CallVoidMethod ((jobject) getView(), AndroidWebView.goForward);
|
||||
}
|
||||
|
||||
void refresh()
|
||||
{
|
||||
connectionThread = nullptr;
|
||||
|
||||
getEnv()->CallVoidMethod ((jobject) getView(), AndroidWebView.reload);
|
||||
}
|
||||
|
||||
void handleAsyncUpdate()
|
||||
{
|
||||
jassert (connectionThread != nullptr);
|
||||
|
||||
if (connectionThread == nullptr)
|
||||
return;
|
||||
|
||||
auto& result = connectionThread->getResult();
|
||||
|
||||
if (result.statusCode >= 200 && result.statusCode < 300)
|
||||
{
|
||||
auto url = javaString (result.url);
|
||||
auto data = javaString (result.data);
|
||||
auto mimeType = javaString ("text/html");
|
||||
auto encoding = javaString ("utf-8");
|
||||
|
||||
getEnv()->CallVoidMethod ((jobject) getView(), AndroidWebView.loadDataWithBaseURL,
|
||||
url.get(), data.get(), mimeType.get(),
|
||||
encoding.get(), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
owner.pageLoadHadNetworkError (result.description);
|
||||
}
|
||||
}
|
||||
|
||||
bool handlePageAboutToLoad (const String& url)
|
||||
{
|
||||
if (MessageManager::getInstance()->isThisTheMessageThread())
|
||||
return owner.pageAboutToLoad (url);
|
||||
|
||||
WeakReference<Pimpl> weakRef (this);
|
||||
|
||||
if (weakRef == nullptr)
|
||||
return false;
|
||||
|
||||
responseReadyEvent.reset();
|
||||
|
||||
bool shouldLoad = false;
|
||||
|
||||
MessageManager::callAsync ([weakRef, url, &shouldLoad]
|
||||
{
|
||||
if (weakRef == nullptr)
|
||||
return;
|
||||
|
||||
shouldLoad = weakRef->owner.pageAboutToLoad (url);
|
||||
|
||||
weakRef->responseReadyEvent.signal();
|
||||
});
|
||||
|
||||
responseReadyEvent.wait (-1);
|
||||
|
||||
return shouldLoad;
|
||||
}
|
||||
|
||||
private:
|
||||
class ConnectionThread : private Thread
|
||||
{
|
||||
public:
|
||||
struct Result
|
||||
{
|
||||
String url;
|
||||
int statusCode = 0;
|
||||
String description;
|
||||
String data;
|
||||
};
|
||||
|
||||
ConnectionThread (Pimpl& ownerToUse,
|
||||
URL& url,
|
||||
const StringArray& headers)
|
||||
: Thread ("WebBrowserComponent::Pimpl::ConnectionThread"),
|
||||
owner (ownerToUse),
|
||||
webInputStream (new WebInputStream (url, true))
|
||||
{
|
||||
webInputStream->withExtraHeaders (headers.joinIntoString ("\n"));
|
||||
webInputStream->withConnectionTimeout (10000);
|
||||
|
||||
result.url = url.toString (true);
|
||||
|
||||
startThread();
|
||||
}
|
||||
|
||||
~ConnectionThread()
|
||||
{
|
||||
webInputStream->cancel();
|
||||
signalThreadShouldExit();
|
||||
waitForThreadToExit (10000);
|
||||
|
||||
webInputStream = nullptr;
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
if (! webInputStream->connect (nullptr))
|
||||
{
|
||||
result.description = "Could not establish connection";
|
||||
owner.triggerAsyncUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
result.statusCode = webInputStream->getStatusCode();
|
||||
result.description = "Status code: " + String (result.statusCode);
|
||||
readFromInputStream();
|
||||
owner.triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
const Result& getResult() { return result; }
|
||||
|
||||
private:
|
||||
void readFromInputStream()
|
||||
{
|
||||
MemoryOutputStream ostream;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (threadShouldExit())
|
||||
return;
|
||||
|
||||
char buffer [8192];
|
||||
const int num = webInputStream->read (buffer, sizeof (buffer));
|
||||
|
||||
if (num <= 0)
|
||||
break;
|
||||
|
||||
ostream.write (buffer, (size_t) num);
|
||||
}
|
||||
|
||||
result.data = ostream.toUTF8();
|
||||
}
|
||||
|
||||
Pimpl& owner;
|
||||
std::unique_ptr<WebInputStream> webInputStream;
|
||||
Result result;
|
||||
};
|
||||
|
||||
|
||||
WebBrowserComponent& owner;
|
||||
GlobalRef juceWebChromeClient;
|
||||
GlobalRef juceWebViewClient;
|
||||
std::unique_ptr<ConnectionThread> connectionThread;
|
||||
WaitableEvent responseReadyEvent;
|
||||
|
||||
WeakReference<Pimpl>::Master masterReference;
|
||||
friend class WeakReference<Pimpl>;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
WebBrowserComponent::WebBrowserComponent (const bool unloadWhenHidden)
|
||||
: blankPageShown (false),
|
||||
unloadPageWhenBrowserIsHidden (unloadWhenHidden)
|
||||
{
|
||||
setOpaque (true);
|
||||
|
||||
browser.reset (new Pimpl (*this));
|
||||
addAndMakeVisible (browser.get());
|
||||
}
|
||||
|
||||
WebBrowserComponent::~WebBrowserComponent()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void WebBrowserComponent::goToURL (const String& url,
|
||||
const StringArray* headers,
|
||||
const MemoryBlock* postData)
|
||||
{
|
||||
lastURL = url;
|
||||
|
||||
if (headers != nullptr)
|
||||
lastHeaders = *headers;
|
||||
else
|
||||
lastHeaders.clear();
|
||||
|
||||
if (postData != nullptr)
|
||||
lastPostData = *postData;
|
||||
else
|
||||
lastPostData.reset();
|
||||
|
||||
blankPageShown = false;
|
||||
|
||||
browser->goToURL (url, headers, postData);
|
||||
}
|
||||
|
||||
void WebBrowserComponent::stop()
|
||||
{
|
||||
browser->stop();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::goBack()
|
||||
{
|
||||
lastURL.clear();
|
||||
blankPageShown = false;
|
||||
|
||||
browser->goBack();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::goForward()
|
||||
{
|
||||
lastURL.clear();
|
||||
|
||||
browser->goForward();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::refresh()
|
||||
{
|
||||
browser->refresh();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void WebBrowserComponent::paint (Graphics& g)
|
||||
{
|
||||
g.fillAll (Colours::white);
|
||||
}
|
||||
|
||||
void WebBrowserComponent::checkWindowAssociation()
|
||||
{
|
||||
if (isShowing())
|
||||
{
|
||||
if (blankPageShown)
|
||||
goBack();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (unloadPageWhenBrowserIsHidden && ! blankPageShown)
|
||||
{
|
||||
// when the component becomes invisible, some stuff like flash
|
||||
// carries on playing audio, so we need to force it onto a blank
|
||||
// page to avoid this, (and send it back when it's made visible again).
|
||||
|
||||
blankPageShown = true;
|
||||
browser->goToURL ("about:blank", 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebBrowserComponent::reloadLastURL()
|
||||
{
|
||||
if (lastURL.isNotEmpty())
|
||||
{
|
||||
goToURL (lastURL, &lastHeaders, lastPostData.getSize() == 0 ? nullptr : &lastPostData);
|
||||
lastURL.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void WebBrowserComponent::parentHierarchyChanged()
|
||||
{
|
||||
checkWindowAssociation();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::resized()
|
||||
{
|
||||
browser->setSize (getWidth(), getHeight());
|
||||
}
|
||||
|
||||
void WebBrowserComponent::visibilityChanged()
|
||||
{
|
||||
checkWindowAssociation();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::focusGained (FocusChangeType)
|
||||
{
|
||||
}
|
||||
|
||||
void WebBrowserComponent::clearCookies()
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
auto cookieManager = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidCookieManager,
|
||||
AndroidCookieManager.getInstance));
|
||||
|
||||
const bool apiAtLeast21 = env->CallStaticIntMethod (JuceAppActivity, JuceAppActivity.getAndroidSDKVersion) >= 21;
|
||||
|
||||
jmethodID clearCookiesMethod = 0;
|
||||
|
||||
if (apiAtLeast21)
|
||||
{
|
||||
clearCookiesMethod = env->GetMethodID (AndroidCookieManager, "removeAllCookies", "(Landroid/webkit/ValueCallback;)V");
|
||||
env->CallVoidMethod (cookieManager, clearCookiesMethod, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
clearCookiesMethod = env->GetMethodID (AndroidCookieManager, "removeAllCookie", "()V");
|
||||
env->CallVoidMethod (cookieManager, clearCookiesMethod);
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, webViewPageLoadStarted, bool, (JNIEnv* env, jobject /*activity*/, jlong host, jobject /*webView*/, jobject url))
|
||||
{
|
||||
setEnv (env);
|
||||
|
||||
return juce_webViewPageLoadStarted (reinterpret_cast<WebBrowserComponent*> (host),
|
||||
juceString (static_cast<jstring> (url)));
|
||||
}
|
||||
|
||||
bool juce_webViewPageLoadStarted (WebBrowserComponent* browserComponent, const String& url)
|
||||
{
|
||||
return browserComponent->browser->handlePageAboutToLoad (url);
|
||||
}
|
||||
|
||||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, webViewPageLoadFinished, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject /*webView*/, jobject url))
|
||||
{
|
||||
setEnv (env);
|
||||
|
||||
reinterpret_cast<WebBrowserComponent*> (host)->pageFinishedLoading (juceString (static_cast<jstring> (url)));
|
||||
}
|
||||
|
||||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, webViewReceivedError, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject /*webView*/, jobject /*request*/, jobject error))
|
||||
{
|
||||
setEnv (env);
|
||||
|
||||
jclass errorClass = env->FindClass ("android/webkit/WebResourceError");
|
||||
|
||||
if (errorClass != 0)
|
||||
{
|
||||
jmethodID method = env->GetMethodID (errorClass, "getDescription", "()Ljava/lang/CharSequence;");
|
||||
|
||||
if (method != 0)
|
||||
{
|
||||
auto sequence = LocalRef<jobject> (env->CallObjectMethod (error, method));
|
||||
auto errorString = LocalRef<jstring> ((jstring) env->CallObjectMethod (sequence, JavaCharSequence.toString));
|
||||
|
||||
reinterpret_cast<WebBrowserComponent*> (host)->pageLoadHadNetworkError (juceString (errorString));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Should never get here!
|
||||
jassertfalse;
|
||||
reinterpret_cast<WebBrowserComponent*> (host)->pageLoadHadNetworkError ({});
|
||||
}
|
||||
|
||||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, webViewReceivedHttpError, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject /*webView*/, jobject /*request*/, jobject errorResponse))
|
||||
{
|
||||
setEnv (env);
|
||||
|
||||
jclass responseClass = env->FindClass ("android/webkit/WebResourceResponse");
|
||||
|
||||
if (responseClass != 0)
|
||||
{
|
||||
jmethodID method = env->GetMethodID (responseClass, "getReasonPhrase", "()Ljava/lang/String;");
|
||||
|
||||
if (method != 0)
|
||||
{
|
||||
auto errorString = LocalRef<jstring> ((jstring) env->CallObjectMethod (errorResponse, method));
|
||||
|
||||
reinterpret_cast<WebBrowserComponent*> (host)->pageLoadHadNetworkError (juceString (errorString));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Should never get here!
|
||||
jassertfalse;
|
||||
reinterpret_cast<WebBrowserComponent*> (host)->pageLoadHadNetworkError ({});
|
||||
}
|
||||
|
||||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, webViewReceivedSslError, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject /*webView*/, jobject /*sslErrorHandler*/, jobject sslError))
|
||||
{
|
||||
setEnv (env);
|
||||
|
||||
auto errorString = LocalRef<jstring> ((jstring) env->CallObjectMethod (sslError, SslError.toString));
|
||||
|
||||
reinterpret_cast<WebBrowserComponent*> (host)->pageLoadHadNetworkError (juceString (errorString));
|
||||
}
|
||||
|
||||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, webViewCloseWindowRequest, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject /*webView*/))
|
||||
{
|
||||
setEnv (env);
|
||||
|
||||
reinterpret_cast<WebBrowserComponent*> (host)->windowCloseRequest();
|
||||
}
|
||||
|
||||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, webViewCreateWindowRequest, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject /*webView*/))
|
||||
{
|
||||
setEnv (env);
|
||||
|
||||
reinterpret_cast<WebBrowserComponent*> (host)->newWindowAttemptingToLoad ({});
|
||||
}
|
||||
|
||||
|
||||
} // namespace juce
|
987
modules/juce_gui_extra/native/juce_ios_PushNotifications.cpp
Normal file
987
modules/juce_gui_extra/native/juce_ios_PushNotifications.cpp
Normal file
@ -0,0 +1,987 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
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 PushNotificationsDelegateDetails
|
||||
{
|
||||
//==============================================================================
|
||||
using Action = PushNotifications::Settings::Action;
|
||||
using Category = PushNotifications::Settings::Category;
|
||||
|
||||
void* actionToNSAction (const Action& a, bool iOSEarlierThan10)
|
||||
{
|
||||
if (iOSEarlierThan10)
|
||||
{
|
||||
auto* action = [[UIMutableUserNotificationAction alloc] init];
|
||||
|
||||
action.identifier = juceStringToNS (a.identifier);
|
||||
action.title = juceStringToNS (a.title);
|
||||
action.behavior = a.style == Action::text ? UIUserNotificationActionBehaviorTextInput
|
||||
: UIUserNotificationActionBehaviorDefault;
|
||||
action.parameters = varObjectToNSDictionary (a.parameters);
|
||||
action.activationMode = a.triggerInBackground ? UIUserNotificationActivationModeBackground
|
||||
: UIUserNotificationActivationModeForeground;
|
||||
action.destructive = (bool) a.destructive;
|
||||
|
||||
[action autorelease];
|
||||
|
||||
return action;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
if (a.style == Action::text)
|
||||
{
|
||||
return [UNTextInputNotificationAction actionWithIdentifier: juceStringToNS (a.identifier)
|
||||
title: juceStringToNS (a.title)
|
||||
options: NSUInteger (a.destructive << 1 | (! a.triggerInBackground) << 2)
|
||||
textInputButtonTitle: juceStringToNS (a.textInputButtonText)
|
||||
textInputPlaceholder: juceStringToNS (a.textInputPlaceholder)];
|
||||
}
|
||||
|
||||
return [UNNotificationAction actionWithIdentifier: juceStringToNS (a.identifier)
|
||||
title: juceStringToNS (a.title)
|
||||
options: NSUInteger (a.destructive << 1 | (! a.triggerInBackground) << 2)];
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void* categoryToNSCategory (const Category& c, bool iOSEarlierThan10)
|
||||
{
|
||||
if (iOSEarlierThan10)
|
||||
{
|
||||
auto* category = [[UIMutableUserNotificationCategory alloc] init];
|
||||
category.identifier = juceStringToNS (c.identifier);
|
||||
|
||||
auto* actions = [NSMutableArray arrayWithCapacity: (NSUInteger) c.actions.size()];
|
||||
|
||||
for (const auto& a : c.actions)
|
||||
{
|
||||
auto* action = (UIUserNotificationAction*) actionToNSAction (a, iOSEarlierThan10);
|
||||
[actions addObject: action];
|
||||
}
|
||||
|
||||
[category setActions: actions forContext: UIUserNotificationActionContextDefault];
|
||||
[category setActions: actions forContext: UIUserNotificationActionContextMinimal];
|
||||
|
||||
[category autorelease];
|
||||
|
||||
return category;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
auto* actions = [NSMutableArray arrayWithCapacity: (NSUInteger) c.actions.size()];
|
||||
|
||||
for (const auto& a : c.actions)
|
||||
{
|
||||
auto* action = (UNNotificationAction*) actionToNSAction (a, iOSEarlierThan10);
|
||||
[actions addObject: action];
|
||||
}
|
||||
|
||||
return [UNNotificationCategory categoryWithIdentifier: juceStringToNS (c.identifier)
|
||||
actions: actions
|
||||
intentIdentifiers: @[]
|
||||
options: c.sendDismissAction ? UNNotificationCategoryOptionCustomDismissAction : 0];
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
UILocalNotification* juceNotificationToUILocalNotification (const PushNotifications::Notification& n)
|
||||
{
|
||||
auto* notification = [[UILocalNotification alloc] init];
|
||||
|
||||
notification.alertTitle = juceStringToNS (n.title);
|
||||
notification.alertBody = juceStringToNS (n.body);
|
||||
notification.category = juceStringToNS (n.category);
|
||||
notification.applicationIconBadgeNumber = n.badgeNumber;
|
||||
|
||||
auto triggerTime = Time::getCurrentTime() + RelativeTime (n.triggerIntervalSec);
|
||||
notification.fireDate = [NSDate dateWithTimeIntervalSince1970: triggerTime.toMilliseconds() / 1000.];
|
||||
notification.userInfo = varObjectToNSDictionary (n.properties);
|
||||
|
||||
auto soundToPlayString = n.soundToPlay.toString (true);
|
||||
|
||||
if (soundToPlayString == "default_os_sound")
|
||||
notification.soundName = UILocalNotificationDefaultSoundName;
|
||||
else if (soundToPlayString.isNotEmpty())
|
||||
notification.soundName = juceStringToNS (soundToPlayString);
|
||||
|
||||
return notification;
|
||||
}
|
||||
|
||||
#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
UNNotificationRequest* juceNotificationToUNNotificationRequest (const PushNotifications::Notification& n)
|
||||
{
|
||||
// content
|
||||
auto* content = [[UNMutableNotificationContent alloc] init];
|
||||
|
||||
content.title = juceStringToNS (n.title);
|
||||
content.subtitle = juceStringToNS (n.subtitle);
|
||||
content.threadIdentifier = juceStringToNS (n.groupId);
|
||||
content.body = juceStringToNS (n.body);
|
||||
content.categoryIdentifier = juceStringToNS (n.category);
|
||||
content.badge = [NSNumber numberWithInt: n.badgeNumber];
|
||||
|
||||
auto soundToPlayString = n.soundToPlay.toString (true);
|
||||
|
||||
if (soundToPlayString == "default_os_sound")
|
||||
content.sound = [UNNotificationSound defaultSound];
|
||||
else if (soundToPlayString.isNotEmpty())
|
||||
content.sound = [UNNotificationSound soundNamed: juceStringToNS (soundToPlayString)];
|
||||
|
||||
auto* propsDict = (NSMutableDictionary*) varObjectToNSDictionary (n.properties);
|
||||
[propsDict setObject: juceStringToNS (soundToPlayString) forKey: nsStringLiteral ("com.juce.soundName")];
|
||||
content.userInfo = propsDict;
|
||||
|
||||
// trigger
|
||||
UNTimeIntervalNotificationTrigger* trigger = nil;
|
||||
|
||||
if (std::abs (n.triggerIntervalSec) >= 0.001)
|
||||
{
|
||||
BOOL shouldRepeat = n.repeat && n.triggerIntervalSec >= 60;
|
||||
trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval: n.triggerIntervalSec repeats: shouldRepeat];
|
||||
}
|
||||
|
||||
// request
|
||||
// each notification on iOS 10 needs to have an identifer, otherwise it will not show up
|
||||
jassert (n.identifier.isNotEmpty());
|
||||
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier: juceStringToNS (n.identifier)
|
||||
content: content
|
||||
trigger: trigger];
|
||||
|
||||
[content autorelease];
|
||||
|
||||
return request;
|
||||
}
|
||||
#endif
|
||||
|
||||
String getUserResponseFromNSDictionary (NSDictionary* dictionary)
|
||||
{
|
||||
if (dictionary == nil || dictionary.count == 0)
|
||||
return {};
|
||||
|
||||
jassert (dictionary.count == 1);
|
||||
|
||||
for (NSString* key in dictionary)
|
||||
{
|
||||
const auto keyString = nsStringToJuce (key);
|
||||
|
||||
id value = dictionary[key];
|
||||
|
||||
if ([value isKindOfClass: [NSString class]])
|
||||
return nsStringToJuce ((NSString*) value);
|
||||
}
|
||||
|
||||
jassertfalse;
|
||||
return {};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
var getNotificationPropertiesFromDictionaryVar (const var& dictionaryVar)
|
||||
{
|
||||
DynamicObject* dictionaryVarObject = dictionaryVar.getDynamicObject();
|
||||
|
||||
if (dictionaryVarObject == nullptr)
|
||||
return {};
|
||||
|
||||
const auto& properties = dictionaryVarObject->getProperties();
|
||||
|
||||
DynamicObject::Ptr propsVarObject = new DynamicObject();
|
||||
|
||||
for (int i = 0; i < properties.size(); ++i)
|
||||
{
|
||||
auto propertyName = properties.getName (i).toString();
|
||||
|
||||
if (propertyName == "aps")
|
||||
continue;
|
||||
|
||||
propsVarObject->setProperty (propertyName, properties.getValueAt (i));
|
||||
}
|
||||
|
||||
return var (propsVarObject);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
double getIntervalSecFromUNNotificationTrigger (UNNotificationTrigger* t)
|
||||
{
|
||||
if (t != nil)
|
||||
{
|
||||
if ([t isKindOfClass: [UNTimeIntervalNotificationTrigger class]])
|
||||
{
|
||||
auto* trigger = (UNTimeIntervalNotificationTrigger*) t;
|
||||
return trigger.timeInterval;
|
||||
}
|
||||
else if ([t isKindOfClass: [UNCalendarNotificationTrigger class]])
|
||||
{
|
||||
auto* trigger = (UNCalendarNotificationTrigger*) t;
|
||||
NSDate* date = [trigger.dateComponents date];
|
||||
NSDate* dateNow = [NSDate date];
|
||||
return [dateNow timeIntervalSinceDate: date];
|
||||
}
|
||||
}
|
||||
|
||||
return 0.;
|
||||
}
|
||||
|
||||
PushNotifications::Notification unNotificationRequestToJuceNotification (UNNotificationRequest* r)
|
||||
{
|
||||
PushNotifications::Notification n;
|
||||
|
||||
n.identifier = nsStringToJuce (r.identifier);
|
||||
n.title = nsStringToJuce (r.content.title);
|
||||
n.subtitle = nsStringToJuce (r.content.subtitle);
|
||||
n.body = nsStringToJuce (r.content.body);
|
||||
n.groupId = nsStringToJuce (r.content.threadIdentifier);
|
||||
n.category = nsStringToJuce (r.content.categoryIdentifier);
|
||||
n.badgeNumber = r.content.badge.intValue;
|
||||
|
||||
auto userInfoVar = nsDictionaryToVar (r.content.userInfo);
|
||||
|
||||
if (auto* object = userInfoVar.getDynamicObject())
|
||||
{
|
||||
static const Identifier soundName ("com.juce.soundName");
|
||||
n.soundToPlay = URL (object->getProperty (soundName).toString());
|
||||
object->removeProperty (soundName);
|
||||
}
|
||||
|
||||
n.properties = userInfoVar;
|
||||
|
||||
n.triggerIntervalSec = getIntervalSecFromUNNotificationTrigger (r.trigger);
|
||||
n.repeat = r.trigger != nil && r.trigger.repeats;
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
PushNotifications::Notification unNotificationToJuceNotification (UNNotification* n)
|
||||
{
|
||||
return unNotificationRequestToJuceNotification (n.request);
|
||||
}
|
||||
#endif
|
||||
|
||||
PushNotifications::Notification uiLocalNotificationToJuceNotification (UILocalNotification* n)
|
||||
{
|
||||
PushNotifications::Notification notif;
|
||||
|
||||
notif.title = nsStringToJuce (n.alertTitle);
|
||||
notif.body = nsStringToJuce (n.alertBody);
|
||||
|
||||
if (n.fireDate != nil)
|
||||
{
|
||||
NSDate* dateNow = [NSDate date];
|
||||
notif.triggerIntervalSec = [dateNow timeIntervalSinceDate: n.fireDate];
|
||||
}
|
||||
|
||||
notif.soundToPlay = URL (nsStringToJuce (n.soundName));
|
||||
notif.badgeNumber = (int) n.applicationIconBadgeNumber;
|
||||
notif.category = nsStringToJuce (n.category);
|
||||
notif.properties = nsDictionaryToVar (n.userInfo);
|
||||
|
||||
return notif;
|
||||
}
|
||||
|
||||
Action uiUserNotificationActionToAction (UIUserNotificationAction* a)
|
||||
{
|
||||
Action action;
|
||||
|
||||
action.identifier = nsStringToJuce (a.identifier);
|
||||
action.title = nsStringToJuce (a.title);
|
||||
action.style = a.behavior == UIUserNotificationActionBehaviorTextInput
|
||||
? Action::text
|
||||
: Action::button;
|
||||
|
||||
action.triggerInBackground = a.activationMode == UIUserNotificationActivationModeBackground;
|
||||
action.destructive = a.destructive;
|
||||
action.parameters = nsDictionaryToVar (a.parameters);
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
Category uiUserNotificationCategoryToCategory (UIUserNotificationCategory* c)
|
||||
{
|
||||
Category category;
|
||||
category.identifier = nsStringToJuce (c.identifier);
|
||||
|
||||
for (UIUserNotificationAction* a in [c actionsForContext: UIUserNotificationActionContextDefault])
|
||||
category.actions.add (uiUserNotificationActionToAction (a));
|
||||
|
||||
return category;
|
||||
}
|
||||
|
||||
#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
Action unNotificationActionToAction (UNNotificationAction* a)
|
||||
{
|
||||
Action action;
|
||||
|
||||
action.identifier = nsStringToJuce (a.identifier);
|
||||
action.title = nsStringToJuce (a.title);
|
||||
action.triggerInBackground = ! (a.options & UNNotificationActionOptionForeground);
|
||||
action.destructive = a.options & UNNotificationActionOptionDestructive;
|
||||
|
||||
if ([a isKindOfClass: [UNTextInputNotificationAction class]])
|
||||
{
|
||||
auto* textAction = (UNTextInputNotificationAction*)a;
|
||||
|
||||
action.style = Action::text;
|
||||
action.textInputButtonText = nsStringToJuce (textAction.textInputButtonTitle);
|
||||
action.textInputPlaceholder = nsStringToJuce (textAction.textInputPlaceholder);
|
||||
}
|
||||
else
|
||||
{
|
||||
action.style = Action::button;
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
Category unNotificationCategoryToCategory (UNNotificationCategory* c)
|
||||
{
|
||||
Category category;
|
||||
|
||||
category.identifier = nsStringToJuce (c.identifier);
|
||||
category.sendDismissAction = c.options & UNNotificationCategoryOptionCustomDismissAction;
|
||||
|
||||
for (UNNotificationAction* a in c.actions)
|
||||
category.actions.add (unNotificationActionToAction (a));
|
||||
|
||||
return category;
|
||||
}
|
||||
#endif
|
||||
|
||||
PushNotifications::Notification nsDictionaryToJuceNotification (NSDictionary* dictionary)
|
||||
{
|
||||
const var dictionaryVar = nsDictionaryToVar (dictionary);
|
||||
|
||||
const var apsVar = dictionaryVar.getProperty ("aps", {});
|
||||
|
||||
if (! apsVar.isObject())
|
||||
return {};
|
||||
|
||||
var alertVar = apsVar.getProperty ("alert", {});
|
||||
|
||||
const var titleVar = alertVar.getProperty ("title", {});
|
||||
const var bodyVar = alertVar.isObject() ? alertVar.getProperty ("body", {}) : alertVar;
|
||||
|
||||
const var categoryVar = apsVar.getProperty ("category", {});
|
||||
const var soundVar = apsVar.getProperty ("sound", {});
|
||||
const var badgeVar = apsVar.getProperty ("badge", {});
|
||||
const var threadIdVar = apsVar.getProperty ("thread-id", {});
|
||||
|
||||
PushNotifications::Notification notification;
|
||||
|
||||
notification.title = titleVar .toString();
|
||||
notification.body = bodyVar .toString();
|
||||
notification.groupId = threadIdVar.toString();
|
||||
notification.category = categoryVar.toString();
|
||||
notification.soundToPlay = URL (soundVar.toString());
|
||||
notification.badgeNumber = (int) badgeVar;
|
||||
notification.properties = getNotificationPropertiesFromDictionaryVar (dictionaryVar);
|
||||
|
||||
return notification;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct PushNotificationsDelegate
|
||||
{
|
||||
PushNotificationsDelegate() : delegate ([getClass().createInstance() init])
|
||||
{
|
||||
Class::setThis (delegate.get(), this);
|
||||
|
||||
id<UIApplicationDelegate> appDelegate = [[UIApplication sharedApplication] delegate];
|
||||
|
||||
SEL selector = NSSelectorFromString (@"setPushNotificationsDelegateToUse:");
|
||||
|
||||
if ([appDelegate respondsToSelector: selector])
|
||||
[appDelegate performSelector: selector withObject: delegate.get()];
|
||||
}
|
||||
|
||||
virtual ~PushNotificationsDelegate() {}
|
||||
|
||||
virtual void didRegisterUserNotificationSettings (UIUserNotificationSettings* notificationSettings) = 0;
|
||||
|
||||
virtual void registeredForRemoteNotifications (NSData* deviceToken) = 0;
|
||||
|
||||
virtual void failedToRegisterForRemoteNotifications (NSError* error) = 0;
|
||||
|
||||
virtual void didReceiveRemoteNotification (NSDictionary* userInfo) = 0;
|
||||
|
||||
virtual void didReceiveRemoteNotificationFetchCompletionHandler (NSDictionary* userInfo,
|
||||
void (^completionHandler)(UIBackgroundFetchResult result)) = 0;
|
||||
|
||||
virtual void handleActionForRemoteNotificationCompletionHandler (NSString* actionIdentifier,
|
||||
NSDictionary* userInfo,
|
||||
NSDictionary* responseInfo,
|
||||
void (^completionHandler)()) = 0;
|
||||
|
||||
virtual void didReceiveLocalNotification (UILocalNotification* notification) = 0;
|
||||
|
||||
virtual void handleActionForLocalNotificationCompletionHandler (NSString* actionIdentifier,
|
||||
UILocalNotification* notification,
|
||||
void (^completionHandler)()) = 0;
|
||||
|
||||
virtual void handleActionForLocalNotificationWithResponseCompletionHandler (NSString* actionIdentifier,
|
||||
UILocalNotification* notification,
|
||||
NSDictionary* responseInfo,
|
||||
void (^completionHandler)()) = 0;
|
||||
|
||||
#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
virtual void willPresentNotificationWithCompletionHandler (UNNotification* notification,
|
||||
void (^completionHandler)(UNNotificationPresentationOptions options)) = 0;
|
||||
|
||||
virtual void didReceiveNotificationResponseWithCompletionHandler (UNNotificationResponse* response,
|
||||
void (^completionHandler)()) = 0;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
std::unique_ptr<NSObject<UIApplicationDelegate, UNUserNotificationCenterDelegate>, NSObjectDeleter> delegate;
|
||||
#else
|
||||
std::unique_ptr<NSObject<UIApplicationDelegate>, NSObjectDeleter> delegate;
|
||||
#endif
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
struct Class : public ObjCClass<NSObject<UIApplicationDelegate, UNUserNotificationCenterDelegate>>
|
||||
{
|
||||
Class() : ObjCClass<NSObject<UIApplicationDelegate, UNUserNotificationCenterDelegate>> ("JucePushNotificationsDelegate_")
|
||||
#else
|
||||
struct Class : public ObjCClass<NSObject<UIApplicationDelegate>>
|
||||
{
|
||||
Class() : ObjCClass<NSObject<UIApplicationDelegate>> ("JucePushNotificationsDelegate_")
|
||||
#endif
|
||||
{
|
||||
addIvar<PushNotificationsDelegate*> ("self");
|
||||
|
||||
addMethod (@selector (application:didRegisterUserNotificationSettings:), didRegisterUserNotificationSettings, "v@:@@");
|
||||
addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), registeredForRemoteNotifications, "v@:@@");
|
||||
addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), failedToRegisterForRemoteNotifications, "v@:@@");
|
||||
addMethod (@selector (application:didReceiveRemoteNotification:), didReceiveRemoteNotification, "v@:@@");
|
||||
addMethod (@selector (application:didReceiveRemoteNotification:fetchCompletionHandler:), didReceiveRemoteNotificationFetchCompletionHandler, "v@:@@@");
|
||||
addMethod (@selector (application:handleActionWithIdentifier:forRemoteNotification:withResponseInfo:completionHandler:), handleActionForRemoteNotificationCompletionHandler, "v@:@@@@@");
|
||||
addMethod (@selector (application:didReceiveLocalNotification:), didReceiveLocalNotification, "v@:@@");
|
||||
addMethod (@selector (application:handleActionWithIdentifier:forLocalNotification:completionHandler:), handleActionForLocalNotificationCompletionHandler, "v@:@@@@");
|
||||
addMethod (@selector (application:handleActionWithIdentifier:forLocalNotification:withResponseInfo:completionHandler:), handleActionForLocalNotificationWithResponseCompletionHandler, "v@:@@@@@");
|
||||
|
||||
#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
addMethod (@selector (userNotificationCenter:willPresentNotification:withCompletionHandler:), willPresentNotificationWithCompletionHandler, "v@:@@@");
|
||||
addMethod (@selector (userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:), didReceiveNotificationResponseWithCompletionHandler, "v@:@@@");
|
||||
#endif
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static PushNotificationsDelegate& getThis (id self) { return *getIvar<PushNotificationsDelegate*> (self, "self"); }
|
||||
static void setThis (id self, PushNotificationsDelegate* d) { object_setInstanceVariable (self, "self", d); }
|
||||
|
||||
//==============================================================================
|
||||
static void didRegisterUserNotificationSettings (id self, SEL, UIApplication*,
|
||||
UIUserNotificationSettings* settings) { getThis (self).didRegisterUserNotificationSettings (settings); }
|
||||
static void registeredForRemoteNotifications (id self, SEL, UIApplication*,
|
||||
NSData* deviceToken) { getThis (self).registeredForRemoteNotifications (deviceToken); }
|
||||
|
||||
static void failedToRegisterForRemoteNotifications (id self, SEL, UIApplication*,
|
||||
NSError* error) { getThis (self).failedToRegisterForRemoteNotifications (error); }
|
||||
|
||||
static void didReceiveRemoteNotification (id self, SEL, UIApplication*,
|
||||
NSDictionary* userInfo) { getThis (self).didReceiveRemoteNotification (userInfo); }
|
||||
|
||||
static void didReceiveRemoteNotificationFetchCompletionHandler (id self, SEL, UIApplication*,
|
||||
NSDictionary* userInfo,
|
||||
void (^completionHandler)(UIBackgroundFetchResult result)) { getThis (self).didReceiveRemoteNotificationFetchCompletionHandler (userInfo, completionHandler); }
|
||||
|
||||
static void handleActionForRemoteNotificationCompletionHandler (id self, SEL, UIApplication*,
|
||||
NSString* actionIdentifier,
|
||||
NSDictionary* userInfo,
|
||||
NSDictionary* responseInfo,
|
||||
void (^completionHandler)()) { getThis (self).handleActionForRemoteNotificationCompletionHandler (actionIdentifier, userInfo, responseInfo, completionHandler); }
|
||||
|
||||
static void didReceiveLocalNotification (id self, SEL, UIApplication*,
|
||||
UILocalNotification* notification) { getThis (self).didReceiveLocalNotification (notification); }
|
||||
|
||||
static void handleActionForLocalNotificationCompletionHandler (id self, SEL, UIApplication*,
|
||||
NSString* actionIdentifier,
|
||||
UILocalNotification* notification,
|
||||
void (^completionHandler)()) { getThis (self).handleActionForLocalNotificationCompletionHandler (actionIdentifier, notification, completionHandler); }
|
||||
|
||||
static void handleActionForLocalNotificationWithResponseCompletionHandler (id self, SEL, UIApplication*,
|
||||
NSString* actionIdentifier,
|
||||
UILocalNotification* notification,
|
||||
NSDictionary* responseInfo,
|
||||
void (^completionHandler)()) { getThis (self). handleActionForLocalNotificationWithResponseCompletionHandler (actionIdentifier, notification, responseInfo, completionHandler); }
|
||||
|
||||
#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
static void willPresentNotificationWithCompletionHandler (id self, SEL, UNUserNotificationCenter*,
|
||||
UNNotification* notification,
|
||||
void (^completionHandler)(UNNotificationPresentationOptions options)) { getThis (self).willPresentNotificationWithCompletionHandler (notification, completionHandler); }
|
||||
|
||||
static void didReceiveNotificationResponseWithCompletionHandler (id self, SEL, UNUserNotificationCenter*,
|
||||
UNNotificationResponse* response,
|
||||
void (^completionHandler)()) { getThis (self).didReceiveNotificationResponseWithCompletionHandler (response, completionHandler); }
|
||||
#endif
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
static Class& getClass()
|
||||
{
|
||||
static Class c;
|
||||
return c;
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
bool PushNotifications::Notification::isValid() const noexcept
|
||||
{
|
||||
const bool iOSEarlierThan10 = std::floor (NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max;
|
||||
|
||||
if (iOSEarlierThan10)
|
||||
return title.isNotEmpty() && body.isNotEmpty() && category.isNotEmpty();
|
||||
|
||||
return title.isNotEmpty() && body.isNotEmpty() && identifier.isNotEmpty() && category.isNotEmpty();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct PushNotifications::Pimpl : private PushNotificationsDelegate
|
||||
{
|
||||
Pimpl (PushNotifications& p)
|
||||
: owner (p)
|
||||
{
|
||||
}
|
||||
|
||||
void requestPermissionsWithSettings (const PushNotifications::Settings& settingsToUse)
|
||||
{
|
||||
settings = settingsToUse;
|
||||
|
||||
auto* categories = [NSMutableSet setWithCapacity: (NSUInteger) settings.categories.size()];
|
||||
|
||||
if (iOSEarlierThan10)
|
||||
{
|
||||
for (const auto& c : settings.categories)
|
||||
{
|
||||
auto* category = (UIUserNotificationCategory*) PushNotificationsDelegateDetails::categoryToNSCategory (c, iOSEarlierThan10);
|
||||
[categories addObject: category];
|
||||
}
|
||||
|
||||
UIUserNotificationType type = NSUInteger ((bool)settings.allowBadge << 0
|
||||
| (bool)settings.allowSound << 1
|
||||
| (bool)settings.allowAlert << 2);
|
||||
|
||||
UIUserNotificationSettings* s = [UIUserNotificationSettings settingsForTypes: type categories: categories];
|
||||
[[UIApplication sharedApplication] registerUserNotificationSettings: s];
|
||||
}
|
||||
#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
else
|
||||
{
|
||||
for (const auto& c : settings.categories)
|
||||
{
|
||||
auto* category = (UNNotificationCategory*) PushNotificationsDelegateDetails::categoryToNSCategory (c, iOSEarlierThan10);
|
||||
[categories addObject: category];
|
||||
}
|
||||
|
||||
UNAuthorizationOptions authOptions = NSUInteger ((bool)settings.allowBadge << 0
|
||||
| (bool)settings.allowSound << 1
|
||||
| (bool)settings.allowAlert << 2);
|
||||
|
||||
[[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories: categories];
|
||||
[[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions: authOptions
|
||||
completionHandler: ^(BOOL /*granted*/, NSError* /*error*/)
|
||||
{
|
||||
requestSettingsUsed();
|
||||
}];
|
||||
}
|
||||
#endif
|
||||
|
||||
[[UIApplication sharedApplication] registerForRemoteNotifications];
|
||||
}
|
||||
|
||||
void requestSettingsUsed()
|
||||
{
|
||||
if (iOSEarlierThan10)
|
||||
{
|
||||
UIUserNotificationSettings* s = [UIApplication sharedApplication].currentUserNotificationSettings;
|
||||
|
||||
settings.allowBadge = s.types & UIUserNotificationTypeBadge;
|
||||
settings.allowSound = s.types & UIUserNotificationTypeSound;
|
||||
settings.allowAlert = s.types & UIUserNotificationTypeAlert;
|
||||
|
||||
for (UIUserNotificationCategory *c in s.categories)
|
||||
settings.categories.add (PushNotificationsDelegateDetails::uiUserNotificationCategoryToCategory (c));
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.notificationSettingsReceived (settings); });
|
||||
}
|
||||
#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
else
|
||||
{
|
||||
|
||||
[[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:
|
||||
^(UNNotificationSettings* s)
|
||||
{
|
||||
[[UNUserNotificationCenter currentNotificationCenter] getNotificationCategoriesWithCompletionHandler:
|
||||
^(NSSet<UNNotificationCategory*>* categories)
|
||||
{
|
||||
settings.allowBadge = s.badgeSetting == UNNotificationSettingEnabled;
|
||||
settings.allowSound = s.soundSetting == UNNotificationSettingEnabled;
|
||||
settings.allowAlert = s.alertSetting == UNNotificationSettingEnabled;
|
||||
|
||||
for (UNNotificationCategory* c in categories)
|
||||
settings.categories.add (PushNotificationsDelegateDetails::unNotificationCategoryToCategory (c));
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.notificationSettingsReceived (settings); });
|
||||
}
|
||||
];
|
||||
|
||||
}];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool areNotificationsEnabled() const { return true; }
|
||||
|
||||
void sendLocalNotification (const Notification& n)
|
||||
{
|
||||
if (iOSEarlierThan10)
|
||||
{
|
||||
auto* notification = PushNotificationsDelegateDetails::juceNotificationToUILocalNotification (n);
|
||||
|
||||
[[UIApplication sharedApplication] scheduleLocalNotification: notification];
|
||||
[notification release];
|
||||
}
|
||||
#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
else
|
||||
{
|
||||
|
||||
UNNotificationRequest* request = PushNotificationsDelegateDetails::juceNotificationToUNNotificationRequest (n);
|
||||
|
||||
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest: request
|
||||
withCompletionHandler: ^(NSError* error)
|
||||
{
|
||||
jassert (error == nil);
|
||||
|
||||
if (error != nil)
|
||||
NSLog (nsStringLiteral ("addNotificationRequest error: %@"), error);
|
||||
}];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void getDeliveredNotifications() const
|
||||
{
|
||||
if (iOSEarlierThan10)
|
||||
{
|
||||
// Not supported on this platform
|
||||
jassertfalse;
|
||||
owner.listeners.call ([] (Listener& l) { l.deliveredNotificationsListReceived ({}); });
|
||||
}
|
||||
#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
else
|
||||
{
|
||||
[[UNUserNotificationCenter currentNotificationCenter] getDeliveredNotificationsWithCompletionHandler:
|
||||
^(NSArray<UNNotification*>* notifications)
|
||||
{
|
||||
Array<PushNotifications::Notification> notifs;
|
||||
|
||||
for (UNNotification* n in notifications)
|
||||
notifs.add (PushNotificationsDelegateDetails::unNotificationToJuceNotification (n));
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.deliveredNotificationsListReceived (notifs); });
|
||||
}];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void removeAllDeliveredNotifications()
|
||||
{
|
||||
if (iOSEarlierThan10)
|
||||
{
|
||||
// Not supported on this platform
|
||||
jassertfalse;
|
||||
}
|
||||
else
|
||||
#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
{
|
||||
|
||||
[[UNUserNotificationCenter currentNotificationCenter] removeAllDeliveredNotifications];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void removeDeliveredNotification (const String& identifier)
|
||||
{
|
||||
if (iOSEarlierThan10)
|
||||
{
|
||||
ignoreUnused (identifier);
|
||||
// Not supported on this platform
|
||||
jassertfalse;
|
||||
}
|
||||
#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
else
|
||||
{
|
||||
|
||||
NSArray<NSString*>* identifiers = [NSArray arrayWithObject: juceStringToNS (identifier)];
|
||||
|
||||
[[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers: identifiers];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void setupChannels (const Array<ChannelGroup>& groups, const Array<Channel>& channels)
|
||||
{
|
||||
ignoreUnused (groups, channels);
|
||||
}
|
||||
|
||||
void getPendingLocalNotifications() const
|
||||
{
|
||||
if (iOSEarlierThan10)
|
||||
{
|
||||
Array<PushNotifications::Notification> notifs;
|
||||
|
||||
for (UILocalNotification* n in [UIApplication sharedApplication].scheduledLocalNotifications)
|
||||
notifs.add (PushNotificationsDelegateDetails::uiLocalNotificationToJuceNotification (n));
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.pendingLocalNotificationsListReceived (notifs); });
|
||||
}
|
||||
#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
else
|
||||
{
|
||||
|
||||
[[UNUserNotificationCenter currentNotificationCenter] getPendingNotificationRequestsWithCompletionHandler:
|
||||
^(NSArray<UNNotificationRequest*>* requests)
|
||||
{
|
||||
Array<PushNotifications::Notification> notifs;
|
||||
|
||||
for (UNNotificationRequest* r : requests)
|
||||
notifs.add (PushNotificationsDelegateDetails::unNotificationRequestToJuceNotification (r));
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.pendingLocalNotificationsListReceived (notifs); });
|
||||
}
|
||||
];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void removePendingLocalNotification (const String& identifier)
|
||||
{
|
||||
if (iOSEarlierThan10)
|
||||
{
|
||||
// Not supported on this platform
|
||||
jassertfalse;
|
||||
}
|
||||
#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
else
|
||||
{
|
||||
|
||||
NSArray<NSString*>* identifiers = [NSArray arrayWithObject: juceStringToNS (identifier)];
|
||||
|
||||
[[UNUserNotificationCenter currentNotificationCenter] removePendingNotificationRequestsWithIdentifiers: identifiers];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void removeAllPendingLocalNotifications()
|
||||
{
|
||||
if (iOSEarlierThan10)
|
||||
{
|
||||
[[UIApplication sharedApplication] cancelAllLocalNotifications];
|
||||
}
|
||||
#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
else
|
||||
{
|
||||
[[UNUserNotificationCenter currentNotificationCenter] removeAllPendingNotificationRequests];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
String getDeviceToken()
|
||||
{
|
||||
// You need to call requestPermissionsWithSettings() first.
|
||||
jassert (initialised);
|
||||
|
||||
return deviceToken;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
//PushNotificationsDelegate
|
||||
void didRegisterUserNotificationSettings (UIUserNotificationSettings*) override
|
||||
{
|
||||
requestSettingsUsed();
|
||||
}
|
||||
|
||||
void registeredForRemoteNotifications (NSData* deviceTokenToUse) override
|
||||
{
|
||||
NSString* deviceTokenString = [[[[deviceTokenToUse description]
|
||||
stringByReplacingOccurrencesOfString: nsStringLiteral ("<") withString: nsStringLiteral ("")]
|
||||
stringByReplacingOccurrencesOfString: nsStringLiteral (">") withString: nsStringLiteral ("")]
|
||||
stringByReplacingOccurrencesOfString: nsStringLiteral (" ") withString: nsStringLiteral ("")];
|
||||
|
||||
deviceToken = nsStringToJuce (deviceTokenString);
|
||||
|
||||
initialised = true;
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.deviceTokenRefreshed (deviceToken); });
|
||||
}
|
||||
|
||||
void failedToRegisterForRemoteNotifications (NSError* error) override
|
||||
{
|
||||
ignoreUnused (error);
|
||||
|
||||
deviceToken.clear();
|
||||
}
|
||||
|
||||
void didReceiveRemoteNotification (NSDictionary* userInfo) override
|
||||
{
|
||||
auto n = PushNotificationsDelegateDetails::nsDictionaryToJuceNotification (userInfo);
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.handleNotification (false, n); });
|
||||
}
|
||||
|
||||
void didReceiveRemoteNotificationFetchCompletionHandler (NSDictionary* userInfo,
|
||||
void (^completionHandler)(UIBackgroundFetchResult result)) override
|
||||
{
|
||||
didReceiveRemoteNotification (userInfo);
|
||||
completionHandler (UIBackgroundFetchResultNewData);
|
||||
}
|
||||
|
||||
void handleActionForRemoteNotificationCompletionHandler (NSString* actionIdentifier,
|
||||
NSDictionary* userInfo,
|
||||
NSDictionary* responseInfo,
|
||||
void (^completionHandler)()) override
|
||||
{
|
||||
auto n = PushNotificationsDelegateDetails::nsDictionaryToJuceNotification (userInfo);
|
||||
auto actionString = nsStringToJuce (actionIdentifier);
|
||||
auto response = PushNotificationsDelegateDetails::getUserResponseFromNSDictionary (responseInfo);
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (false, n, actionString, response); });
|
||||
|
||||
completionHandler();
|
||||
}
|
||||
|
||||
void didReceiveLocalNotification (UILocalNotification* notification) override
|
||||
{
|
||||
auto n = PushNotificationsDelegateDetails::uiLocalNotificationToJuceNotification (notification);
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.handleNotification (true, n); });
|
||||
}
|
||||
|
||||
void handleActionForLocalNotificationCompletionHandler (NSString* actionIdentifier,
|
||||
UILocalNotification* notification,
|
||||
void (^completionHandler)()) override
|
||||
{
|
||||
handleActionForLocalNotificationWithResponseCompletionHandler (actionIdentifier,
|
||||
notification,
|
||||
nil,
|
||||
completionHandler);
|
||||
}
|
||||
|
||||
void handleActionForLocalNotificationWithResponseCompletionHandler (NSString* actionIdentifier,
|
||||
UILocalNotification* notification,
|
||||
NSDictionary* responseInfo,
|
||||
void (^completionHandler)()) override
|
||||
{
|
||||
auto n = PushNotificationsDelegateDetails::uiLocalNotificationToJuceNotification (notification);
|
||||
auto actionString = nsStringToJuce (actionIdentifier);
|
||||
auto response = PushNotificationsDelegateDetails::getUserResponseFromNSDictionary (responseInfo);
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (true, n, actionString, response); });
|
||||
|
||||
completionHandler();
|
||||
}
|
||||
|
||||
#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
void willPresentNotificationWithCompletionHandler (UNNotification* notification,
|
||||
void (^completionHandler)(UNNotificationPresentationOptions options)) override
|
||||
{
|
||||
NSUInteger options = NSUInteger ((int)settings.allowBadge << 0
|
||||
| (int)settings.allowSound << 1
|
||||
| (int)settings.allowAlert << 2);
|
||||
|
||||
ignoreUnused (notification);
|
||||
|
||||
completionHandler (options);
|
||||
}
|
||||
|
||||
void didReceiveNotificationResponseWithCompletionHandler (UNNotificationResponse* response,
|
||||
void (^completionHandler)()) override
|
||||
{
|
||||
const bool remote = [response.notification.request.trigger isKindOfClass: [UNPushNotificationTrigger class]];
|
||||
|
||||
auto actionString = nsStringToJuce (response.actionIdentifier);
|
||||
|
||||
if (actionString == "com.apple.UNNotificationDefaultActionIdentifier")
|
||||
actionString.clear();
|
||||
else if (actionString == "com.apple.UNNotificationDismissActionIdentifier")
|
||||
actionString = "com.juce.NotificationDeleted";
|
||||
|
||||
auto n = PushNotificationsDelegateDetails::unNotificationToJuceNotification (response.notification);
|
||||
|
||||
String responseString;
|
||||
|
||||
if ([response isKindOfClass: [UNTextInputNotificationResponse class]])
|
||||
{
|
||||
UNTextInputNotificationResponse* textResponse = (UNTextInputNotificationResponse*)response;
|
||||
responseString = nsStringToJuce (textResponse.userText);
|
||||
}
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (! remote, n, actionString, responseString); });
|
||||
completionHandler();
|
||||
}
|
||||
#endif
|
||||
|
||||
void subscribeToTopic (const String& topic) { ignoreUnused (topic); }
|
||||
void unsubscribeFromTopic (const String& topic) { ignoreUnused (topic); }
|
||||
|
||||
void sendUpstreamMessage (const String& serverSenderId,
|
||||
const String& collapseKey,
|
||||
const String& messageId,
|
||||
const String& messageType,
|
||||
int timeToLive,
|
||||
const StringPairArray& additionalData)
|
||||
{
|
||||
ignoreUnused (serverSenderId, collapseKey, messageId, messageType);
|
||||
ignoreUnused (timeToLive, additionalData);
|
||||
}
|
||||
|
||||
private:
|
||||
PushNotifications& owner;
|
||||
|
||||
const bool iOSEarlierThan10 = std::floor (NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max;
|
||||
|
||||
bool initialised = false;
|
||||
String deviceToken;
|
||||
|
||||
PushNotifications::Settings settings;
|
||||
};
|
||||
|
||||
} // namespace juce
|
133
modules/juce_gui_extra/native/juce_ios_UIViewComponent.mm
Normal file
133
modules/juce_gui_extra/native/juce_ios_UIViewComponent.mm
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.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
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 UIViewComponent::Pimpl : public ComponentMovementWatcher
|
||||
{
|
||||
public:
|
||||
Pimpl (UIView* v, Component& comp)
|
||||
: ComponentMovementWatcher (&comp),
|
||||
view (v),
|
||||
owner (comp)
|
||||
{
|
||||
[view retain];
|
||||
|
||||
if (owner.isShowing())
|
||||
componentPeerChanged();
|
||||
}
|
||||
|
||||
~Pimpl()
|
||||
{
|
||||
[view removeFromSuperview];
|
||||
[view release];
|
||||
}
|
||||
|
||||
void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
|
||||
{
|
||||
auto* topComp = owner.getTopLevelComponent();
|
||||
|
||||
if (topComp->getPeer() != nullptr)
|
||||
{
|
||||
auto pos = topComp->getLocalPoint (&owner, Point<int>());
|
||||
|
||||
[view setFrame: CGRectMake ((float) pos.x, (float) pos.y,
|
||||
(float) owner.getWidth(), (float) owner.getHeight())];
|
||||
}
|
||||
}
|
||||
|
||||
void componentPeerChanged() override
|
||||
{
|
||||
auto* peer = owner.getPeer();
|
||||
|
||||
if (currentPeer != peer)
|
||||
{
|
||||
if ([view superview] != nil)
|
||||
[view removeFromSuperview]; // Must be careful not to call this unless it's required - e.g. some Apple AU views
|
||||
// override the call and use it as a sign that they're being deleted, which breaks everything..
|
||||
currentPeer = peer;
|
||||
|
||||
if (peer != nullptr)
|
||||
{
|
||||
UIView* peerView = (UIView*) peer->getNativeHandle();
|
||||
[peerView addSubview: view];
|
||||
componentMovedOrResized (false, false);
|
||||
}
|
||||
}
|
||||
|
||||
[view setHidden: ! owner.isShowing()];
|
||||
}
|
||||
|
||||
void componentVisibilityChanged() override
|
||||
{
|
||||
componentPeerChanged();
|
||||
}
|
||||
|
||||
Rectangle<int> getViewBounds() const
|
||||
{
|
||||
CGRect r = [view frame];
|
||||
return Rectangle<int> ((int) r.size.width, (int) r.size.height);
|
||||
}
|
||||
|
||||
UIView* const view;
|
||||
|
||||
private:
|
||||
Component& owner;
|
||||
ComponentPeer* currentPeer = nullptr;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
UIViewComponent::UIViewComponent() {}
|
||||
UIViewComponent::~UIViewComponent() {}
|
||||
|
||||
void UIViewComponent::setView (void* view)
|
||||
{
|
||||
if (view != getView())
|
||||
{
|
||||
pimpl.reset();
|
||||
|
||||
if (view != nullptr)
|
||||
pimpl.reset (new Pimpl ((UIView*) view, *this));
|
||||
}
|
||||
}
|
||||
|
||||
void* UIViewComponent::getView() const
|
||||
{
|
||||
return pimpl == nullptr ? nullptr : pimpl->view;
|
||||
}
|
||||
|
||||
void UIViewComponent::resizeToFitView()
|
||||
{
|
||||
if (pimpl != nullptr)
|
||||
setBounds (pimpl->getViewBounds());
|
||||
}
|
||||
|
||||
void UIViewComponent::paint (Graphics&) {}
|
||||
|
||||
} // namespace juce
|
149
modules/juce_gui_extra/native/juce_linux_X11_SystemTrayIcon.cpp
Normal file
149
modules/juce_gui_extra/native/juce_linux_X11_SystemTrayIcon.cpp
Normal file
@ -0,0 +1,149 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
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 SystemTrayIconComponent::Pimpl
|
||||
{
|
||||
public:
|
||||
Pimpl (const Image& im, Window windowH) : image (im)
|
||||
{
|
||||
ScopedXDisplay xDisplay;
|
||||
auto display = xDisplay.display;
|
||||
|
||||
ScopedXLock xlock (display);
|
||||
|
||||
Screen* const screen = XDefaultScreenOfDisplay (display);
|
||||
const int screenNumber = XScreenNumberOfScreen (screen);
|
||||
|
||||
String screenAtom ("_NET_SYSTEM_TRAY_S");
|
||||
screenAtom << screenNumber;
|
||||
Atom selectionAtom = Atoms::getCreating (display, screenAtom.toUTF8());
|
||||
|
||||
XGrabServer (display);
|
||||
Window managerWin = XGetSelectionOwner (display, selectionAtom);
|
||||
|
||||
if (managerWin != None)
|
||||
XSelectInput (display, managerWin, StructureNotifyMask);
|
||||
|
||||
XUngrabServer (display);
|
||||
XFlush (display);
|
||||
|
||||
if (managerWin != None)
|
||||
{
|
||||
XEvent ev = { 0 };
|
||||
ev.xclient.type = ClientMessage;
|
||||
ev.xclient.window = managerWin;
|
||||
ev.xclient.message_type = Atoms::getCreating (display, "_NET_SYSTEM_TRAY_OPCODE");
|
||||
ev.xclient.format = 32;
|
||||
ev.xclient.data.l[0] = CurrentTime;
|
||||
ev.xclient.data.l[1] = 0 /*SYSTEM_TRAY_REQUEST_DOCK*/;
|
||||
ev.xclient.data.l[2] = (long) windowH;
|
||||
ev.xclient.data.l[3] = 0;
|
||||
ev.xclient.data.l[4] = 0;
|
||||
|
||||
XSendEvent (display, managerWin, False, NoEventMask, &ev);
|
||||
XSync (display, False);
|
||||
}
|
||||
|
||||
// For older KDE's ...
|
||||
long atomData = 1;
|
||||
Atom trayAtom = Atoms::getCreating (display, "KWM_DOCKWINDOW");
|
||||
XChangeProperty (display, windowH, trayAtom, trayAtom, 32, PropModeReplace, (unsigned char*) &atomData, 1);
|
||||
|
||||
// For more recent KDE's...
|
||||
trayAtom = Atoms::getCreating (display, "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR");
|
||||
XChangeProperty (display, windowH, trayAtom, XA_WINDOW, 32, PropModeReplace, (unsigned char*) &windowH, 1);
|
||||
|
||||
// A minimum size must be specified for GNOME and Xfce, otherwise the icon is displayed with a width of 1
|
||||
XSizeHints* hints = XAllocSizeHints();
|
||||
hints->flags = PMinSize;
|
||||
hints->min_width = 22;
|
||||
hints->min_height = 22;
|
||||
XSetWMNormalHints (display, windowH, hints);
|
||||
XFree (hints);
|
||||
}
|
||||
|
||||
Image image;
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
void SystemTrayIconComponent::setIconImage (const Image& newImage)
|
||||
{
|
||||
pimpl.reset();
|
||||
|
||||
if (newImage.isValid())
|
||||
{
|
||||
if (! isOnDesktop())
|
||||
addToDesktop (0);
|
||||
|
||||
pimpl.reset (new Pimpl (newImage, (Window) getWindowHandle()));
|
||||
|
||||
setVisible (true);
|
||||
toFront (false);
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::paint (Graphics& g)
|
||||
{
|
||||
if (pimpl != nullptr)
|
||||
g.drawImage (pimpl->image, getLocalBounds().toFloat(),
|
||||
RectanglePlacement::xLeft | RectanglePlacement::yTop | RectanglePlacement::onlyReduceInSize);
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::setIconTooltip (const String& /*tooltip*/)
|
||||
{
|
||||
// xxx Not implemented!
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::setHighlighted (bool)
|
||||
{
|
||||
// xxx Not implemented!
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::showInfoBubble (const String& /*title*/, const String& /*content*/)
|
||||
{
|
||||
// xxx Not implemented!
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::hideInfoBubble()
|
||||
{
|
||||
// xxx Not implemented!
|
||||
}
|
||||
|
||||
void* SystemTrayIconComponent::getNativeHandle() const
|
||||
{
|
||||
return getWindowHandle();
|
||||
}
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,824 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
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 int juce_gtkWebkitMain (int argc, const char* argv[]);
|
||||
|
||||
class CommandReceiver
|
||||
{
|
||||
public:
|
||||
struct Responder
|
||||
{
|
||||
virtual ~Responder() {}
|
||||
|
||||
virtual void handleCommand (const String& cmd, const var& param) = 0;
|
||||
virtual void receiverHadError() = 0;
|
||||
};
|
||||
|
||||
CommandReceiver (Responder* responderToUse, int inputChannelToUse)
|
||||
: responder (responderToUse), inChannel (inputChannelToUse)
|
||||
{
|
||||
setBlocking (inChannel, false);
|
||||
}
|
||||
|
||||
static void setBlocking (int fd, bool shouldBlock)
|
||||
{
|
||||
int flags = fcntl (fd, F_GETFL);
|
||||
fcntl (fd, F_SETFL, (shouldBlock ? (flags & ~O_NONBLOCK)
|
||||
: (flags | O_NONBLOCK)));
|
||||
}
|
||||
|
||||
int getFd() const { return inChannel; }
|
||||
|
||||
void tryNextRead()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
size_t len = (receivingLength ? sizeof (size_t) : bufferLength.len);
|
||||
|
||||
if (! receivingLength)
|
||||
buffer.realloc (len);
|
||||
|
||||
char* dst = (receivingLength ? bufferLength.data : buffer.getData());
|
||||
|
||||
ssize_t actual = read (inChannel, &dst[pos], static_cast<size_t> (len - pos));
|
||||
|
||||
if (actual < 0)
|
||||
{
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
pos += static_cast<size_t> (actual);
|
||||
|
||||
if (pos == len)
|
||||
{
|
||||
pos = 0;
|
||||
|
||||
if (! receivingLength)
|
||||
parseJSON (String (buffer.getData(), bufferLength.len));
|
||||
|
||||
receivingLength = (! receivingLength);
|
||||
}
|
||||
}
|
||||
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK && responder != nullptr)
|
||||
responder->receiverHadError();
|
||||
}
|
||||
|
||||
static void sendCommand (int outChannel, const String& cmd, const var& params)
|
||||
{
|
||||
DynamicObject::Ptr obj = new DynamicObject;
|
||||
|
||||
obj->setProperty (getCmdIdentifier(), cmd);
|
||||
|
||||
if (! params.isVoid())
|
||||
obj->setProperty (getParamIdentifier(), params);
|
||||
|
||||
String json (JSON::toString (var (obj)));
|
||||
|
||||
size_t jsonLength = static_cast<size_t> (json.length());
|
||||
size_t len = sizeof (size_t) + jsonLength;
|
||||
|
||||
HeapBlock<char> buffer (len);
|
||||
char* dst = buffer.getData();
|
||||
|
||||
memcpy (dst, &jsonLength, sizeof (size_t));
|
||||
dst += sizeof (size_t);
|
||||
|
||||
memcpy (dst, json.toRawUTF8(), jsonLength);
|
||||
|
||||
ssize_t ret;
|
||||
|
||||
do
|
||||
{
|
||||
ret = write (outChannel, buffer.getData(), len);
|
||||
} while (ret == -1 && errno == EINTR);
|
||||
}
|
||||
|
||||
private:
|
||||
void parseJSON (const String& json)
|
||||
{
|
||||
var object (JSON::fromString (json));
|
||||
|
||||
if (! object.isVoid())
|
||||
{
|
||||
String cmd (object.getProperty (getCmdIdentifier(), var()).toString());
|
||||
var params (object.getProperty (getParamIdentifier(), var()));
|
||||
|
||||
if (responder != nullptr)
|
||||
responder->handleCommand (cmd, params);
|
||||
}
|
||||
}
|
||||
|
||||
static Identifier getCmdIdentifier() { static Identifier Id ("cmd"); return Id; }
|
||||
static Identifier getParamIdentifier() { static Identifier Id ("params"); return Id; }
|
||||
|
||||
Responder* responder;
|
||||
int inChannel;
|
||||
size_t pos = 0;
|
||||
bool receivingLength = true;
|
||||
union { char data [sizeof (size_t)]; size_t len; } bufferLength;
|
||||
HeapBlock<char> buffer;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class GtkChildProcess : private CommandReceiver::Responder
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
GtkChildProcess (int inChannel, int outChannelToUse)
|
||||
: outChannel (outChannelToUse), receiver (this, inChannel)
|
||||
{}
|
||||
|
||||
typedef void (*SetHardwareAcclPolicyFunctionPtr) (WebKitSettings*, int);
|
||||
|
||||
int entry()
|
||||
{
|
||||
CommandReceiver::setBlocking (outChannel, true);
|
||||
|
||||
gtk_init (nullptr, nullptr);
|
||||
|
||||
WebKitSettings* settings = webkit_settings_new();
|
||||
|
||||
// webkit_settings_set_hardware_acceleration_policy was only added recently to webkit2
|
||||
// but is needed when running a WebBrowserComponent in a Parallels VM with 3D acceleration enabled
|
||||
auto setHardwarePolicy
|
||||
= reinterpret_cast<SetHardwareAcclPolicyFunctionPtr> (dlsym (RTLD_DEFAULT, "webkit_settings_set_hardware_acceleration_policy"));
|
||||
|
||||
if (setHardwarePolicy != nullptr)
|
||||
setHardwarePolicy (settings, 2 /*WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER*/);
|
||||
|
||||
GtkWidget *plug;
|
||||
|
||||
plug = gtk_plug_new(0);
|
||||
GtkWidget* container;
|
||||
container = gtk_scrolled_window_new (nullptr, nullptr);
|
||||
|
||||
GtkWidget* webviewWidget = webkit_web_view_new_with_settings (settings);
|
||||
webview = WEBKIT_WEB_VIEW (webviewWidget);
|
||||
|
||||
|
||||
gtk_container_add (GTK_CONTAINER (container), webviewWidget);
|
||||
gtk_container_add (GTK_CONTAINER (plug), container);
|
||||
|
||||
webkit_web_view_load_uri (webview, "about:blank");
|
||||
|
||||
g_signal_connect (webview, "decide-policy",
|
||||
G_CALLBACK (decidePolicyCallback), this);
|
||||
|
||||
g_signal_connect (webview, "load-changed",
|
||||
G_CALLBACK (loadChangedCallback), this);
|
||||
|
||||
g_signal_connect (webview, "load-failed",
|
||||
G_CALLBACK (loadFailedCallback), this);
|
||||
|
||||
gtk_widget_show_all (plug);
|
||||
unsigned long wID = (unsigned long) gtk_plug_get_id (GTK_PLUG (plug));
|
||||
|
||||
|
||||
ssize_t ret;
|
||||
|
||||
do {
|
||||
ret = write (outChannel, &wID, sizeof (wID));
|
||||
} while (ret == -1 && errno == EINTR);
|
||||
|
||||
g_unix_fd_add (receiver.getFd(), G_IO_IN, pipeReadyStatic, this);
|
||||
receiver.tryNextRead();
|
||||
|
||||
gtk_main();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void goToURL (const var& params)
|
||||
{
|
||||
static Identifier urlIdentifier ("url");
|
||||
String url (params.getProperty (urlIdentifier, var()).toString());
|
||||
|
||||
webkit_web_view_load_uri (webview, url.toRawUTF8());
|
||||
}
|
||||
|
||||
void handleDecisionResponse (const var& params)
|
||||
{
|
||||
WebKitPolicyDecision* decision
|
||||
= (WebKitPolicyDecision*) ((int64) params.getProperty ("decision_id", var (0)));
|
||||
bool allow = params.getProperty ("allow", var (false));
|
||||
|
||||
if (decision != nullptr && decisions.contains (decision))
|
||||
{
|
||||
if (allow)
|
||||
webkit_policy_decision_use (decision);
|
||||
else
|
||||
webkit_policy_decision_ignore (decision);
|
||||
|
||||
decisions.removeAllInstancesOf (decision);
|
||||
g_object_unref (decision);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void handleCommand (const String& cmd, const var& params) override
|
||||
{
|
||||
if (cmd == "quit") quit();
|
||||
else if (cmd == "goToURL") goToURL (params);
|
||||
else if (cmd == "goBack") webkit_web_view_go_back (webview);
|
||||
else if (cmd == "goForward") webkit_web_view_go_forward (webview);
|
||||
else if (cmd == "refresh") webkit_web_view_reload (webview);
|
||||
else if (cmd == "stop") webkit_web_view_stop_loading (webview);
|
||||
else if (cmd == "decision") handleDecisionResponse (params);
|
||||
}
|
||||
|
||||
void receiverHadError() override
|
||||
{
|
||||
exit (-1);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool pipeReady (gint fd, GIOCondition)
|
||||
{
|
||||
if (fd == receiver.getFd())
|
||||
{
|
||||
receiver.tryNextRead();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void quit()
|
||||
{
|
||||
gtk_main_quit();
|
||||
}
|
||||
|
||||
bool onNavigation (String frameName,
|
||||
WebKitNavigationAction* action,
|
||||
WebKitPolicyDecision* decision)
|
||||
{
|
||||
if (decision != nullptr && frameName.isEmpty())
|
||||
{
|
||||
g_object_ref (decision);
|
||||
decisions.add (decision);
|
||||
|
||||
DynamicObject::Ptr params = new DynamicObject;
|
||||
|
||||
params->setProperty ("url", String (webkit_uri_request_get_uri (webkit_navigation_action_get_request (action))));
|
||||
params->setProperty ("decision_id", (int64) decision);
|
||||
CommandReceiver::sendCommand (outChannel, "pageAboutToLoad", var (params));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool onNewWindow (String /*frameName*/,
|
||||
WebKitNavigationAction* action,
|
||||
WebKitPolicyDecision* decision)
|
||||
{
|
||||
if (decision != nullptr)
|
||||
{
|
||||
DynamicObject::Ptr params = new DynamicObject;
|
||||
|
||||
params->setProperty ("url", String (webkit_uri_request_get_uri (webkit_navigation_action_get_request (action))));
|
||||
CommandReceiver::sendCommand (outChannel, "newWindowAttemptingToLoad", var (params));
|
||||
|
||||
// never allow new windows
|
||||
webkit_policy_decision_ignore (decision);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void onLoadChanged (WebKitLoadEvent loadEvent)
|
||||
{
|
||||
if (loadEvent == WEBKIT_LOAD_FINISHED)
|
||||
{
|
||||
DynamicObject::Ptr params = new DynamicObject;
|
||||
|
||||
params->setProperty ("url", String (webkit_web_view_get_uri (webview)));
|
||||
CommandReceiver::sendCommand (outChannel, "pageFinishedLoading", var (params));
|
||||
}
|
||||
}
|
||||
|
||||
bool onDecidePolicy (WebKitPolicyDecision* decision,
|
||||
WebKitPolicyDecisionType decisionType)
|
||||
{
|
||||
switch (decisionType)
|
||||
{
|
||||
case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION:
|
||||
{
|
||||
WebKitNavigationPolicyDecision* navigationDecision = WEBKIT_NAVIGATION_POLICY_DECISION (decision);
|
||||
const char* frameName = webkit_navigation_policy_decision_get_frame_name (navigationDecision);
|
||||
|
||||
return onNavigation (String (frameName != nullptr ? frameName : ""),
|
||||
webkit_navigation_policy_decision_get_navigation_action (navigationDecision),
|
||||
decision);
|
||||
}
|
||||
break;
|
||||
case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
|
||||
{
|
||||
WebKitNavigationPolicyDecision* navigationDecision = WEBKIT_NAVIGATION_POLICY_DECISION (decision);
|
||||
const char* frameName = webkit_navigation_policy_decision_get_frame_name (navigationDecision);
|
||||
|
||||
return onNewWindow (String (frameName != nullptr ? frameName : ""),
|
||||
webkit_navigation_policy_decision_get_navigation_action (navigationDecision),
|
||||
decision);
|
||||
}
|
||||
break;
|
||||
case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
|
||||
{
|
||||
WebKitResponsePolicyDecision *response = WEBKIT_RESPONSE_POLICY_DECISION (decision);
|
||||
|
||||
// for now just always allow response requests
|
||||
ignoreUnused (response);
|
||||
webkit_policy_decision_use (decision);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void onLoadFailed (GError* error)
|
||||
{
|
||||
DynamicObject::Ptr params = new DynamicObject;
|
||||
|
||||
params->setProperty ("error", String (error != nullptr ? error->message : "unknown error"));
|
||||
CommandReceiver::sendCommand (outChannel, "pageLoadHadNetworkError", var (params));
|
||||
}
|
||||
|
||||
private:
|
||||
static gboolean pipeReadyStatic (gint fd, GIOCondition condition, gpointer user)
|
||||
{
|
||||
return (reinterpret_cast<GtkChildProcess*> (user)->pipeReady (fd, condition) ? TRUE : FALSE);
|
||||
}
|
||||
|
||||
static gboolean decidePolicyCallback (WebKitWebView*,
|
||||
WebKitPolicyDecision* decision,
|
||||
WebKitPolicyDecisionType decisionType,
|
||||
gpointer user)
|
||||
{
|
||||
GtkChildProcess& owner = *reinterpret_cast<GtkChildProcess*> (user);
|
||||
return (owner.onDecidePolicy (decision, decisionType) ? TRUE : FALSE);
|
||||
}
|
||||
|
||||
static void loadChangedCallback (WebKitWebView*,
|
||||
WebKitLoadEvent loadEvent,
|
||||
gpointer user)
|
||||
{
|
||||
GtkChildProcess& owner = *reinterpret_cast<GtkChildProcess*> (user);
|
||||
owner.onLoadChanged (loadEvent);
|
||||
}
|
||||
|
||||
static void loadFailedCallback (WebKitWebView*,
|
||||
WebKitLoadEvent /*loadEvent*/,
|
||||
gchar* /*failing_uri*/,
|
||||
GError* error,
|
||||
gpointer user)
|
||||
{
|
||||
GtkChildProcess& owner = *reinterpret_cast<GtkChildProcess*> (user);
|
||||
owner.onLoadFailed (error);
|
||||
}
|
||||
|
||||
int outChannel;
|
||||
CommandReceiver receiver;
|
||||
WebKitWebView* webview = nullptr;
|
||||
Array<WebKitPolicyDecision*> decisions;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class WebBrowserComponent::Pimpl : private Thread,
|
||||
private CommandReceiver::Responder
|
||||
{
|
||||
public:
|
||||
Pimpl (WebBrowserComponent& parent)
|
||||
: Thread ("Webview"), owner (parent)
|
||||
{}
|
||||
|
||||
~Pimpl()
|
||||
{
|
||||
quit();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void init()
|
||||
{
|
||||
launchChild();
|
||||
|
||||
int ret = pipe (threadControl);
|
||||
|
||||
ignoreUnused (ret);
|
||||
jassert (ret == 0);
|
||||
|
||||
CommandReceiver::setBlocking (inChannel, true);
|
||||
CommandReceiver::setBlocking (outChannel, true);
|
||||
CommandReceiver::setBlocking (threadControl[0], false);
|
||||
CommandReceiver::setBlocking (threadControl[1], true);
|
||||
|
||||
unsigned long windowHandle;
|
||||
ssize_t actual = read (inChannel, &windowHandle, sizeof (windowHandle));
|
||||
|
||||
if (actual != sizeof (windowHandle))
|
||||
{
|
||||
killChild();
|
||||
return;
|
||||
}
|
||||
|
||||
receiver.reset (new CommandReceiver (this, inChannel));
|
||||
startThread();
|
||||
|
||||
xembed.reset (new XEmbedComponent (windowHandle));
|
||||
owner.addAndMakeVisible (xembed.get());
|
||||
}
|
||||
|
||||
void quit()
|
||||
{
|
||||
if (isThreadRunning())
|
||||
{
|
||||
signalThreadShouldExit();
|
||||
|
||||
char ignore = 0;
|
||||
ssize_t ret;
|
||||
|
||||
do
|
||||
{
|
||||
ret = write (threadControl[1], &ignore, 1);
|
||||
} while (ret == -1 && errno == EINTR);
|
||||
|
||||
waitForThreadToExit (-1);
|
||||
receiver = nullptr;
|
||||
}
|
||||
|
||||
if (childProcess != 0)
|
||||
{
|
||||
CommandReceiver::sendCommand (outChannel, "quit", var());
|
||||
killChild();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData)
|
||||
{
|
||||
DynamicObject::Ptr params = new DynamicObject;
|
||||
|
||||
params->setProperty ("url", url);
|
||||
|
||||
if (headers != nullptr)
|
||||
params->setProperty ("headers", var (*headers));
|
||||
|
||||
if (postData != nullptr)
|
||||
params->setProperty ("postData", var (*postData));
|
||||
|
||||
CommandReceiver::sendCommand (outChannel, "goToURL", var (params));
|
||||
}
|
||||
|
||||
void goBack() { CommandReceiver::sendCommand (outChannel, "goBack", var()); }
|
||||
void goForward() { CommandReceiver::sendCommand (outChannel, "goForward", var()); }
|
||||
void refresh() { CommandReceiver::sendCommand (outChannel, "refresh", var()); }
|
||||
void stop() { CommandReceiver::sendCommand (outChannel, "stop", var()); }
|
||||
|
||||
void resized()
|
||||
{
|
||||
if (xembed != nullptr)
|
||||
xembed->setBounds (owner.getLocalBounds());
|
||||
}
|
||||
private:
|
||||
//==============================================================================
|
||||
void killChild()
|
||||
{
|
||||
if (childProcess != 0)
|
||||
{
|
||||
xembed = nullptr;
|
||||
|
||||
int status = 0, result;
|
||||
|
||||
result = waitpid (childProcess, &status, WNOHANG);
|
||||
for (int i = 0; i < 15 && (! WIFEXITED(status) || result != childProcess); ++i)
|
||||
{
|
||||
Thread::sleep (100);
|
||||
result = waitpid (childProcess, &status, WNOHANG);
|
||||
}
|
||||
|
||||
// clean-up any zombies
|
||||
status = 0;
|
||||
if (! WIFEXITED(status) || result != childProcess)
|
||||
{
|
||||
do
|
||||
{
|
||||
kill (childProcess, SIGTERM);
|
||||
waitpid (childProcess, &status, 0);
|
||||
} while (! WIFEXITED(status));
|
||||
}
|
||||
|
||||
childProcess = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void launchChild()
|
||||
{
|
||||
int ret;
|
||||
int inPipe[2], outPipe[2];
|
||||
|
||||
ret = pipe (inPipe);
|
||||
ignoreUnused (ret); jassert (ret == 0);
|
||||
|
||||
ret = pipe (outPipe);
|
||||
ignoreUnused (ret); jassert (ret == 0);
|
||||
|
||||
int pid = fork();
|
||||
if (pid == 0)
|
||||
{
|
||||
close (inPipe[0]);
|
||||
close (outPipe[1]);
|
||||
|
||||
HeapBlock<const char*> argv (5);
|
||||
StringArray arguments;
|
||||
|
||||
arguments.add (File::getSpecialLocation (File::currentExecutableFile).getFullPathName());
|
||||
arguments.add ("--juce-gtkwebkitfork-child");
|
||||
arguments.add (String (outPipe[0]));
|
||||
arguments.add (String (inPipe [1]));
|
||||
|
||||
for (int i = 0; i < arguments.size(); ++i)
|
||||
argv[i] = arguments[i].toRawUTF8();
|
||||
|
||||
argv[4] = nullptr;
|
||||
|
||||
#if JUCE_STANDALONE_APPLICATION
|
||||
execv (arguments[0].toRawUTF8(), (char**) argv.getData());
|
||||
#else
|
||||
juce_gtkWebkitMain (4, (const char**) argv.getData());
|
||||
#endif
|
||||
exit (0);
|
||||
}
|
||||
|
||||
close (inPipe[1]);
|
||||
close (outPipe[0]);
|
||||
|
||||
inChannel = inPipe[0];
|
||||
outChannel = outPipe[1];
|
||||
|
||||
childProcess = pid;
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
while (! threadShouldExit())
|
||||
{
|
||||
if (shouldExit())
|
||||
return;
|
||||
|
||||
receiver->tryNextRead();
|
||||
|
||||
fd_set set;
|
||||
FD_ZERO (&set);
|
||||
FD_SET (threadControl[0], &set);
|
||||
FD_SET (receiver->getFd(), &set);
|
||||
|
||||
int max_fd = jmax (threadControl[0], receiver->getFd());
|
||||
|
||||
int result = 0;
|
||||
|
||||
while (result == 0 || (result < 0 && errno == EINTR))
|
||||
result = select (max_fd + 1, &set, NULL, NULL, NULL);
|
||||
|
||||
if (result < 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool shouldExit()
|
||||
{
|
||||
char ignore;
|
||||
ssize_t result = read (threadControl[0], &ignore, 1);
|
||||
|
||||
return (result != -1 || (errno != EAGAIN && errno != EWOULDBLOCK));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void handleCommandOnMessageThread (const String& cmd, const var& params)
|
||||
{
|
||||
String url (params.getProperty ("url", var()).toString());
|
||||
|
||||
if (cmd == "pageAboutToLoad") handlePageAboutToLoad (url, params);
|
||||
else if (cmd == "pageFinishedLoading") owner.pageFinishedLoading (url);
|
||||
else if (cmd == "windowCloseRequest") owner.windowCloseRequest();
|
||||
else if (cmd == "newWindowAttemptingToLoad") owner.newWindowAttemptingToLoad (url);
|
||||
else if (cmd == "pageLoadHadNetworkError") handlePageLoadHadNetworkError (params);
|
||||
|
||||
threadBlocker.signal();
|
||||
}
|
||||
|
||||
void handlePageAboutToLoad (const String& url, const var& inputParams)
|
||||
{
|
||||
int64 decision_id = inputParams.getProperty ("decision_id", var (0));
|
||||
|
||||
if (decision_id != 0)
|
||||
{
|
||||
DynamicObject::Ptr params = new DynamicObject;
|
||||
|
||||
params->setProperty ("decision_id", decision_id);
|
||||
params->setProperty ("allow", owner.pageAboutToLoad (url));
|
||||
|
||||
CommandReceiver::sendCommand (outChannel, "decision", var (params));
|
||||
}
|
||||
}
|
||||
|
||||
void handlePageLoadHadNetworkError (const var& params)
|
||||
{
|
||||
String error = params.getProperty ("error", "Unknown error");
|
||||
|
||||
if (owner.pageLoadHadNetworkError (error))
|
||||
goToURL (String ("data:text/plain,") + error, nullptr, nullptr);
|
||||
}
|
||||
|
||||
void handleCommand (const String& cmd, const var& params) override
|
||||
{
|
||||
threadBlocker.reset();
|
||||
|
||||
(new HandleOnMessageThread (this, cmd, params))->post();
|
||||
|
||||
// wait until the command has executed on the message thread
|
||||
// this ensures that Pimpl can never be deleted while the
|
||||
// message has not been executed yet
|
||||
threadBlocker.wait (-1);
|
||||
}
|
||||
|
||||
void receiverHadError() override {}
|
||||
|
||||
//==============================================================================
|
||||
struct HandleOnMessageThread : public CallbackMessage
|
||||
{
|
||||
HandleOnMessageThread (Pimpl* pimpl, const String& cmdToUse, const var& params)
|
||||
: owner (pimpl), cmdToSend (cmdToUse), paramsToSend (params)
|
||||
{}
|
||||
|
||||
void messageCallback() override
|
||||
{
|
||||
owner->handleCommandOnMessageThread (cmdToSend, paramsToSend);
|
||||
}
|
||||
|
||||
Pimpl* owner;
|
||||
String cmdToSend;
|
||||
var paramsToSend;
|
||||
};
|
||||
|
||||
private:
|
||||
WebBrowserComponent& owner;
|
||||
std::unique_ptr<CommandReceiver> receiver;
|
||||
int childProcess = 0, inChannel = 0, outChannel = 0;
|
||||
int threadControl[2];
|
||||
std::unique_ptr<XEmbedComponent> xembed;
|
||||
WaitableEvent threadBlocker;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
WebBrowserComponent::WebBrowserComponent (const bool unloadPageWhenBrowserIsHidden_)
|
||||
: browser (new Pimpl (*this)),
|
||||
blankPageShown (false),
|
||||
unloadPageWhenBrowserIsHidden (unloadPageWhenBrowserIsHidden_)
|
||||
{
|
||||
setOpaque (true);
|
||||
|
||||
browser->init();
|
||||
}
|
||||
|
||||
WebBrowserComponent::~WebBrowserComponent()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void WebBrowserComponent::goToURL (const String& url,
|
||||
const StringArray* headers,
|
||||
const MemoryBlock* postData)
|
||||
{
|
||||
lastURL = url;
|
||||
|
||||
if (headers != nullptr)
|
||||
lastHeaders = *headers;
|
||||
else
|
||||
lastHeaders.clear();
|
||||
|
||||
if (postData != nullptr)
|
||||
lastPostData = *postData;
|
||||
else
|
||||
lastPostData.reset();
|
||||
|
||||
blankPageShown = false;
|
||||
|
||||
browser->goToURL (url, headers, postData);
|
||||
}
|
||||
|
||||
void WebBrowserComponent::stop()
|
||||
{
|
||||
browser->stop();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::goBack()
|
||||
{
|
||||
lastURL.clear();
|
||||
blankPageShown = false;
|
||||
|
||||
browser->goBack();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::goForward()
|
||||
{
|
||||
lastURL.clear();
|
||||
browser->goForward();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::refresh()
|
||||
{
|
||||
browser->refresh();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void WebBrowserComponent::paint (Graphics& g)
|
||||
{
|
||||
g.fillAll (Colours::white);
|
||||
}
|
||||
|
||||
void WebBrowserComponent::checkWindowAssociation()
|
||||
{
|
||||
}
|
||||
|
||||
void WebBrowserComponent::reloadLastURL()
|
||||
{
|
||||
if (lastURL.isNotEmpty())
|
||||
{
|
||||
goToURL (lastURL, &lastHeaders, &lastPostData);
|
||||
lastURL.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void WebBrowserComponent::parentHierarchyChanged()
|
||||
{
|
||||
checkWindowAssociation();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::resized()
|
||||
{
|
||||
if (browser != nullptr)
|
||||
browser->resized();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::visibilityChanged()
|
||||
{
|
||||
checkWindowAssociation();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::focusGained (FocusChangeType)
|
||||
{
|
||||
}
|
||||
|
||||
void WebBrowserComponent::clearCookies()
|
||||
{
|
||||
// Currently not implemented on linux as WebBrowserComponent currently does not
|
||||
// store cookies on linux
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
int juce_gtkWebkitMain (int argc, const char* argv[])
|
||||
{
|
||||
if (argc != 4) return -1;
|
||||
|
||||
|
||||
GtkChildProcess child (String (argv[2]).getIntValue(),
|
||||
String (argv[3]).getIntValue());
|
||||
return child.entry();
|
||||
}
|
||||
|
||||
} // namespace juce
|
685
modules/juce_gui_extra/native/juce_linux_XEmbedComponent.cpp
Normal file
685
modules/juce_gui_extra/native/juce_linux_XEmbedComponent.cpp
Normal file
@ -0,0 +1,685 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
bool juce_handleXEmbedEvent (ComponentPeer*, void*);
|
||||
Window juce_getCurrentFocusWindow (ComponentPeer*);
|
||||
|
||||
//==============================================================================
|
||||
unsigned long juce_createKeyProxyWindow (ComponentPeer*);
|
||||
void juce_deleteKeyProxyWindow (ComponentPeer*);
|
||||
|
||||
//==============================================================================
|
||||
class XEmbedComponent::Pimpl : private ComponentListener
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
maxXEmbedVersionToSupport = 0
|
||||
};
|
||||
|
||||
enum Flags
|
||||
{
|
||||
XEMBED_MAPPED = (1<<0)
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
XEMBED_EMBEDDED_NOTIFY = 0,
|
||||
XEMBED_WINDOW_ACTIVATE = 1,
|
||||
XEMBED_WINDOW_DEACTIVATE = 2,
|
||||
XEMBED_REQUEST_FOCUS = 3,
|
||||
XEMBED_FOCUS_IN = 4,
|
||||
XEMBED_FOCUS_OUT = 5,
|
||||
XEMBED_FOCUS_NEXT = 6,
|
||||
XEMBED_FOCUS_PREV = 7,
|
||||
XEMBED_MODALITY_ON = 10,
|
||||
XEMBED_MODALITY_OFF = 11,
|
||||
XEMBED_REGISTER_ACCELERATOR = 12,
|
||||
XEMBED_UNREGISTER_ACCELERATOR = 13,
|
||||
XEMBED_ACTIVATE_ACCELERATOR = 14
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
XEMBED_FOCUS_CURRENT = 0,
|
||||
XEMBED_FOCUS_FIRST = 1,
|
||||
XEMBED_FOCUS_LAST = 2
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class SharedKeyWindow : public ReferenceCountedObject
|
||||
{
|
||||
public:
|
||||
using Ptr = ReferenceCountedObjectPtr<SharedKeyWindow>;
|
||||
|
||||
//==============================================================================
|
||||
Window getHandle() { return keyProxy; }
|
||||
|
||||
static Window getCurrentFocusWindow (ComponentPeer* peerToLookFor)
|
||||
{
|
||||
auto& keyWindows = getKeyWindows();
|
||||
|
||||
if (peerToLookFor != nullptr)
|
||||
if (auto* foundKeyWindow = keyWindows[peerToLookFor])
|
||||
return foundKeyWindow->keyProxy;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static SharedKeyWindow::Ptr getKeyWindowForPeer (ComponentPeer* peerToLookFor)
|
||||
{
|
||||
jassert (peerToLookFor != nullptr);
|
||||
|
||||
auto& keyWindows = getKeyWindows();
|
||||
auto foundKeyWindow = keyWindows[peerToLookFor];
|
||||
|
||||
if (foundKeyWindow == nullptr)
|
||||
{
|
||||
foundKeyWindow = new SharedKeyWindow (peerToLookFor);
|
||||
keyWindows.set (peerToLookFor, foundKeyWindow);
|
||||
}
|
||||
|
||||
return foundKeyWindow;
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
friend struct ContainerDeletePolicy<SharedKeyWindow>;
|
||||
|
||||
SharedKeyWindow (ComponentPeer* peerToUse)
|
||||
: keyPeer (peerToUse),
|
||||
keyProxy (juce_createKeyProxyWindow (keyPeer))
|
||||
{}
|
||||
|
||||
~SharedKeyWindow()
|
||||
{
|
||||
juce_deleteKeyProxyWindow (keyPeer);
|
||||
|
||||
auto& keyWindows = getKeyWindows();
|
||||
keyWindows.remove (keyPeer);
|
||||
}
|
||||
|
||||
ComponentPeer* keyPeer;
|
||||
Window keyProxy;
|
||||
|
||||
static HashMap<ComponentPeer*, SharedKeyWindow*>& getKeyWindows()
|
||||
{
|
||||
// store a weak reference to the shared key windows
|
||||
static HashMap<ComponentPeer*, SharedKeyWindow*> keyWindows;
|
||||
return keyWindows;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
//==============================================================================
|
||||
Pimpl (XEmbedComponent& parent, Window x11Window,
|
||||
bool wantsKeyboardFocus, bool isClientInitiated, bool shouldAllowResize)
|
||||
: owner (parent), atoms (x11display.display), clientInitiated (isClientInitiated),
|
||||
wantsFocus (wantsKeyboardFocus), allowResize (shouldAllowResize)
|
||||
{
|
||||
getWidgets().add (this);
|
||||
|
||||
createHostWindow();
|
||||
|
||||
if (clientInitiated)
|
||||
setClient (x11Window, true);
|
||||
|
||||
owner.setWantsKeyboardFocus (wantsFocus);
|
||||
owner.addComponentListener (this);
|
||||
}
|
||||
|
||||
~Pimpl()
|
||||
{
|
||||
owner.removeComponentListener (this);
|
||||
setClient (0, true);
|
||||
|
||||
if (host != 0)
|
||||
{
|
||||
auto dpy = getDisplay();
|
||||
XDestroyWindow (dpy, host);
|
||||
XSync (dpy, false);
|
||||
|
||||
const long mask = NoEventMask | KeyPressMask | KeyReleaseMask
|
||||
| EnterWindowMask | LeaveWindowMask | PointerMotionMask
|
||||
| KeymapStateMask | ExposureMask | StructureNotifyMask
|
||||
| FocusChangeMask;
|
||||
|
||||
XEvent event;
|
||||
while (XCheckWindowEvent (dpy, host, mask, &event) == True)
|
||||
{}
|
||||
|
||||
host = 0;
|
||||
}
|
||||
|
||||
getWidgets().removeAllInstancesOf (this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void setClient (Window xembedClient, bool shouldReparent)
|
||||
{
|
||||
removeClient();
|
||||
|
||||
if (xembedClient != 0)
|
||||
{
|
||||
auto dpy = getDisplay();
|
||||
|
||||
client = xembedClient;
|
||||
|
||||
// if the client has initiated the component then keep the clients size
|
||||
// otherwise the client should use the host's window' size
|
||||
if (clientInitiated)
|
||||
{
|
||||
configureNotify();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto newBounds = getX11BoundsFromJuce();
|
||||
XResizeWindow (dpy, client, static_cast<unsigned int> (newBounds.getWidth()),
|
||||
static_cast<unsigned int> (newBounds.getHeight()));
|
||||
}
|
||||
|
||||
XSelectInput (dpy, client, StructureNotifyMask | PropertyChangeMask | FocusChangeMask);
|
||||
getXEmbedMappedFlag();
|
||||
|
||||
if (shouldReparent)
|
||||
XReparentWindow (dpy, client, host, 0, 0);
|
||||
|
||||
if (supportsXembed)
|
||||
sendXEmbedEvent (CurrentTime, XEMBED_EMBEDDED_NOTIFY, 0, (long) host, xembedVersion);
|
||||
|
||||
updateMapping();
|
||||
}
|
||||
}
|
||||
|
||||
void focusGained (FocusChangeType changeType)
|
||||
{
|
||||
if (client != 0 && supportsXembed && wantsFocus)
|
||||
{
|
||||
updateKeyFocus();
|
||||
sendXEmbedEvent (CurrentTime, XEMBED_FOCUS_IN,
|
||||
(changeType == focusChangedByTabKey ? XEMBED_FOCUS_FIRST : XEMBED_FOCUS_CURRENT));
|
||||
}
|
||||
}
|
||||
|
||||
void focusLost (FocusChangeType)
|
||||
{
|
||||
if (client != 0 && supportsXembed && wantsFocus)
|
||||
{
|
||||
sendXEmbedEvent (CurrentTime, XEMBED_FOCUS_OUT);
|
||||
updateKeyFocus();
|
||||
}
|
||||
}
|
||||
|
||||
void broughtToFront()
|
||||
{
|
||||
if (client != 0 && supportsXembed)
|
||||
sendXEmbedEvent (CurrentTime, XEMBED_WINDOW_ACTIVATE);
|
||||
}
|
||||
|
||||
unsigned long getHostWindowID()
|
||||
{
|
||||
// You are using the client initiated version of the protocol. You cannot
|
||||
// retrieve the window id of the host. Please read the documentation for
|
||||
// the XEmebedComponent class.
|
||||
jassert (! clientInitiated);
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
XEmbedComponent& owner;
|
||||
Window client = 0, host = 0;
|
||||
|
||||
ScopedXDisplay x11display;
|
||||
Atoms atoms;
|
||||
|
||||
bool clientInitiated;
|
||||
bool wantsFocus = false;
|
||||
bool allowResize = false;
|
||||
bool supportsXembed = false;
|
||||
bool hasBeenMapped = false;
|
||||
int xembedVersion = maxXEmbedVersionToSupport;
|
||||
|
||||
ComponentPeer* lastPeer = nullptr;
|
||||
SharedKeyWindow::Ptr keyWindow;
|
||||
|
||||
//==============================================================================
|
||||
void componentParentHierarchyChanged (Component&) override { peerChanged (owner.getPeer()); }
|
||||
void componentMovedOrResized (Component&, bool, bool) override
|
||||
{
|
||||
if (host != 0 && lastPeer != nullptr)
|
||||
{
|
||||
auto dpy = getDisplay();
|
||||
auto newBounds = getX11BoundsFromJuce();
|
||||
XWindowAttributes attr;
|
||||
|
||||
if (XGetWindowAttributes (dpy, host, &attr))
|
||||
{
|
||||
Rectangle<int> currentBounds (attr.x, attr.y, attr.width, attr.height);
|
||||
if (currentBounds != newBounds)
|
||||
{
|
||||
XMoveResizeWindow (dpy, host, newBounds.getX(), newBounds.getY(),
|
||||
static_cast<unsigned int> (newBounds.getWidth()),
|
||||
static_cast<unsigned int> (newBounds.getHeight()));
|
||||
}
|
||||
}
|
||||
|
||||
if (client != 0 && XGetWindowAttributes (dpy, client, &attr))
|
||||
{
|
||||
Rectangle<int> currentBounds (attr.x, attr.y, attr.width, attr.height);
|
||||
|
||||
if ((currentBounds.getWidth() != newBounds.getWidth()
|
||||
|| currentBounds.getHeight() != newBounds.getHeight()))
|
||||
{
|
||||
XMoveResizeWindow (dpy, client, 0, 0,
|
||||
static_cast<unsigned int> (newBounds.getWidth()),
|
||||
static_cast<unsigned int> (newBounds.getHeight()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void createHostWindow()
|
||||
{
|
||||
auto dpy = getDisplay();
|
||||
int defaultScreen = XDefaultScreen (dpy);
|
||||
Window root = RootWindow (dpy, defaultScreen);
|
||||
|
||||
XSetWindowAttributes swa;
|
||||
swa.border_pixel = 0;
|
||||
swa.background_pixmap = None;
|
||||
swa.override_redirect = True;
|
||||
swa.event_mask = SubstructureNotifyMask | StructureNotifyMask | FocusChangeMask;
|
||||
|
||||
host = XCreateWindow (dpy, root, 0, 0, 1, 1, 0, CopyFromParent,
|
||||
InputOutput, CopyFromParent,
|
||||
CWEventMask | CWBorderPixel | CWBackPixmap | CWOverrideRedirect,
|
||||
&swa);
|
||||
}
|
||||
|
||||
void removeClient()
|
||||
{
|
||||
if (client != 0)
|
||||
{
|
||||
auto dpy = getDisplay();
|
||||
XSelectInput (dpy, client, 0);
|
||||
|
||||
keyWindow = nullptr;
|
||||
|
||||
int defaultScreen = XDefaultScreen (dpy);
|
||||
Window root = RootWindow (dpy, defaultScreen);
|
||||
|
||||
if (hasBeenMapped)
|
||||
{
|
||||
XUnmapWindow (dpy, client);
|
||||
hasBeenMapped = false;
|
||||
}
|
||||
|
||||
XReparentWindow (dpy, client, root, 0, 0);
|
||||
client = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void updateMapping()
|
||||
{
|
||||
if (client != 0)
|
||||
{
|
||||
const bool shouldBeMapped = getXEmbedMappedFlag();
|
||||
|
||||
if (shouldBeMapped != hasBeenMapped)
|
||||
{
|
||||
hasBeenMapped = shouldBeMapped;
|
||||
|
||||
if (shouldBeMapped)
|
||||
XMapWindow (getDisplay(), client);
|
||||
else
|
||||
XUnmapWindow (getDisplay(), client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Window getParentX11Window()
|
||||
{
|
||||
if (auto peer = owner.getPeer())
|
||||
return reinterpret_cast<Window> (peer->getNativeHandle());
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Display* getDisplay() { return reinterpret_cast<Display*> (x11display.display); }
|
||||
|
||||
//==============================================================================
|
||||
bool getXEmbedMappedFlag()
|
||||
{
|
||||
GetXProperty embedInfo (x11display.display, client, atoms.XembedInfo, 0, 2, false, atoms.XembedInfo);
|
||||
|
||||
if (embedInfo.success && embedInfo.actualFormat == 32
|
||||
&& embedInfo.numItems >= 2 && embedInfo.data != nullptr)
|
||||
{
|
||||
auto* buffer = (long*) embedInfo.data;
|
||||
|
||||
supportsXembed = true;
|
||||
xembedVersion = jmin ((int) maxXEmbedVersionToSupport, (int) buffer[0]);
|
||||
|
||||
return ((buffer[1] & XEMBED_MAPPED) != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
supportsXembed = false;
|
||||
xembedVersion = maxXEmbedVersionToSupport;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void propertyChanged (const Atom& a)
|
||||
{
|
||||
if (a == atoms.XembedInfo)
|
||||
updateMapping();
|
||||
}
|
||||
|
||||
void configureNotify()
|
||||
{
|
||||
XWindowAttributes attr;
|
||||
auto dpy = getDisplay();
|
||||
|
||||
if (XGetWindowAttributes (dpy, client, &attr))
|
||||
{
|
||||
XWindowAttributes hostAttr;
|
||||
|
||||
if (XGetWindowAttributes (dpy, host, &hostAttr))
|
||||
if (attr.width != hostAttr.width || attr.height != hostAttr.height)
|
||||
XResizeWindow (dpy, host, (unsigned int) attr.width, (unsigned int) attr.height);
|
||||
|
||||
// as the client window is not on any screen yet, we need to guess
|
||||
// on which screen it might appear to get a scaling factor :-(
|
||||
auto& displays = Desktop::getInstance().getDisplays();
|
||||
auto* peer = owner.getPeer();
|
||||
const double scale = (peer != nullptr ? displays.getDisplayContaining (peer->getBounds().getCentre())
|
||||
: displays.getMainDisplay()).scale;
|
||||
|
||||
Point<int> topLeftInPeer
|
||||
= (peer != nullptr ? peer->getComponent().getLocalPoint (&owner, Point<int> (0, 0))
|
||||
: owner.getBounds().getTopLeft());
|
||||
|
||||
Rectangle<int> newBounds (topLeftInPeer.getX(), topLeftInPeer.getY(),
|
||||
static_cast<int> (static_cast<double> (attr.width) / scale),
|
||||
static_cast<int> (static_cast<double> (attr.height) / scale));
|
||||
|
||||
|
||||
if (peer != nullptr)
|
||||
newBounds = owner.getLocalArea (&peer->getComponent(), newBounds);
|
||||
|
||||
jassert (newBounds.getX() == 0 && newBounds.getY() == 0);
|
||||
|
||||
if (newBounds != owner.getLocalBounds())
|
||||
owner.setSize (newBounds.getWidth(), newBounds.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
void peerChanged (ComponentPeer* newPeer)
|
||||
{
|
||||
if (newPeer != lastPeer)
|
||||
{
|
||||
if (lastPeer != nullptr)
|
||||
keyWindow = nullptr;
|
||||
|
||||
auto dpy = getDisplay();
|
||||
Window rootWindow = RootWindow (dpy, DefaultScreen (dpy));
|
||||
Rectangle<int> newBounds = getX11BoundsFromJuce();
|
||||
|
||||
if (newPeer == nullptr)
|
||||
XUnmapWindow (dpy, host);
|
||||
|
||||
Window newParent = (newPeer != nullptr ? getParentX11Window() : rootWindow);
|
||||
XReparentWindow (dpy, host, newParent, newBounds.getX(), newBounds.getY());
|
||||
|
||||
lastPeer = newPeer;
|
||||
|
||||
if (newPeer != nullptr)
|
||||
{
|
||||
if (wantsFocus)
|
||||
{
|
||||
keyWindow = SharedKeyWindow::getKeyWindowForPeer (newPeer);
|
||||
updateKeyFocus();
|
||||
}
|
||||
|
||||
componentMovedOrResized (owner, true, true);
|
||||
XMapWindow (dpy, host);
|
||||
|
||||
broughtToFront();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateKeyFocus()
|
||||
{
|
||||
if (lastPeer != nullptr && lastPeer->isFocused())
|
||||
XSetInputFocus (getDisplay(), getCurrentFocusWindow (lastPeer), RevertToParent, CurrentTime);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void handleXembedCmd (const ::Time& /*xTime*/, long opcode, long /*detail*/, long /*data1*/, long /*data2*/)
|
||||
{
|
||||
switch (opcode)
|
||||
{
|
||||
case XEMBED_REQUEST_FOCUS:
|
||||
if (wantsFocus)
|
||||
owner.grabKeyboardFocus();
|
||||
break;
|
||||
|
||||
case XEMBED_FOCUS_NEXT:
|
||||
if (wantsFocus)
|
||||
owner.moveKeyboardFocusToSibling (true);
|
||||
break;
|
||||
|
||||
case XEMBED_FOCUS_PREV:
|
||||
if (wantsFocus)
|
||||
owner.moveKeyboardFocusToSibling (false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool handleX11Event (const XEvent& e)
|
||||
{
|
||||
if (e.xany.window == client && client != 0)
|
||||
{
|
||||
switch (e.type)
|
||||
{
|
||||
case PropertyNotify:
|
||||
propertyChanged (e.xproperty.atom);
|
||||
return true;
|
||||
|
||||
case ConfigureNotify:
|
||||
if (allowResize)
|
||||
configureNotify();
|
||||
else
|
||||
MessageManager::callAsync ([this] {componentMovedOrResized (owner, true, true);});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (e.xany.window == host && host != 0)
|
||||
{
|
||||
switch (e.type)
|
||||
{
|
||||
case ReparentNotify:
|
||||
if (e.xreparent.parent == host && e.xreparent.window != client)
|
||||
{
|
||||
setClient (e.xreparent.window, false);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case CreateNotify:
|
||||
if (e.xcreatewindow.parent != e.xcreatewindow.window && e.xcreatewindow.parent == host && e.xcreatewindow.window != client)
|
||||
{
|
||||
setClient (e.xcreatewindow.window, false);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case GravityNotify:
|
||||
componentMovedOrResized (owner, true, true);
|
||||
return true;
|
||||
|
||||
case ClientMessage:
|
||||
if (e.xclient.message_type == atoms.XembedMsgType && e.xclient.format == 32)
|
||||
{
|
||||
handleXembedCmd ((::Time) e.xclient.data.l[0], e.xclient.data.l[1],
|
||||
e.xclient.data.l[2], e.xclient.data.l[3],
|
||||
e.xclient.data.l[4]);
|
||||
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void sendXEmbedEvent (const ::Time& xTime, long opcode,
|
||||
long opcodeMinor = 0, long data1 = 0, long data2 = 0)
|
||||
{
|
||||
XClientMessageEvent msg;
|
||||
auto dpy = getDisplay();
|
||||
|
||||
::memset (&msg, 0, sizeof (XClientMessageEvent));
|
||||
msg.window = client;
|
||||
msg.type = ClientMessage;
|
||||
msg.message_type = atoms.XembedMsgType;
|
||||
msg.format = 32;
|
||||
msg.data.l[0] = (long) xTime;
|
||||
msg.data.l[1] = opcode;
|
||||
msg.data.l[2] = opcodeMinor;
|
||||
msg.data.l[3] = data1;
|
||||
msg.data.l[4] = data2;
|
||||
|
||||
XSendEvent (dpy, client, False, NoEventMask, (XEvent*) &msg);
|
||||
XSync (dpy, False);
|
||||
}
|
||||
|
||||
Rectangle<int> getX11BoundsFromJuce()
|
||||
{
|
||||
if (auto* peer = owner.getPeer())
|
||||
{
|
||||
auto r = peer->getComponent().getLocalArea (&owner, owner.getLocalBounds());
|
||||
auto scale = Desktop::getInstance().getDisplays().getDisplayContaining (peer->localToGlobal (r.getCentre())).scale;
|
||||
|
||||
return r * scale;
|
||||
}
|
||||
|
||||
return owner.getLocalBounds();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
friend bool juce::juce_handleXEmbedEvent (ComponentPeer*, void*);
|
||||
friend unsigned long juce::juce_getCurrentFocusWindow (ComponentPeer*);
|
||||
|
||||
static Array<Pimpl*>& getWidgets()
|
||||
{
|
||||
static Array<Pimpl*> i;
|
||||
return i;
|
||||
}
|
||||
|
||||
static bool dispatchX11Event (ComponentPeer* p, const XEvent* eventArg)
|
||||
{
|
||||
if (eventArg != nullptr)
|
||||
{
|
||||
auto& e = *eventArg;
|
||||
|
||||
if (auto w = e.xany.window)
|
||||
for (auto* widget : getWidgets())
|
||||
if (w == widget->host || w == widget->client)
|
||||
return widget->handleX11Event (e);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto* widget : getWidgets())
|
||||
if (widget->owner.getPeer() == p)
|
||||
widget->peerChanged (nullptr);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static Window getCurrentFocusWindow (ComponentPeer* p)
|
||||
{
|
||||
if (p != nullptr)
|
||||
{
|
||||
for (auto* widget : getWidgets())
|
||||
if (widget->owner.getPeer() == p && widget->owner.hasKeyboardFocus (false))
|
||||
return widget->client;
|
||||
}
|
||||
|
||||
return SharedKeyWindow::getCurrentFocusWindow (p);
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
XEmbedComponent::XEmbedComponent (bool wantsKeyboardFocus, bool allowForeignWidgetToResizeComponent)
|
||||
: pimpl (new Pimpl (*this, 0, wantsKeyboardFocus, false, allowForeignWidgetToResizeComponent))
|
||||
{
|
||||
setOpaque (true);
|
||||
}
|
||||
|
||||
XEmbedComponent::XEmbedComponent (unsigned long wID, bool wantsKeyboardFocus, bool allowForeignWidgetToResizeComponent)
|
||||
: pimpl (new Pimpl (*this, wID, wantsKeyboardFocus, true, allowForeignWidgetToResizeComponent))
|
||||
{
|
||||
setOpaque (true);
|
||||
}
|
||||
|
||||
XEmbedComponent::~XEmbedComponent() {}
|
||||
|
||||
void XEmbedComponent::paint (Graphics& g)
|
||||
{
|
||||
g.fillAll (Colours::lightgrey);
|
||||
}
|
||||
|
||||
void XEmbedComponent::focusGained (FocusChangeType changeType) { pimpl->focusGained (changeType); }
|
||||
void XEmbedComponent::focusLost (FocusChangeType changeType) { pimpl->focusLost (changeType); }
|
||||
void XEmbedComponent::broughtToFront() { pimpl->broughtToFront(); }
|
||||
unsigned long XEmbedComponent::getHostWindowID() { return pimpl->getHostWindowID(); }
|
||||
|
||||
//==============================================================================
|
||||
bool juce_handleXEmbedEvent (ComponentPeer* p, void* e)
|
||||
{
|
||||
return XEmbedComponent::Pimpl::dispatchX11Event (p, reinterpret_cast<const XEvent*> (e));
|
||||
}
|
||||
|
||||
unsigned long juce_getCurrentFocusWindow (ComponentPeer* peer)
|
||||
{
|
||||
return (unsigned long) XEmbedComponent::Pimpl::getCurrentFocusWindow (peer);
|
||||
}
|
||||
|
||||
} // namespace juce
|
269
modules/juce_gui_extra/native/juce_mac_AppleRemote.mm
Normal file
269
modules/juce_gui_extra/native/juce_mac_AppleRemote.mm
Normal file
@ -0,0 +1,269 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
AppleRemoteDevice::AppleRemoteDevice()
|
||||
: device (nullptr),
|
||||
queue (nullptr),
|
||||
remoteId (0)
|
||||
{
|
||||
}
|
||||
|
||||
AppleRemoteDevice::~AppleRemoteDevice()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
io_object_t getAppleRemoteDevice()
|
||||
{
|
||||
CFMutableDictionaryRef dict = IOServiceMatching ("AppleIRController");
|
||||
|
||||
io_iterator_t iter = 0;
|
||||
io_object_t iod = 0;
|
||||
|
||||
if (IOServiceGetMatchingServices (kIOMasterPortDefault, dict, &iter) == kIOReturnSuccess
|
||||
&& iter != 0)
|
||||
{
|
||||
iod = IOIteratorNext (iter);
|
||||
}
|
||||
|
||||
IOObjectRelease (iter);
|
||||
return iod;
|
||||
}
|
||||
|
||||
bool createAppleRemoteInterface (io_object_t iod, void** device)
|
||||
{
|
||||
jassert (*device == nullptr);
|
||||
io_name_t classname;
|
||||
|
||||
if (IOObjectGetClass (iod, classname) == kIOReturnSuccess)
|
||||
{
|
||||
IOCFPlugInInterface** cfPlugInInterface = nullptr;
|
||||
SInt32 score = 0;
|
||||
|
||||
if (IOCreatePlugInInterfaceForService (iod,
|
||||
kIOHIDDeviceUserClientTypeID,
|
||||
kIOCFPlugInInterfaceID,
|
||||
&cfPlugInInterface,
|
||||
&score) == kIOReturnSuccess)
|
||||
{
|
||||
HRESULT hr = (*cfPlugInInterface)->QueryInterface (cfPlugInInterface,
|
||||
CFUUIDGetUUIDBytes (kIOHIDDeviceInterfaceID),
|
||||
device);
|
||||
|
||||
ignoreUnused (hr);
|
||||
|
||||
(*cfPlugInInterface)->Release (cfPlugInInterface);
|
||||
}
|
||||
}
|
||||
|
||||
return *device != nullptr;
|
||||
}
|
||||
|
||||
void appleRemoteQueueCallback (void* const target, const IOReturn result, void*, void*)
|
||||
{
|
||||
if (result == kIOReturnSuccess)
|
||||
((AppleRemoteDevice*) target)->handleCallbackInternal();
|
||||
}
|
||||
}
|
||||
|
||||
bool AppleRemoteDevice::start (const bool inExclusiveMode)
|
||||
{
|
||||
if (queue != nullptr)
|
||||
return true;
|
||||
|
||||
stop();
|
||||
|
||||
bool result = false;
|
||||
io_object_t iod = getAppleRemoteDevice();
|
||||
|
||||
if (iod != 0)
|
||||
{
|
||||
if (createAppleRemoteInterface (iod, &device) && open (inExclusiveMode))
|
||||
result = true;
|
||||
else
|
||||
stop();
|
||||
|
||||
IOObjectRelease (iod);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void AppleRemoteDevice::stop()
|
||||
{
|
||||
if (queue != nullptr)
|
||||
{
|
||||
(*(IOHIDQueueInterface**) queue)->stop ((IOHIDQueueInterface**) queue);
|
||||
(*(IOHIDQueueInterface**) queue)->dispose ((IOHIDQueueInterface**) queue);
|
||||
(*(IOHIDQueueInterface**) queue)->Release ((IOHIDQueueInterface**) queue);
|
||||
queue = nullptr;
|
||||
}
|
||||
|
||||
if (device != nullptr)
|
||||
{
|
||||
(*(IOHIDDeviceInterface**) device)->close ((IOHIDDeviceInterface**) device);
|
||||
(*(IOHIDDeviceInterface**) device)->Release ((IOHIDDeviceInterface**) device);
|
||||
device = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool AppleRemoteDevice::isActive() const
|
||||
{
|
||||
return queue != nullptr;
|
||||
}
|
||||
|
||||
bool AppleRemoteDevice::open (const bool openInExclusiveMode)
|
||||
{
|
||||
Array <int> cookies;
|
||||
|
||||
CFArrayRef elements;
|
||||
IOHIDDeviceInterface122** const device122 = (IOHIDDeviceInterface122**) device;
|
||||
|
||||
if ((*device122)->copyMatchingElements (device122, 0, &elements) != kIOReturnSuccess)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < CFArrayGetCount (elements); ++i)
|
||||
{
|
||||
CFDictionaryRef element = (CFDictionaryRef) CFArrayGetValueAtIndex (elements, i);
|
||||
|
||||
// get the cookie
|
||||
CFTypeRef object = CFDictionaryGetValue (element, CFSTR (kIOHIDElementCookieKey));
|
||||
|
||||
if (object == 0 || CFGetTypeID (object) != CFNumberGetTypeID())
|
||||
continue;
|
||||
|
||||
long number;
|
||||
if (! CFNumberGetValue ((CFNumberRef) object, kCFNumberLongType, &number))
|
||||
continue;
|
||||
|
||||
cookies.add ((int) number);
|
||||
}
|
||||
|
||||
CFRelease (elements);
|
||||
|
||||
if ((*(IOHIDDeviceInterface**) device)
|
||||
->open ((IOHIDDeviceInterface**) device,
|
||||
openInExclusiveMode ? kIOHIDOptionsTypeSeizeDevice
|
||||
: kIOHIDOptionsTypeNone) == KERN_SUCCESS)
|
||||
{
|
||||
queue = (*(IOHIDDeviceInterface**) device)->allocQueue ((IOHIDDeviceInterface**) device);
|
||||
|
||||
if (queue != 0)
|
||||
{
|
||||
(*(IOHIDQueueInterface**) queue)->create ((IOHIDQueueInterface**) queue, 0, 12);
|
||||
|
||||
for (int i = 0; i < cookies.size(); ++i)
|
||||
{
|
||||
IOHIDElementCookie cookie = (IOHIDElementCookie) cookies.getUnchecked(i);
|
||||
(*(IOHIDQueueInterface**) queue)->addElement ((IOHIDQueueInterface**) queue, cookie, 0);
|
||||
}
|
||||
|
||||
CFRunLoopSourceRef eventSource;
|
||||
|
||||
if ((*(IOHIDQueueInterface**) queue)
|
||||
->createAsyncEventSource ((IOHIDQueueInterface**) queue, &eventSource) == KERN_SUCCESS)
|
||||
{
|
||||
if ((*(IOHIDQueueInterface**) queue)->setEventCallout ((IOHIDQueueInterface**) queue,
|
||||
appleRemoteQueueCallback, this, 0) == KERN_SUCCESS)
|
||||
{
|
||||
CFRunLoopAddSource (CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode);
|
||||
|
||||
(*(IOHIDQueueInterface**) queue)->start ((IOHIDQueueInterface**) queue);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void AppleRemoteDevice::handleCallbackInternal()
|
||||
{
|
||||
int totalValues = 0;
|
||||
AbsoluteTime nullTime = { 0, 0 };
|
||||
char cookies [12];
|
||||
int numCookies = 0;
|
||||
|
||||
while (numCookies < numElementsInArray (cookies))
|
||||
{
|
||||
IOHIDEventStruct e;
|
||||
|
||||
if ((*(IOHIDQueueInterface**) queue)->getNextEvent ((IOHIDQueueInterface**) queue, &e, nullTime, 0) != kIOReturnSuccess)
|
||||
break;
|
||||
|
||||
if ((int) e.elementCookie == 19)
|
||||
{
|
||||
remoteId = e.value;
|
||||
buttonPressed (switched, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
totalValues += e.value;
|
||||
cookies [numCookies++] = (char) (pointer_sized_int) e.elementCookie;
|
||||
}
|
||||
}
|
||||
|
||||
cookies [numCookies++] = 0;
|
||||
|
||||
static const char buttonPatterns[] =
|
||||
{
|
||||
0x1f, 0x14, 0x12, 0x1f, 0x14, 0x12, 0,
|
||||
0x1f, 0x15, 0x12, 0x1f, 0x15, 0x12, 0,
|
||||
0x1f, 0x1d, 0x1c, 0x12, 0,
|
||||
0x1f, 0x1e, 0x1c, 0x12, 0,
|
||||
0x1f, 0x16, 0x12, 0x1f, 0x16, 0x12, 0,
|
||||
0x1f, 0x17, 0x12, 0x1f, 0x17, 0x12, 0,
|
||||
0x1f, 0x12, 0x04, 0x02, 0,
|
||||
0x1f, 0x12, 0x03, 0x02, 0,
|
||||
0x1f, 0x12, 0x1f, 0x12, 0,
|
||||
0x23, 0x1f, 0x12, 0x23, 0x1f, 0x12, 0,
|
||||
19, 0
|
||||
};
|
||||
|
||||
int buttonNum = (int) menuButton;
|
||||
int i = 0;
|
||||
|
||||
while (i < numElementsInArray (buttonPatterns))
|
||||
{
|
||||
if (strcmp (cookies, buttonPatterns + i) == 0)
|
||||
{
|
||||
buttonPressed ((ButtonType) buttonNum, totalValues > 0);
|
||||
break;
|
||||
}
|
||||
|
||||
i += (int) strlen (buttonPatterns + i) + 1;
|
||||
++buttonNum;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,347 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Creates a floating carbon window that can be used to hold a carbon UI.
|
||||
|
||||
This is a handy class that's designed to be inlined where needed, e.g.
|
||||
in the audio plugin hosting code.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class CarbonViewWrapperComponent : public Component,
|
||||
public ComponentMovementWatcher,
|
||||
public Timer
|
||||
{
|
||||
public:
|
||||
CarbonViewWrapperComponent()
|
||||
: ComponentMovementWatcher (this),
|
||||
carbonWindow (nil),
|
||||
keepPluginWindowWhenHidden (false),
|
||||
wrapperWindow (nil),
|
||||
embeddedView (0),
|
||||
recursiveResize (false),
|
||||
repaintChildOnCreation (true)
|
||||
{
|
||||
}
|
||||
|
||||
~CarbonViewWrapperComponent()
|
||||
{
|
||||
jassert (embeddedView == 0); // must call deleteWindow() in the subclass's destructor!
|
||||
}
|
||||
|
||||
virtual HIViewRef attachView (WindowRef windowRef, HIViewRef rootView) = 0;
|
||||
virtual void removeView (HIViewRef embeddedView) = 0;
|
||||
virtual void handleMouseDown (int, int) {}
|
||||
virtual void handlePaint() {}
|
||||
|
||||
virtual bool getEmbeddedViewSize (int& w, int& h)
|
||||
{
|
||||
if (embeddedView == 0)
|
||||
return false;
|
||||
|
||||
HIRect bounds;
|
||||
HIViewGetBounds (embeddedView, &bounds);
|
||||
w = jmax (1, roundToInt (bounds.size.width));
|
||||
h = jmax (1, roundToInt (bounds.size.height));
|
||||
return true;
|
||||
}
|
||||
|
||||
void createWindow()
|
||||
{
|
||||
if (wrapperWindow == nil)
|
||||
{
|
||||
Rect r;
|
||||
r.left = (short) getScreenX();
|
||||
r.top = (short) getScreenY();
|
||||
r.right = (short) (r.left + getWidth());
|
||||
r.bottom = (short) (r.top + getHeight());
|
||||
|
||||
CreateNewWindow (kDocumentWindowClass,
|
||||
(WindowAttributes) (kWindowStandardHandlerAttribute | kWindowCompositingAttribute
|
||||
| kWindowNoShadowAttribute | kWindowNoTitleBarAttribute),
|
||||
&r, &wrapperWindow);
|
||||
|
||||
jassert (wrapperWindow != 0);
|
||||
if (wrapperWindow == 0)
|
||||
return;
|
||||
|
||||
carbonWindow = [[NSWindow alloc] initWithWindowRef: wrapperWindow];
|
||||
|
||||
[getOwnerWindow() addChildWindow: carbonWindow
|
||||
ordered: NSWindowAbove];
|
||||
|
||||
embeddedView = attachView (wrapperWindow, HIViewGetRoot (wrapperWindow));
|
||||
|
||||
// Check for the plugin creating its own floating window, and if there is one,
|
||||
// we need to reparent it to make it visible..
|
||||
if (carbonWindow.childWindows.count > 0)
|
||||
if (NSWindow* floatingChildWindow = [[carbonWindow childWindows] objectAtIndex: 0])
|
||||
[getOwnerWindow() addChildWindow: floatingChildWindow
|
||||
ordered: NSWindowAbove];
|
||||
|
||||
EventTypeSpec windowEventTypes[] =
|
||||
{
|
||||
{ kEventClassWindow, kEventWindowGetClickActivation },
|
||||
{ kEventClassWindow, kEventWindowHandleDeactivate },
|
||||
{ kEventClassWindow, kEventWindowBoundsChanging },
|
||||
{ kEventClassMouse, kEventMouseDown },
|
||||
{ kEventClassMouse, kEventMouseMoved },
|
||||
{ kEventClassMouse, kEventMouseDragged },
|
||||
{ kEventClassMouse, kEventMouseUp },
|
||||
{ kEventClassWindow, kEventWindowDrawContent },
|
||||
{ kEventClassWindow, kEventWindowShown },
|
||||
{ kEventClassWindow, kEventWindowHidden }
|
||||
};
|
||||
|
||||
EventHandlerUPP upp = NewEventHandlerUPP (carbonEventCallback);
|
||||
InstallWindowEventHandler (wrapperWindow, upp,
|
||||
sizeof (windowEventTypes) / sizeof (EventTypeSpec),
|
||||
windowEventTypes, this, &eventHandlerRef);
|
||||
|
||||
setOurSizeToEmbeddedViewSize();
|
||||
setEmbeddedWindowToOurSize();
|
||||
|
||||
creationTime = Time::getCurrentTime();
|
||||
}
|
||||
}
|
||||
|
||||
void deleteWindow()
|
||||
{
|
||||
removeView (embeddedView);
|
||||
embeddedView = 0;
|
||||
|
||||
if (wrapperWindow != nil)
|
||||
{
|
||||
NSWindow* ownerWindow = getOwnerWindow();
|
||||
|
||||
if ([[ownerWindow childWindows] count] > 0)
|
||||
{
|
||||
[ownerWindow removeChildWindow: carbonWindow];
|
||||
[carbonWindow close];
|
||||
}
|
||||
|
||||
RemoveEventHandler (eventHandlerRef);
|
||||
DisposeWindow (wrapperWindow);
|
||||
wrapperWindow = nil;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void setOurSizeToEmbeddedViewSize()
|
||||
{
|
||||
int w, h;
|
||||
if (getEmbeddedViewSize (w, h))
|
||||
{
|
||||
if (w != getWidth() || h != getHeight())
|
||||
{
|
||||
startTimer (50);
|
||||
setSize (w, h);
|
||||
|
||||
if (Component* p = getParentComponent())
|
||||
p->setSize (w, h);
|
||||
}
|
||||
else
|
||||
{
|
||||
startTimer (jlimit (50, 500, getTimerInterval() + 20));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stopTimer();
|
||||
}
|
||||
}
|
||||
|
||||
void setEmbeddedWindowToOurSize()
|
||||
{
|
||||
if (! recursiveResize)
|
||||
{
|
||||
recursiveResize = true;
|
||||
|
||||
if (embeddedView != 0)
|
||||
{
|
||||
HIRect r;
|
||||
r.origin.x = 0;
|
||||
r.origin.y = 0;
|
||||
r.size.width = (float) getWidth();
|
||||
r.size.height = (float) getHeight();
|
||||
HIViewSetFrame (embeddedView, &r);
|
||||
}
|
||||
|
||||
if (wrapperWindow != nil)
|
||||
{
|
||||
jassert (getTopLevelComponent()->getDesktopScaleFactor() == 1.0f);
|
||||
Rectangle<int> screenBounds (getScreenBounds() * Desktop::getInstance().getGlobalScaleFactor());
|
||||
|
||||
Rect wr;
|
||||
wr.left = (short) screenBounds.getX();
|
||||
wr.top = (short) screenBounds.getY();
|
||||
wr.right = (short) screenBounds.getRight();
|
||||
wr.bottom = (short) screenBounds.getBottom();
|
||||
|
||||
SetWindowBounds (wrapperWindow, kWindowContentRgn, &wr);
|
||||
|
||||
// This group stuff is mainly a workaround for Mackie plugins like FinalMix..
|
||||
WindowGroupRef group = GetWindowGroup (wrapperWindow);
|
||||
WindowRef attachedWindow;
|
||||
|
||||
if (GetIndexedWindow (group, 2, kWindowGroupContentsReturnWindows, &attachedWindow) == noErr)
|
||||
{
|
||||
SelectWindow (attachedWindow);
|
||||
ActivateWindow (attachedWindow, TRUE);
|
||||
HideWindow (wrapperWindow);
|
||||
}
|
||||
|
||||
ShowWindow (wrapperWindow);
|
||||
}
|
||||
|
||||
recursiveResize = false;
|
||||
}
|
||||
}
|
||||
|
||||
void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
|
||||
{
|
||||
setEmbeddedWindowToOurSize();
|
||||
}
|
||||
|
||||
// (overridden to intercept movements of the top-level window)
|
||||
void componentMovedOrResized (Component& component, bool wasMoved, bool wasResized) override
|
||||
{
|
||||
ComponentMovementWatcher::componentMovedOrResized (component, wasMoved, wasResized);
|
||||
|
||||
if (&component == getTopLevelComponent())
|
||||
setEmbeddedWindowToOurSize();
|
||||
}
|
||||
|
||||
void componentPeerChanged() override
|
||||
{
|
||||
deleteWindow();
|
||||
createWindow();
|
||||
}
|
||||
|
||||
void componentVisibilityChanged() override
|
||||
{
|
||||
if (isShowing())
|
||||
createWindow();
|
||||
else if (! keepPluginWindowWhenHidden)
|
||||
deleteWindow();
|
||||
|
||||
setEmbeddedWindowToOurSize();
|
||||
}
|
||||
|
||||
static void recursiveHIViewRepaint (HIViewRef view)
|
||||
{
|
||||
HIViewSetNeedsDisplay (view, true);
|
||||
HIViewRef child = HIViewGetFirstSubview (view);
|
||||
|
||||
while (child != 0)
|
||||
{
|
||||
recursiveHIViewRepaint (child);
|
||||
child = HIViewGetNextView (child);
|
||||
}
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
if (isShowing())
|
||||
{
|
||||
setOurSizeToEmbeddedViewSize();
|
||||
|
||||
// To avoid strange overpainting problems when the UI is first opened, we'll
|
||||
// repaint it a few times during the first second that it's on-screen..
|
||||
if (repaintChildOnCreation && (Time::getCurrentTime() - creationTime).inMilliseconds() < 1000)
|
||||
recursiveHIViewRepaint (HIViewGetRoot (wrapperWindow));
|
||||
}
|
||||
}
|
||||
|
||||
void setRepaintsChildHIViewWhenCreated (bool b) noexcept
|
||||
{
|
||||
repaintChildOnCreation = b;
|
||||
}
|
||||
|
||||
OSStatus carbonEventHandler (EventHandlerCallRef /*nextHandlerRef*/, EventRef event)
|
||||
{
|
||||
switch (GetEventKind (event))
|
||||
{
|
||||
case kEventWindowHandleDeactivate:
|
||||
ActivateWindow (wrapperWindow, TRUE);
|
||||
return noErr;
|
||||
|
||||
case kEventWindowGetClickActivation:
|
||||
{
|
||||
getTopLevelComponent()->toFront (false);
|
||||
[carbonWindow makeKeyAndOrderFront: nil];
|
||||
|
||||
ClickActivationResult howToHandleClick = kActivateAndHandleClick;
|
||||
|
||||
SetEventParameter (event, kEventParamClickActivation, typeClickActivationResult,
|
||||
sizeof (ClickActivationResult), &howToHandleClick);
|
||||
|
||||
if (embeddedView != 0)
|
||||
HIViewSetNeedsDisplay (embeddedView, true);
|
||||
|
||||
return noErr;
|
||||
}
|
||||
}
|
||||
|
||||
return eventNotHandledErr;
|
||||
}
|
||||
|
||||
static pascal OSStatus carbonEventCallback (EventHandlerCallRef nextHandlerRef, EventRef event, void* userData)
|
||||
{
|
||||
return ((CarbonViewWrapperComponent*) userData)->carbonEventHandler (nextHandlerRef, event);
|
||||
}
|
||||
|
||||
NSWindow* carbonWindow;
|
||||
bool keepPluginWindowWhenHidden;
|
||||
|
||||
protected:
|
||||
WindowRef wrapperWindow;
|
||||
HIViewRef embeddedView;
|
||||
bool recursiveResize, repaintChildOnCreation;
|
||||
Time creationTime;
|
||||
|
||||
EventHandlerRef eventHandlerRef;
|
||||
|
||||
NSWindow* getOwnerWindow() const { return [((NSView*) getWindowHandle()) window]; }
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// Non-public utility function that hosts can use if they need to get hold of the
|
||||
// internals of a carbon wrapper window..
|
||||
void* getCarbonWindow (Component* possibleCarbonComponent)
|
||||
{
|
||||
if (CarbonViewWrapperComponent* cv = dynamic_cast<CarbonViewWrapperComponent*> (possibleCarbonComponent))
|
||||
return cv->carbonWindow;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace juce
|
248
modules/juce_gui_extra/native/juce_mac_NSViewComponent.mm
Normal file
248
modules/juce_gui_extra/native/juce_mac_NSViewComponent.mm
Normal file
@ -0,0 +1,248 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
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 NSViewResizeWatcher
|
||||
{
|
||||
NSViewResizeWatcher() : callback (nil) {}
|
||||
|
||||
virtual ~NSViewResizeWatcher()
|
||||
{
|
||||
// must call detachViewWatcher() first
|
||||
jassert (callback == nil);
|
||||
}
|
||||
|
||||
void attachViewWatcher (NSView* view)
|
||||
{
|
||||
static ViewFrameChangeCallbackClass cls;
|
||||
callback = [cls.createInstance() init];
|
||||
ViewFrameChangeCallbackClass::setTarget (callback, this);
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver: callback
|
||||
selector: @selector (frameChanged:)
|
||||
name: NSViewFrameDidChangeNotification
|
||||
object: view];
|
||||
}
|
||||
|
||||
void detachViewWatcher()
|
||||
{
|
||||
if (callback != nil)
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver: callback];
|
||||
[callback release];
|
||||
callback = nil;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void viewResized() = 0;
|
||||
|
||||
private:
|
||||
id callback;
|
||||
|
||||
//==============================================================================
|
||||
struct ViewFrameChangeCallbackClass : public ObjCClass<NSObject>
|
||||
{
|
||||
ViewFrameChangeCallbackClass() : ObjCClass<NSObject> ("JUCE_NSViewCallback_")
|
||||
{
|
||||
addIvar<NSViewResizeWatcher*> ("target");
|
||||
addMethod (@selector (frameChanged:), frameChanged, "v@:@");
|
||||
registerClass();
|
||||
}
|
||||
|
||||
static void setTarget (id self, NSViewResizeWatcher* c)
|
||||
{
|
||||
object_setInstanceVariable (self, "target", c);
|
||||
}
|
||||
|
||||
private:
|
||||
static void frameChanged (id self, SEL, NSNotification*)
|
||||
{
|
||||
if (auto* target = getIvar<NSViewResizeWatcher*> (self, "target"))
|
||||
target->viewResized();
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (ViewFrameChangeCallbackClass)
|
||||
};
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NSViewResizeWatcher)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class NSViewAttachment : public ReferenceCountedObject,
|
||||
public ComponentMovementWatcher,
|
||||
private NSViewResizeWatcher
|
||||
{
|
||||
public:
|
||||
NSViewAttachment (NSView* v, Component& comp)
|
||||
: ComponentMovementWatcher (&comp),
|
||||
view (v), owner (comp),
|
||||
currentPeer (nullptr)
|
||||
{
|
||||
[view retain];
|
||||
[view setPostsFrameChangedNotifications: YES];
|
||||
updateAlpha();
|
||||
|
||||
if (owner.isShowing())
|
||||
componentPeerChanged();
|
||||
|
||||
attachViewWatcher (view);
|
||||
}
|
||||
|
||||
~NSViewAttachment()
|
||||
{
|
||||
detachViewWatcher();
|
||||
removeFromParent();
|
||||
[view release];
|
||||
}
|
||||
|
||||
void componentMovedOrResized (Component& comp, bool wasMoved, bool wasResized) override
|
||||
{
|
||||
ComponentMovementWatcher::componentMovedOrResized (comp, wasMoved, wasResized);
|
||||
|
||||
// The ComponentMovementWatcher version of this method avoids calling
|
||||
// us when the top-level comp is resized, but for an NSView we need to know this
|
||||
// because with inverted coordinates, we need to update the position even if the
|
||||
// top-left pos hasn't changed
|
||||
if (comp.isOnDesktop() && wasResized)
|
||||
componentMovedOrResized (wasMoved, wasResized);
|
||||
}
|
||||
|
||||
void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
|
||||
{
|
||||
if (auto* peer = owner.getTopLevelComponent()->getPeer())
|
||||
{
|
||||
auto r = makeNSRect (peer->getAreaCoveredBy (owner));
|
||||
r.origin.y = [[view superview] frame].size.height - (r.origin.y + r.size.height);
|
||||
[view setFrame: r];
|
||||
}
|
||||
}
|
||||
|
||||
void componentPeerChanged() override
|
||||
{
|
||||
auto* peer = owner.getPeer();
|
||||
|
||||
if (currentPeer != peer)
|
||||
{
|
||||
currentPeer = peer;
|
||||
|
||||
if (peer != nullptr)
|
||||
{
|
||||
auto peerView = (NSView*) peer->getNativeHandle();
|
||||
[peerView addSubview: view];
|
||||
componentMovedOrResized (false, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
removeFromParent();
|
||||
}
|
||||
}
|
||||
|
||||
[view setHidden: ! owner.isShowing()];
|
||||
}
|
||||
|
||||
void componentVisibilityChanged() override
|
||||
{
|
||||
componentPeerChanged();
|
||||
}
|
||||
|
||||
void viewResized() override
|
||||
{
|
||||
owner.childBoundsChanged (nullptr);
|
||||
}
|
||||
|
||||
void updateAlpha()
|
||||
{
|
||||
[view setAlphaValue: (CGFloat) owner.getAlpha()];
|
||||
}
|
||||
|
||||
NSView* const view;
|
||||
|
||||
using Ptr = ReferenceCountedObjectPtr<NSViewAttachment>;
|
||||
|
||||
private:
|
||||
Component& owner;
|
||||
ComponentPeer* currentPeer;
|
||||
|
||||
void removeFromParent()
|
||||
{
|
||||
if ([view superview] != nil)
|
||||
[view removeFromSuperview]; // Must be careful not to call this unless it's required - e.g. some Apple AU views
|
||||
// override the call and use it as a sign that they're being deleted, which breaks everything..
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NSViewAttachment)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
NSViewComponent::NSViewComponent() {}
|
||||
NSViewComponent::~NSViewComponent() {}
|
||||
|
||||
void NSViewComponent::setView (void* view)
|
||||
{
|
||||
if (view != getView())
|
||||
{
|
||||
auto old = attachment;
|
||||
|
||||
attachment = nullptr;
|
||||
|
||||
if (view != nullptr)
|
||||
attachment = attachViewToComponent (*this, view);
|
||||
|
||||
old = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void* NSViewComponent::getView() const
|
||||
{
|
||||
return attachment != nullptr ? static_cast<NSViewAttachment*> (attachment.get())->view
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
void NSViewComponent::resizeToFitView()
|
||||
{
|
||||
if (attachment != nullptr)
|
||||
{
|
||||
auto r = [static_cast<NSViewAttachment*> (attachment.get())->view frame];
|
||||
setBounds (Rectangle<int> ((int) r.size.width, (int) r.size.height));
|
||||
}
|
||||
}
|
||||
|
||||
void NSViewComponent::paint (Graphics&) {}
|
||||
|
||||
void NSViewComponent::alphaChanged()
|
||||
{
|
||||
if (attachment != nullptr)
|
||||
(static_cast<NSViewAttachment*> (attachment.get()))->updateAlpha();
|
||||
}
|
||||
|
||||
ReferenceCountedObject* NSViewComponent::attachViewToComponent (Component& comp, void* view)
|
||||
{
|
||||
return new NSViewAttachment ((NSView*) view, comp);
|
||||
}
|
||||
|
||||
} // namespace juce
|
553
modules/juce_gui_extra/native/juce_mac_PushNotifications.cpp
Normal file
553
modules/juce_gui_extra/native/juce_mac_PushNotifications.cpp
Normal file
@ -0,0 +1,553 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
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 PushNotificationsDelegateDetailsOsx
|
||||
{
|
||||
using Action = PushNotifications::Notification::Action;
|
||||
|
||||
//==============================================================================
|
||||
NSUserNotification* juceNotificationToNSUserNotification (const PushNotifications::Notification& n,
|
||||
bool isEarlierThanMavericks,
|
||||
bool isEarlierThanYosemite)
|
||||
{
|
||||
auto* notification = [[NSUserNotification alloc] init];
|
||||
|
||||
notification.title = juceStringToNS (n.title);
|
||||
notification.subtitle = juceStringToNS (n.subtitle);
|
||||
notification.informativeText = juceStringToNS (n.body);
|
||||
notification.userInfo = varObjectToNSDictionary (n.properties);
|
||||
|
||||
auto triggerTime = Time::getCurrentTime() + RelativeTime (n.triggerIntervalSec);
|
||||
notification.deliveryDate = [NSDate dateWithTimeIntervalSince1970: triggerTime.toMilliseconds() / 1000.];
|
||||
|
||||
if (n.repeat && n.triggerIntervalSec >= 60)
|
||||
{
|
||||
auto* dateComponents = [[NSDateComponents alloc] init];
|
||||
auto intervalSec = NSInteger (n.triggerIntervalSec);
|
||||
dateComponents.second = intervalSec;
|
||||
dateComponents.nanosecond = NSInteger ((n.triggerIntervalSec - intervalSec) * 1000000000);
|
||||
|
||||
notification.deliveryRepeatInterval = dateComponents;
|
||||
|
||||
[dateComponents autorelease];
|
||||
}
|
||||
|
||||
auto soundToPlayString = n.soundToPlay.toString (true);
|
||||
|
||||
if (soundToPlayString == "default_os_sound")
|
||||
{
|
||||
notification.soundName = NSUserNotificationDefaultSoundName;
|
||||
}
|
||||
else if (soundToPlayString.isNotEmpty())
|
||||
{
|
||||
auto* soundName = juceStringToNS (soundToPlayString.fromLastOccurrenceOf ("/", false, false)
|
||||
.upToLastOccurrenceOf (".", false, false));
|
||||
|
||||
notification.soundName = soundName;
|
||||
}
|
||||
|
||||
notification.hasActionButton = n.actions.size() > 0;
|
||||
|
||||
if (n.actions.size() > 0)
|
||||
notification.actionButtonTitle = juceStringToNS (n.actions.getReference (0).title);
|
||||
|
||||
if (! isEarlierThanMavericks)
|
||||
{
|
||||
notification.identifier = juceStringToNS (n.identifier);
|
||||
|
||||
if (n.actions.size() > 0)
|
||||
{
|
||||
notification.hasReplyButton = n.actions.getReference (0).style == Action::text;
|
||||
notification.responsePlaceholder = juceStringToNS (n.actions.getReference (0).textInputPlaceholder);
|
||||
}
|
||||
|
||||
auto* imageDirectory = n.icon.contains ("/")
|
||||
? juceStringToNS (n.icon.upToLastOccurrenceOf ("/", false, true))
|
||||
: [NSString string];
|
||||
|
||||
auto* imageName = juceStringToNS (n.icon.fromLastOccurrenceOf ("/", false, false)
|
||||
.upToLastOccurrenceOf (".", false, false));
|
||||
auto* imageExtension = juceStringToNS (n.icon.fromLastOccurrenceOf (".", false, false));
|
||||
|
||||
NSString* imagePath = nil;
|
||||
|
||||
if ([imageDirectory length] == NSUInteger (0))
|
||||
{
|
||||
imagePath = [[NSBundle mainBundle] pathForResource: imageName
|
||||
ofType: imageExtension];
|
||||
}
|
||||
else
|
||||
{
|
||||
imagePath = [[NSBundle mainBundle] pathForResource: imageName
|
||||
ofType: imageExtension
|
||||
inDirectory: imageDirectory];
|
||||
}
|
||||
|
||||
notification.contentImage = [[NSImage alloc] initWithContentsOfFile: imagePath];
|
||||
|
||||
if (! isEarlierThanYosemite)
|
||||
{
|
||||
if (n.actions.size() > 1)
|
||||
{
|
||||
auto* additionalActions = [NSMutableArray arrayWithCapacity: (NSUInteger) n.actions.size() - 1];
|
||||
|
||||
for (int a = 1; a < n.actions.size(); ++a)
|
||||
[additionalActions addObject: [NSUserNotificationAction actionWithIdentifier: juceStringToNS (n.actions[a].identifier)
|
||||
title: juceStringToNS (n.actions[a].title)]];
|
||||
|
||||
notification.additionalActions = additionalActions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[notification autorelease];
|
||||
|
||||
return notification;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
PushNotifications::Notification nsUserNotificationToJuceNotification (NSUserNotification* n,
|
||||
bool isEarlierThanMavericks,
|
||||
bool isEarlierThanYosemite)
|
||||
{
|
||||
PushNotifications::Notification notif;
|
||||
|
||||
notif.title = nsStringToJuce (n.title);
|
||||
notif.subtitle = nsStringToJuce (n.subtitle);
|
||||
notif.body = nsStringToJuce (n.informativeText);
|
||||
|
||||
notif.repeat = n.deliveryRepeatInterval != nil;
|
||||
|
||||
if (n.deliveryRepeatInterval != nil)
|
||||
{
|
||||
notif.triggerIntervalSec = n.deliveryRepeatInterval.second + (n.deliveryRepeatInterval.nanosecond / 1000000000.);
|
||||
}
|
||||
else
|
||||
{
|
||||
NSDate* dateNow = [NSDate date];
|
||||
notif.triggerIntervalSec = [dateNow timeIntervalSinceDate: n.deliveryDate];
|
||||
}
|
||||
|
||||
notif.soundToPlay = URL (nsStringToJuce (n.soundName));
|
||||
notif.properties = nsDictionaryToVar (n.userInfo);
|
||||
|
||||
if (! isEarlierThanMavericks)
|
||||
{
|
||||
notif.identifier = nsStringToJuce (n.identifier);
|
||||
|
||||
if (n.contentImage != nil)
|
||||
notif.icon = nsStringToJuce ([n.contentImage name]);
|
||||
}
|
||||
|
||||
Array<Action> actions;
|
||||
|
||||
if (n.actionButtonTitle != nil)
|
||||
{
|
||||
Action action;
|
||||
action.title = nsStringToJuce (n.actionButtonTitle);
|
||||
|
||||
if (! isEarlierThanMavericks)
|
||||
{
|
||||
if (n.hasReplyButton)
|
||||
action.style = Action::text;
|
||||
|
||||
if (n.responsePlaceholder != nil)
|
||||
action.textInputPlaceholder = nsStringToJuce (n.responsePlaceholder);
|
||||
}
|
||||
|
||||
actions.add (action);
|
||||
}
|
||||
|
||||
if (! isEarlierThanYosemite)
|
||||
{
|
||||
if (n.additionalActions != nil)
|
||||
{
|
||||
for (NSUserNotificationAction* a in n.additionalActions)
|
||||
{
|
||||
Action action;
|
||||
action.identifier = nsStringToJuce (a.identifier);
|
||||
action.title = nsStringToJuce (a.title);
|
||||
|
||||
actions.add (action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return notif;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
var getNotificationPropertiesFromDictionaryVar (const var& dictionaryVar)
|
||||
{
|
||||
auto* dictionaryVarObject = dictionaryVar.getDynamicObject();
|
||||
|
||||
if (dictionaryVarObject == nullptr)
|
||||
return {};
|
||||
|
||||
const auto& properties = dictionaryVarObject->getProperties();
|
||||
|
||||
DynamicObject::Ptr propsVarObject = new DynamicObject();
|
||||
|
||||
for (int i = 0; i < properties.size(); ++i)
|
||||
{
|
||||
auto propertyName = properties.getName (i).toString();
|
||||
|
||||
if (propertyName == "aps")
|
||||
continue;
|
||||
|
||||
propsVarObject->setProperty (propertyName, properties.getValueAt (i));
|
||||
}
|
||||
|
||||
return var (propsVarObject.get());
|
||||
}
|
||||
|
||||
PushNotifications::Notification nsDictionaryToJuceNotification (NSDictionary* dictionary)
|
||||
{
|
||||
const var dictionaryVar = nsDictionaryToVar (dictionary);
|
||||
|
||||
const var apsVar = dictionaryVar.getProperty ("aps", {});
|
||||
|
||||
if (! apsVar.isObject())
|
||||
return {};
|
||||
|
||||
var alertVar = apsVar.getProperty ("alert", {});
|
||||
|
||||
const var titleVar = alertVar.getProperty ("title", {});
|
||||
const var bodyVar = alertVar.isObject() ? alertVar.getProperty ("body", {}) : alertVar;
|
||||
|
||||
const var categoryVar = apsVar.getProperty ("category", {});
|
||||
const var soundVar = apsVar.getProperty ("sound", {});
|
||||
const var badgeVar = apsVar.getProperty ("badge", {});
|
||||
const var threadIdVar = apsVar.getProperty ("thread-id", {});
|
||||
|
||||
PushNotifications::Notification notification;
|
||||
|
||||
notification.title = titleVar .toString();
|
||||
notification.body = bodyVar .toString();
|
||||
notification.groupId = threadIdVar.toString();
|
||||
notification.category = categoryVar.toString();
|
||||
notification.soundToPlay = URL (soundVar.toString());
|
||||
notification.badgeNumber = (int) badgeVar;
|
||||
notification.properties = getNotificationPropertiesFromDictionaryVar (dictionaryVar);
|
||||
|
||||
return notification;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct PushNotificationsDelegate
|
||||
{
|
||||
PushNotificationsDelegate() : delegate ([getClass().createInstance() init])
|
||||
{
|
||||
Class::setThis (delegate.get(), this);
|
||||
|
||||
id<NSApplicationDelegate> appDelegate = [[NSApplication sharedApplication] delegate];
|
||||
|
||||
SEL selector = NSSelectorFromString (@"setPushNotificationsDelegate:");
|
||||
|
||||
if ([appDelegate respondsToSelector: selector])
|
||||
[appDelegate performSelector: selector withObject: delegate.get()];
|
||||
|
||||
[NSUserNotificationCenter defaultUserNotificationCenter].delegate = delegate.get();
|
||||
}
|
||||
|
||||
virtual ~PushNotificationsDelegate()
|
||||
{
|
||||
[NSUserNotificationCenter defaultUserNotificationCenter].delegate = nil;
|
||||
}
|
||||
|
||||
virtual void registeredForRemoteNotifications (NSData* deviceToken) = 0;
|
||||
|
||||
virtual void failedToRegisterForRemoteNotifications (NSError* error) = 0;
|
||||
|
||||
virtual void didReceiveRemoteNotification (NSDictionary* userInfo) = 0;
|
||||
|
||||
virtual void didDeliverNotification (NSUserNotification* notification) = 0;
|
||||
|
||||
virtual void didActivateNotification (NSUserNotification* notification) = 0;
|
||||
|
||||
virtual bool shouldPresentNotification (NSUserNotification* notification) = 0;
|
||||
|
||||
protected:
|
||||
std::unique_ptr<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>, NSObjectDeleter> delegate;
|
||||
|
||||
private:
|
||||
struct Class : public ObjCClass<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>>
|
||||
{
|
||||
Class() : ObjCClass<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>> ("JucePushNotificationsDelegate_")
|
||||
{
|
||||
addIvar<PushNotificationsDelegate*> ("self");
|
||||
|
||||
addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), registeredForRemoteNotifications, "v@:@@");
|
||||
addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), failedToRegisterForRemoteNotifications, "v@:@@");
|
||||
addMethod (@selector (application:didReceiveRemoteNotification:), didReceiveRemoteNotification, "v@:@@");
|
||||
addMethod (@selector (userNotificationCenter:didDeliverNotification:), didDeliverNotification, "v@:@@");
|
||||
addMethod (@selector (userNotificationCenter:didActivateNotification:), didActivateNotification, "v@:@@");
|
||||
addMethod (@selector (userNotificationCenter:shouldPresentNotification:), shouldPresentNotification, "B@:@@");
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static PushNotificationsDelegate& getThis (id self) { return *getIvar<PushNotificationsDelegate*> (self, "self"); }
|
||||
static void setThis (id self, PushNotificationsDelegate* d) { object_setInstanceVariable (self, "self", d); }
|
||||
|
||||
//==============================================================================
|
||||
static void registeredForRemoteNotifications (id self, SEL, NSApplication*,
|
||||
NSData* deviceToken) { getThis (self).registeredForRemoteNotifications (deviceToken); }
|
||||
|
||||
static void failedToRegisterForRemoteNotifications (id self, SEL, NSApplication*,
|
||||
NSError* error) { getThis (self).failedToRegisterForRemoteNotifications (error); }
|
||||
|
||||
static void didReceiveRemoteNotification (id self, SEL, NSApplication*,
|
||||
NSDictionary* userInfo) { getThis (self).didReceiveRemoteNotification (userInfo); }
|
||||
|
||||
static void didDeliverNotification (id self, SEL, NSUserNotificationCenter*,
|
||||
NSUserNotification* notification) { getThis (self).didDeliverNotification (notification); }
|
||||
|
||||
static void didActivateNotification (id self, SEL, NSUserNotificationCenter*,
|
||||
NSUserNotification* notification) { getThis (self).didActivateNotification (notification); }
|
||||
|
||||
static bool shouldPresentNotification (id self, SEL, NSUserNotificationCenter*,
|
||||
NSUserNotification* notification) { return getThis (self).shouldPresentNotification (notification); }
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
static Class& getClass()
|
||||
{
|
||||
static Class c;
|
||||
return c;
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
bool PushNotifications::Notification::isValid() const noexcept { return true; }
|
||||
|
||||
//==============================================================================
|
||||
struct PushNotifications::Pimpl : private PushNotificationsDelegate
|
||||
{
|
||||
Pimpl (PushNotifications& p)
|
||||
: owner (p)
|
||||
{
|
||||
}
|
||||
|
||||
void requestPermissionsWithSettings (const PushNotifications::Settings& settingsToUse)
|
||||
{
|
||||
if (isEarlierThanLion)
|
||||
return;
|
||||
|
||||
settings = settingsToUse;
|
||||
|
||||
NSRemoteNotificationType types = NSUInteger ((bool) settings.allowBadge);
|
||||
|
||||
if (isAtLeastMountainLion)
|
||||
types |= ((bool) settings.allowSound << 1 | (bool) settings.allowAlert << 2);
|
||||
|
||||
[[NSApplication sharedApplication] registerForRemoteNotificationTypes: types];
|
||||
}
|
||||
|
||||
void requestSettingsUsed()
|
||||
{
|
||||
if (isEarlierThanLion)
|
||||
{
|
||||
// no settings available
|
||||
owner.listeners.call ([] (Listener& l) { l.notificationSettingsReceived ({}); });
|
||||
return;
|
||||
}
|
||||
|
||||
settings.allowBadge = [NSApplication sharedApplication].enabledRemoteNotificationTypes & NSRemoteNotificationTypeBadge;
|
||||
|
||||
if (isAtLeastMountainLion)
|
||||
{
|
||||
settings.allowSound = [NSApplication sharedApplication].enabledRemoteNotificationTypes & NSRemoteNotificationTypeSound;
|
||||
settings.allowAlert = [NSApplication sharedApplication].enabledRemoteNotificationTypes & NSRemoteNotificationTypeAlert;
|
||||
}
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.notificationSettingsReceived (settings); });
|
||||
}
|
||||
|
||||
bool areNotificationsEnabled() const { return true; }
|
||||
|
||||
void sendLocalNotification (const Notification& n)
|
||||
{
|
||||
auto* notification = PushNotificationsDelegateDetailsOsx::juceNotificationToNSUserNotification (n, isEarlierThanMavericks, isEarlierThanYosemite);
|
||||
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] scheduleNotification: notification];
|
||||
}
|
||||
|
||||
void getDeliveredNotifications() const
|
||||
{
|
||||
Array<PushNotifications::Notification> notifs;
|
||||
|
||||
for (NSUserNotification* n in [NSUserNotificationCenter defaultUserNotificationCenter].deliveredNotifications)
|
||||
notifs.add (PushNotificationsDelegateDetailsOsx::nsUserNotificationToJuceNotification (n, isEarlierThanMavericks, isEarlierThanYosemite));
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.deliveredNotificationsListReceived (notifs); });
|
||||
}
|
||||
|
||||
void removeAllDeliveredNotifications()
|
||||
{
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] removeAllDeliveredNotifications];
|
||||
}
|
||||
|
||||
void removeDeliveredNotification (const String& identifier)
|
||||
{
|
||||
PushNotifications::Notification n;
|
||||
n.identifier = identifier;
|
||||
|
||||
auto nsNotification = PushNotificationsDelegateDetailsOsx::juceNotificationToNSUserNotification (n, isEarlierThanMavericks, isEarlierThanYosemite);
|
||||
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] removeDeliveredNotification: nsNotification];
|
||||
}
|
||||
|
||||
void setupChannels (const Array<ChannelGroup>& groups, const Array<Channel>& channels)
|
||||
{
|
||||
ignoreUnused (groups, channels);
|
||||
}
|
||||
|
||||
void getPendingLocalNotifications() const
|
||||
{
|
||||
Array<PushNotifications::Notification> notifs;
|
||||
|
||||
for (NSUserNotification* n in [NSUserNotificationCenter defaultUserNotificationCenter].scheduledNotifications)
|
||||
notifs.add (PushNotificationsDelegateDetailsOsx::nsUserNotificationToJuceNotification (n, isEarlierThanMavericks, isEarlierThanYosemite));
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.pendingLocalNotificationsListReceived (notifs); });
|
||||
}
|
||||
|
||||
void removePendingLocalNotification (const String& identifier)
|
||||
{
|
||||
PushNotifications::Notification n;
|
||||
n.identifier = identifier;
|
||||
|
||||
auto nsNotification = PushNotificationsDelegateDetailsOsx::juceNotificationToNSUserNotification (n, isEarlierThanMavericks, isEarlierThanYosemite);
|
||||
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] removeScheduledNotification: nsNotification];
|
||||
}
|
||||
|
||||
void removeAllPendingLocalNotifications()
|
||||
{
|
||||
for (NSUserNotification* n in [NSUserNotificationCenter defaultUserNotificationCenter].scheduledNotifications)
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] removeScheduledNotification: n];
|
||||
}
|
||||
|
||||
String getDeviceToken()
|
||||
{
|
||||
// You need to call requestPermissionsWithSettings() first.
|
||||
jassert (initialised);
|
||||
|
||||
return deviceToken;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
//PushNotificationsDelegate
|
||||
void registeredForRemoteNotifications (NSData* deviceTokenToUse) override
|
||||
{
|
||||
auto* deviceTokenString = [[[[deviceTokenToUse description]
|
||||
stringByReplacingOccurrencesOfString: nsStringLiteral ("<") withString: nsStringLiteral ("")]
|
||||
stringByReplacingOccurrencesOfString: nsStringLiteral (">") withString: nsStringLiteral ("")]
|
||||
stringByReplacingOccurrencesOfString: nsStringLiteral (" ") withString: nsStringLiteral ("")];
|
||||
|
||||
deviceToken = nsStringToJuce (deviceTokenString);
|
||||
|
||||
initialised = true;
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.deviceTokenRefreshed (deviceToken); });
|
||||
}
|
||||
|
||||
void failedToRegisterForRemoteNotifications (NSError* error) override
|
||||
{
|
||||
ignoreUnused (error);
|
||||
deviceToken.clear();
|
||||
}
|
||||
|
||||
void didReceiveRemoteNotification (NSDictionary* userInfo) override
|
||||
{
|
||||
auto n = PushNotificationsDelegateDetailsOsx::nsDictionaryToJuceNotification (userInfo);
|
||||
owner.listeners.call ([&] (Listener& l) { l.handleNotification (true, n); });
|
||||
}
|
||||
|
||||
void didDeliverNotification (NSUserNotification* notification) override
|
||||
{
|
||||
ignoreUnused (notification);
|
||||
}
|
||||
|
||||
void didActivateNotification (NSUserNotification* notification) override
|
||||
{
|
||||
auto n = PushNotificationsDelegateDetailsOsx::nsUserNotificationToJuceNotification (notification, isEarlierThanMavericks, isEarlierThanYosemite);
|
||||
|
||||
if (notification.activationType == NSUserNotificationActivationTypeContentsClicked)
|
||||
{
|
||||
owner.listeners.call ([&] (Listener& l) { l.handleNotification (notification.remote, n); });
|
||||
}
|
||||
else
|
||||
{
|
||||
auto actionIdentifier = (! isEarlierThanYosemite && notification.additionalActivationAction != nil)
|
||||
? nsStringToJuce (notification.additionalActivationAction.identifier)
|
||||
: nsStringToJuce (notification.actionButtonTitle);
|
||||
|
||||
auto reply = notification.activationType == NSUserNotificationActivationTypeReplied
|
||||
? nsStringToJuce ([notification.response string])
|
||||
: String();
|
||||
|
||||
owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (notification.remote, n, actionIdentifier, reply); });
|
||||
}
|
||||
}
|
||||
|
||||
bool shouldPresentNotification (NSUserNotification* notification) override { return true; }
|
||||
|
||||
void subscribeToTopic (const String& topic) { ignoreUnused (topic); }
|
||||
void unsubscribeFromTopic (const String& topic) { ignoreUnused (topic); }
|
||||
|
||||
void sendUpstreamMessage (const String& serverSenderId,
|
||||
const String& collapseKey,
|
||||
const String& messageId,
|
||||
const String& messageType,
|
||||
int timeToLive,
|
||||
const StringPairArray& additionalData)
|
||||
{
|
||||
ignoreUnused (serverSenderId, collapseKey, messageId, messageType);
|
||||
ignoreUnused (timeToLive, additionalData);
|
||||
}
|
||||
|
||||
private:
|
||||
PushNotifications& owner;
|
||||
|
||||
const bool isEarlierThanLion = std::floor (NSFoundationVersionNumber) < std::floor (NSFoundationVersionNumber10_7);
|
||||
const bool isAtLeastMountainLion = std::floor (NSFoundationVersionNumber) >= NSFoundationVersionNumber10_7;
|
||||
const bool isEarlierThanMavericks = std::floor (NSFoundationVersionNumber) < NSFoundationVersionNumber10_9;
|
||||
const bool isEarlierThanYosemite = std::floor (NSFoundationVersionNumber) <= NSFoundationVersionNumber10_9;
|
||||
|
||||
bool initialised = false;
|
||||
String deviceToken;
|
||||
|
||||
PushNotifications::Settings settings;
|
||||
};
|
||||
|
||||
} // namespace juce
|
276
modules/juce_gui_extra/native/juce_mac_SystemTrayIcon.cpp
Normal file
276
modules/juce_gui_extra/native/juce_mac_SystemTrayIcon.cpp
Normal file
@ -0,0 +1,276 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
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 NSMenu* createNSMenu (const PopupMenu&, const String& name, int topLevelMenuId,
|
||||
int topLevelIndex, bool addDelegate);
|
||||
|
||||
class SystemTrayIconComponent::Pimpl : private Timer
|
||||
{
|
||||
public:
|
||||
Pimpl (SystemTrayIconComponent& iconComp, const Image& im)
|
||||
: owner (iconComp), statusIcon (imageToNSImage (im))
|
||||
{
|
||||
static SystemTrayViewClass cls;
|
||||
view = [cls.createInstance() init];
|
||||
SystemTrayViewClass::setOwner (view, this);
|
||||
SystemTrayViewClass::setImage (view, statusIcon);
|
||||
|
||||
setIconSize();
|
||||
|
||||
statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength: NSSquareStatusItemLength] retain];
|
||||
[statusItem setView: view];
|
||||
|
||||
SystemTrayViewClass::frameChanged (view, SEL(), nullptr);
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver: view
|
||||
selector: @selector (frameChanged:)
|
||||
name: NSWindowDidMoveNotification
|
||||
object: nil];
|
||||
}
|
||||
|
||||
~Pimpl()
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver: view];
|
||||
[[NSStatusBar systemStatusBar] removeStatusItem: statusItem];
|
||||
SystemTrayViewClass::setOwner (view, nullptr);
|
||||
SystemTrayViewClass::setImage (view, nil);
|
||||
[statusItem release];
|
||||
[view release];
|
||||
[statusIcon release];
|
||||
}
|
||||
|
||||
void updateIcon (const Image& newImage)
|
||||
{
|
||||
[statusIcon release];
|
||||
statusIcon = imageToNSImage (newImage);
|
||||
setIconSize();
|
||||
SystemTrayViewClass::setImage (view, statusIcon);
|
||||
[statusItem setView: view];
|
||||
}
|
||||
|
||||
void setHighlighted (bool shouldHighlight)
|
||||
{
|
||||
isHighlighted = shouldHighlight;
|
||||
[view setNeedsDisplay: true];
|
||||
}
|
||||
|
||||
void handleStatusItemAction (NSEvent* e)
|
||||
{
|
||||
NSEventType type = [e type];
|
||||
|
||||
const bool isLeft = (type == NSEventTypeLeftMouseDown || type == NSEventTypeLeftMouseUp);
|
||||
const bool isRight = (type == NSEventTypeRightMouseDown || type == NSEventTypeRightMouseUp);
|
||||
|
||||
if (owner.isCurrentlyBlockedByAnotherModalComponent())
|
||||
{
|
||||
if (isLeft || isRight)
|
||||
if (auto* current = Component::getCurrentlyModalComponent())
|
||||
current->inputAttemptWhenModal();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto eventMods = ComponentPeer::getCurrentModifiersRealtime();
|
||||
|
||||
if (([e modifierFlags] & NSEventModifierFlagCommand) != 0)
|
||||
eventMods = eventMods.withFlags (ModifierKeys::commandModifier);
|
||||
|
||||
auto now = Time::getCurrentTime();
|
||||
auto mouseSource = Desktop::getInstance().getMainMouseSource();
|
||||
auto pressure = (float) e.pressure;
|
||||
|
||||
if (isLeft || isRight) // Only mouse up is sent by the OS, so simulate a down/up
|
||||
{
|
||||
setHighlighted (true);
|
||||
startTimer (150);
|
||||
|
||||
owner.mouseDown (MouseEvent (mouseSource, {},
|
||||
eventMods.withFlags (isLeft ? ModifierKeys::leftButtonModifier
|
||||
: ModifierKeys::rightButtonModifier),
|
||||
pressure, MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
|
||||
MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
|
||||
&owner, &owner, now, {}, now, 1, false));
|
||||
|
||||
owner.mouseUp (MouseEvent (mouseSource, {}, eventMods.withoutMouseButtons(), pressure,
|
||||
MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
|
||||
MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
|
||||
&owner, &owner, now, {}, now, 1, false));
|
||||
}
|
||||
else if (type == NSEventTypeMouseMoved)
|
||||
{
|
||||
owner.mouseMove (MouseEvent (mouseSource, {}, eventMods, pressure,
|
||||
MouseInputSource::invalidOrientation, MouseInputSource::invalidRotation,
|
||||
MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
|
||||
&owner, &owner, now, {}, now, 1, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void showMenu (const PopupMenu& menu)
|
||||
{
|
||||
if (NSMenu* m = createNSMenu (menu, "MenuBarItem", -2, -3, true))
|
||||
{
|
||||
setHighlighted (true);
|
||||
stopTimer();
|
||||
[statusItem popUpStatusItemMenu: m];
|
||||
startTimer (1);
|
||||
}
|
||||
}
|
||||
|
||||
SystemTrayIconComponent& owner;
|
||||
NSStatusItem* statusItem = nil;
|
||||
|
||||
private:
|
||||
NSImage* statusIcon = nil;
|
||||
NSControl* view = nil;
|
||||
bool isHighlighted = false;
|
||||
|
||||
void setIconSize()
|
||||
{
|
||||
[statusIcon setSize: NSMakeSize (20.0f, 20.0f)];
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
stopTimer();
|
||||
setHighlighted (false);
|
||||
}
|
||||
|
||||
struct SystemTrayViewClass : public ObjCClass<NSControl>
|
||||
{
|
||||
SystemTrayViewClass() : ObjCClass<NSControl> ("JUCESystemTrayView_")
|
||||
{
|
||||
addIvar<Pimpl*> ("owner");
|
||||
addIvar<NSImage*> ("image");
|
||||
|
||||
addMethod (@selector (mouseDown:), handleEventDown, "v@:@");
|
||||
addMethod (@selector (rightMouseDown:), handleEventDown, "v@:@");
|
||||
addMethod (@selector (drawRect:), drawRect, "v@:@");
|
||||
addMethod (@selector (frameChanged:), frameChanged, "v@:@");
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
static Pimpl* getOwner (id self) { return getIvar<Pimpl*> (self, "owner"); }
|
||||
static NSImage* getImage (id self) { return getIvar<NSImage*> (self, "image"); }
|
||||
static void setOwner (id self, Pimpl* owner) { object_setInstanceVariable (self, "owner", owner); }
|
||||
static void setImage (id self, NSImage* image) { object_setInstanceVariable (self, "image", image); }
|
||||
|
||||
static void frameChanged (id self, SEL, NSNotification*)
|
||||
{
|
||||
if (auto* owner = getOwner (self))
|
||||
{
|
||||
NSRect r = [[[owner->statusItem view] window] frame];
|
||||
NSRect sr = [[[NSScreen screens] objectAtIndex: 0] frame];
|
||||
r.origin.y = sr.size.height - r.origin.y - r.size.height;
|
||||
owner->owner.setBounds (convertToRectInt (r));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static void handleEventDown (id self, SEL, NSEvent* e)
|
||||
{
|
||||
if (auto* owner = getOwner (self))
|
||||
owner->handleStatusItemAction (e);
|
||||
}
|
||||
|
||||
static void drawRect (id self, SEL, NSRect)
|
||||
{
|
||||
NSRect bounds = [self bounds];
|
||||
|
||||
if (auto* owner = getOwner (self))
|
||||
[owner->statusItem drawStatusBarBackgroundInRect: bounds
|
||||
withHighlight: owner->isHighlighted];
|
||||
|
||||
if (NSImage* const im = getImage (self))
|
||||
{
|
||||
NSSize imageSize = [im size];
|
||||
|
||||
[im drawInRect: NSMakeRect (bounds.origin.x + ((bounds.size.width - imageSize.width) / 2.0f),
|
||||
bounds.origin.y + ((bounds.size.height - imageSize.height) / 2.0f),
|
||||
imageSize.width, imageSize.height)
|
||||
fromRect: NSZeroRect
|
||||
operation: NSCompositingOperationSourceOver
|
||||
fraction: 1.0f];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
void SystemTrayIconComponent::setIconImage (const Image& newImage)
|
||||
{
|
||||
if (newImage.isValid())
|
||||
{
|
||||
if (pimpl == nullptr)
|
||||
pimpl.reset (new Pimpl (*this, newImage));
|
||||
else
|
||||
pimpl->updateIcon (newImage);
|
||||
}
|
||||
else
|
||||
{
|
||||
pimpl.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::setIconTooltip (const String&)
|
||||
{
|
||||
// xxx not yet implemented!
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::setHighlighted (bool highlight)
|
||||
{
|
||||
if (pimpl != nullptr)
|
||||
pimpl->setHighlighted (highlight);
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::showInfoBubble (const String& /*title*/, const String& /*content*/)
|
||||
{
|
||||
// xxx Not implemented!
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::hideInfoBubble()
|
||||
{
|
||||
// xxx Not implemented!
|
||||
}
|
||||
|
||||
void* SystemTrayIconComponent::getNativeHandle() const
|
||||
{
|
||||
return pimpl != nullptr ? pimpl->statusItem : nullptr;
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::showDropdownMenu (const PopupMenu& menu)
|
||||
{
|
||||
if (pimpl != nullptr)
|
||||
pimpl->showMenu (menu);
|
||||
}
|
||||
|
||||
} // namespace juce
|
487
modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm
Normal file
487
modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm
Normal file
@ -0,0 +1,487 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#if JUCE_MAC
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
struct WebViewKeyEquivalentResponder : public ObjCClass<WebView>
|
||||
{
|
||||
WebViewKeyEquivalentResponder() : ObjCClass<WebView> ("WebViewKeyEquivalentResponder_")
|
||||
{
|
||||
addMethod (@selector (performKeyEquivalent:), performKeyEquivalent, @encode (BOOL), "@:@");
|
||||
registerClass();
|
||||
}
|
||||
|
||||
private:
|
||||
static BOOL performKeyEquivalent (id self, SEL selector, NSEvent* event)
|
||||
{
|
||||
NSResponder* first = [[self window] firstResponder];
|
||||
|
||||
#if (defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12)
|
||||
if (([event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask) == NSEventModifierFlagCommand)
|
||||
#else
|
||||
if (([event modifierFlags] & NSDeviceIndependentModifierFlagsMask) == NSCommandKeyMask)
|
||||
#endif
|
||||
{
|
||||
if ([[event charactersIgnoringModifiers] isEqualToString:@"x"]) return [NSApp sendAction:@selector(cut:) to:first from:self];
|
||||
if ([[event charactersIgnoringModifiers] isEqualToString:@"c"]) return [NSApp sendAction:@selector(copy:) to:first from:self];
|
||||
if ([[event charactersIgnoringModifiers] isEqualToString:@"v"]) return [NSApp sendAction:@selector(paste:) to:first from:self];
|
||||
if ([[event charactersIgnoringModifiers] isEqualToString:@"a"]) return [NSApp sendAction:@selector(selectAll:) to:first from:self];
|
||||
}
|
||||
|
||||
objc_super s = { self, [WebView class] };
|
||||
return ObjCMsgSendSuper<BOOL, NSEvent*> (&s, selector, event);
|
||||
}
|
||||
};
|
||||
|
||||
struct DownloadClickDetectorClass : public ObjCClass<NSObject>
|
||||
{
|
||||
DownloadClickDetectorClass() : ObjCClass<NSObject> ("JUCEWebClickDetector_")
|
||||
{
|
||||
addIvar<WebBrowserComponent*> ("owner");
|
||||
|
||||
addMethod (@selector (webView:decidePolicyForNavigationAction:request:frame:decisionListener:),
|
||||
decidePolicyForNavigationAction, "v@:@@@@@");
|
||||
addMethod (@selector (webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener:),
|
||||
decidePolicyForNewWindowAction, "v@:@@@@@");
|
||||
addMethod (@selector (webView:didFinishLoadForFrame:), didFinishLoadForFrame, "v@:@@");
|
||||
addMethod (@selector (webView:didFailLoadWithError:forFrame:), didFailLoadWithError, "v@:@@@");
|
||||
addMethod (@selector (webView:didFailProvisionalLoadWithError:forFrame:), didFailLoadWithError, "v@:@@@");
|
||||
addMethod (@selector (webView:willCloseFrame:), willCloseFrame, "v@:@@");
|
||||
addMethod (@selector (webView:runOpenPanelForFileButtonWithResultListener:allowMultipleFiles:), runOpenPanel, "v@:@@", @encode (BOOL));
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
static void setOwner (id self, WebBrowserComponent* owner) { object_setInstanceVariable (self, "owner", owner); }
|
||||
static WebBrowserComponent* getOwner (id self) { return getIvar<WebBrowserComponent*> (self, "owner"); }
|
||||
|
||||
private:
|
||||
static String getOriginalURL (NSDictionary* actionInformation)
|
||||
{
|
||||
if (NSURL* url = [actionInformation valueForKey: nsStringLiteral ("WebActionOriginalURLKey")])
|
||||
return nsStringToJuce ([url absoluteString]);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static void decidePolicyForNavigationAction (id self, SEL, WebView*, NSDictionary* actionInformation,
|
||||
NSURLRequest*, WebFrame*, id<WebPolicyDecisionListener> listener)
|
||||
{
|
||||
if (getOwner (self)->pageAboutToLoad (getOriginalURL (actionInformation)))
|
||||
[listener use];
|
||||
else
|
||||
[listener ignore];
|
||||
}
|
||||
|
||||
static void decidePolicyForNewWindowAction (id self, SEL, WebView*, NSDictionary* actionInformation,
|
||||
NSURLRequest*, NSString*, id<WebPolicyDecisionListener> listener)
|
||||
{
|
||||
getOwner (self)->newWindowAttemptingToLoad (getOriginalURL (actionInformation));
|
||||
[listener ignore];
|
||||
}
|
||||
|
||||
static void didFinishLoadForFrame (id self, SEL, WebView* sender, WebFrame* frame)
|
||||
{
|
||||
if ([frame isEqual: [sender mainFrame]])
|
||||
{
|
||||
NSURL* url = [[[frame dataSource] request] URL];
|
||||
getOwner (self)->pageFinishedLoading (nsStringToJuce ([url absoluteString]));
|
||||
}
|
||||
}
|
||||
|
||||
static void didFailLoadWithError (id self, SEL, WebView* sender, NSError* error, WebFrame* frame)
|
||||
{
|
||||
if ([frame isEqual: [sender mainFrame]] && error != nullptr && [error code] != NSURLErrorCancelled)
|
||||
{
|
||||
auto errorString = nsStringToJuce ([error localizedDescription]);
|
||||
bool proceedToErrorPage = getOwner (self)->pageLoadHadNetworkError (errorString);
|
||||
|
||||
// WebKit doesn't have an internal error page, so make a really simple one ourselves
|
||||
if (proceedToErrorPage)
|
||||
getOwner (self)->goToURL ("data:text/plain;charset=UTF-8," + errorString);
|
||||
}
|
||||
}
|
||||
|
||||
static void willCloseFrame (id self, SEL, WebView*, WebFrame*)
|
||||
{
|
||||
getOwner (self)->windowCloseRequest();
|
||||
}
|
||||
|
||||
static void runOpenPanel (id, SEL, WebView*, id<WebOpenPanelResultListener> resultListener, BOOL allowMultipleFiles)
|
||||
{
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
FileChooser chooser (TRANS("Select the file you want to upload..."),
|
||||
File::getSpecialLocation (File::userHomeDirectory), "*");
|
||||
|
||||
if (allowMultipleFiles ? chooser.browseForMultipleFilesToOpen()
|
||||
: chooser.browseForFileToOpen())
|
||||
{
|
||||
for (auto& f : chooser.getResults())
|
||||
[resultListener chooseFilename: juceStringToNS (f.getFullPathName())];
|
||||
}
|
||||
#else
|
||||
ignoreUnused (resultListener, allowMultipleFiles);
|
||||
jassertfalse; // Can't use this without modal loops being enabled!
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
//==============================================================================
|
||||
@interface WebViewTapDetector : NSObject<UIGestureRecognizerDelegate>
|
||||
{
|
||||
}
|
||||
|
||||
- (BOOL) gestureRecognizer: (UIGestureRecognizer*) gestureRecognizer
|
||||
shouldRecognizeSimultaneouslyWithGestureRecognizer: (UIGestureRecognizer*) otherGestureRecognizer;
|
||||
@end
|
||||
|
||||
@implementation WebViewTapDetector
|
||||
|
||||
- (BOOL) gestureRecognizer: (UIGestureRecognizer*) gestureRecognizer
|
||||
shouldRecognizeSimultaneouslyWithGestureRecognizer: (UIGestureRecognizer*) otherGestureRecognizer
|
||||
{
|
||||
juce::ignoreUnused (gestureRecognizer, otherGestureRecognizer);
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
//==============================================================================
|
||||
@interface WebViewURLChangeDetector : NSObject<UIWebViewDelegate>
|
||||
{
|
||||
juce::WebBrowserComponent* ownerComponent;
|
||||
}
|
||||
|
||||
- (WebViewURLChangeDetector*) initWithWebBrowserOwner: (juce::WebBrowserComponent*) ownerComponent;
|
||||
- (BOOL) webView: (UIWebView*) webView shouldStartLoadWithRequest: (NSURLRequest*) request
|
||||
navigationType: (UIWebViewNavigationType) navigationType;
|
||||
- (void) webViewDidFinishLoad: (UIWebView*) webView;
|
||||
@end
|
||||
|
||||
@implementation WebViewURLChangeDetector
|
||||
|
||||
- (WebViewURLChangeDetector*) initWithWebBrowserOwner: (juce::WebBrowserComponent*) ownerComp
|
||||
{
|
||||
[super init];
|
||||
ownerComponent = ownerComp;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL) webView: (UIWebView*) webView shouldStartLoadWithRequest: (NSURLRequest*) request
|
||||
navigationType: (UIWebViewNavigationType) navigationType
|
||||
{
|
||||
juce::ignoreUnused (webView, navigationType);
|
||||
return ownerComponent->pageAboutToLoad (juce::nsStringToJuce (request.URL.absoluteString));
|
||||
}
|
||||
|
||||
- (void) webViewDidFinishLoad: (UIWebView*) webView
|
||||
{
|
||||
ownerComponent->pageFinishedLoading (juce::nsStringToJuce (webView.request.URL.absoluteString));
|
||||
}
|
||||
@end
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
class WebBrowserComponent::Pimpl
|
||||
#if JUCE_MAC
|
||||
: public NSViewComponent
|
||||
#else
|
||||
: public UIViewComponent
|
||||
#endif
|
||||
{
|
||||
public:
|
||||
Pimpl (WebBrowserComponent* owner)
|
||||
{
|
||||
#if JUCE_MAC
|
||||
static WebViewKeyEquivalentResponder webviewClass;
|
||||
webView = (WebView*) webviewClass.createInstance();
|
||||
|
||||
webView = [webView initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f)
|
||||
frameName: nsEmptyString()
|
||||
groupName: nsEmptyString()];
|
||||
setView (webView);
|
||||
|
||||
static DownloadClickDetectorClass cls;
|
||||
clickListener = [cls.createInstance() init];
|
||||
DownloadClickDetectorClass::setOwner (clickListener, owner);
|
||||
[webView setPolicyDelegate: clickListener];
|
||||
[webView setFrameLoadDelegate: clickListener];
|
||||
[webView setUIDelegate: clickListener];
|
||||
#else
|
||||
webView = [[UIWebView alloc] initWithFrame: CGRectMake (0, 0, 1.0f, 1.0f)];
|
||||
setView (webView);
|
||||
|
||||
tapDetector = [[WebViewTapDetector alloc] init];
|
||||
urlDetector = [[WebViewURLChangeDetector alloc] initWithWebBrowserOwner: owner];
|
||||
gestureRecogniser = nil;
|
||||
webView.delegate = urlDetector;
|
||||
#endif
|
||||
}
|
||||
|
||||
~Pimpl()
|
||||
{
|
||||
#if JUCE_MAC
|
||||
[webView setPolicyDelegate: nil];
|
||||
[webView setFrameLoadDelegate: nil];
|
||||
[webView setUIDelegate: nil];
|
||||
[clickListener release];
|
||||
#else
|
||||
webView.delegate = nil;
|
||||
[webView removeGestureRecognizer: gestureRecogniser];
|
||||
[gestureRecogniser release];
|
||||
[tapDetector release];
|
||||
[urlDetector release];
|
||||
#endif
|
||||
|
||||
setView (nil);
|
||||
}
|
||||
|
||||
void goToURL (const String& url,
|
||||
const StringArray* headers,
|
||||
const MemoryBlock* postData)
|
||||
{
|
||||
stop();
|
||||
|
||||
if (url.trimStart().startsWithIgnoreCase ("javascript:"))
|
||||
{
|
||||
[webView stringByEvaluatingJavaScriptFromString:
|
||||
juceStringToNS (url.fromFirstOccurrenceOf (":", false, false))];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSString* urlString = juceStringToNS (url);
|
||||
|
||||
#if (JUCE_MAC && (defined (__MAC_OS_X_VERSION_MIN_REQUIRED) && defined (__MAC_10_9) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_9)) || (JUCE_IOS && (defined (__IPHONE_OS_VERSION_MIN_REQUIRED) && defined (__IPHONE_7_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0))
|
||||
urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
|
||||
#else
|
||||
urlString = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
|
||||
#endif
|
||||
NSMutableURLRequest* r
|
||||
= [NSMutableURLRequest requestWithURL: [NSURL URLWithString: urlString]
|
||||
cachePolicy: NSURLRequestUseProtocolCachePolicy
|
||||
timeoutInterval: 30.0];
|
||||
|
||||
if (postData != nullptr && postData->getSize() > 0)
|
||||
{
|
||||
[r setHTTPMethod: nsStringLiteral ("POST")];
|
||||
[r setHTTPBody: [NSData dataWithBytes: postData->getData()
|
||||
length: postData->getSize()]];
|
||||
}
|
||||
|
||||
if (headers != nullptr)
|
||||
{
|
||||
for (int i = 0; i < headers->size(); ++i)
|
||||
{
|
||||
const String headerName ((*headers)[i].upToFirstOccurrenceOf (":", false, false).trim());
|
||||
const String headerValue ((*headers)[i].fromFirstOccurrenceOf (":", false, false).trim());
|
||||
|
||||
[r setValue: juceStringToNS (headerValue)
|
||||
forHTTPHeaderField: juceStringToNS (headerName)];
|
||||
}
|
||||
}
|
||||
|
||||
#if JUCE_MAC
|
||||
[[webView mainFrame] loadRequest: r];
|
||||
#else
|
||||
[webView loadRequest: r];
|
||||
#endif
|
||||
|
||||
#if JUCE_IOS
|
||||
[webView setScalesPageToFit:YES];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void goBack() { [webView goBack]; }
|
||||
void goForward() { [webView goForward]; }
|
||||
|
||||
#if JUCE_MAC
|
||||
void stop() { [webView stopLoading: nil]; }
|
||||
void refresh() { [webView reload: nil]; }
|
||||
#else
|
||||
void stop() { [webView stopLoading]; }
|
||||
void refresh() { [webView reload]; }
|
||||
#endif
|
||||
|
||||
void mouseMove (const MouseEvent&)
|
||||
{
|
||||
// WebKit doesn't capture mouse-moves itself, so it seems the only way to make
|
||||
// them work is to push them via this non-public method..
|
||||
if ([webView respondsToSelector: @selector (_updateMouseoverWithFakeEvent)])
|
||||
[webView performSelector: @selector (_updateMouseoverWithFakeEvent)];
|
||||
}
|
||||
|
||||
private:
|
||||
#if JUCE_MAC
|
||||
WebView* webView;
|
||||
id clickListener;
|
||||
#else
|
||||
UIWebView* webView;
|
||||
WebViewTapDetector* tapDetector;
|
||||
WebViewURLChangeDetector* urlDetector;
|
||||
UITapGestureRecognizer* gestureRecogniser;
|
||||
#endif
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
WebBrowserComponent::WebBrowserComponent (bool unloadWhenHidden)
|
||||
: unloadPageWhenBrowserIsHidden (unloadWhenHidden)
|
||||
{
|
||||
setOpaque (true);
|
||||
browser.reset (new Pimpl (this));
|
||||
addAndMakeVisible (browser.get());
|
||||
}
|
||||
|
||||
WebBrowserComponent::~WebBrowserComponent()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void WebBrowserComponent::goToURL (const String& url,
|
||||
const StringArray* headers,
|
||||
const MemoryBlock* postData)
|
||||
{
|
||||
lastURL = url;
|
||||
|
||||
if (headers != nullptr)
|
||||
lastHeaders = *headers;
|
||||
else
|
||||
lastHeaders.clear();
|
||||
|
||||
if (postData != nullptr)
|
||||
lastPostData = *postData;
|
||||
else
|
||||
lastPostData.reset();
|
||||
|
||||
blankPageShown = false;
|
||||
|
||||
browser->goToURL (url, headers, postData);
|
||||
}
|
||||
|
||||
void WebBrowserComponent::stop()
|
||||
{
|
||||
browser->stop();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::goBack()
|
||||
{
|
||||
lastURL.clear();
|
||||
blankPageShown = false;
|
||||
browser->goBack();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::goForward()
|
||||
{
|
||||
lastURL.clear();
|
||||
browser->goForward();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::refresh()
|
||||
{
|
||||
browser->refresh();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void WebBrowserComponent::paint (Graphics&)
|
||||
{
|
||||
}
|
||||
|
||||
void WebBrowserComponent::checkWindowAssociation()
|
||||
{
|
||||
if (isShowing())
|
||||
{
|
||||
reloadLastURL();
|
||||
|
||||
if (blankPageShown)
|
||||
goBack();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (unloadPageWhenBrowserIsHidden && ! blankPageShown)
|
||||
{
|
||||
// when the component becomes invisible, some stuff like flash
|
||||
// carries on playing audio, so we need to force it onto a blank
|
||||
// page to avoid this, (and send it back when it's made visible again).
|
||||
|
||||
blankPageShown = true;
|
||||
browser->goToURL ("about:blank", 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebBrowserComponent::reloadLastURL()
|
||||
{
|
||||
if (lastURL.isNotEmpty())
|
||||
{
|
||||
goToURL (lastURL, &lastHeaders, &lastPostData);
|
||||
lastURL.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void WebBrowserComponent::parentHierarchyChanged()
|
||||
{
|
||||
checkWindowAssociation();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::resized()
|
||||
{
|
||||
browser->setSize (getWidth(), getHeight());
|
||||
}
|
||||
|
||||
void WebBrowserComponent::visibilityChanged()
|
||||
{
|
||||
checkWindowAssociation();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::focusGained (FocusChangeType)
|
||||
{
|
||||
}
|
||||
|
||||
void WebBrowserComponent::clearCookies()
|
||||
{
|
||||
NSHTTPCookieStorage* storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
|
||||
|
||||
if (NSArray* cookies = [storage cookies])
|
||||
{
|
||||
const NSUInteger n = [cookies count];
|
||||
|
||||
for (NSUInteger i = 0; i < n; ++i)
|
||||
[storage deleteCookie: [cookies objectAtIndex: i]];
|
||||
}
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
||||
}
|
||||
|
||||
} // namespace juce
|
458
modules/juce_gui_extra/native/juce_win32_ActiveXComponent.cpp
Normal file
458
modules/juce_gui_extra/native/juce_win32_ActiveXComponent.cpp
Normal file
@ -0,0 +1,458 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
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 int64 getMouseEventTime();
|
||||
|
||||
JUCE_DECLARE_UUID_GETTER (IOleObject, "00000112-0000-0000-C000-000000000046")
|
||||
JUCE_DECLARE_UUID_GETTER (IOleWindow, "00000114-0000-0000-C000-000000000046")
|
||||
JUCE_DECLARE_UUID_GETTER (IOleInPlaceSite, "00000119-0000-0000-C000-000000000046")
|
||||
|
||||
namespace ActiveXHelpers
|
||||
{
|
||||
//==============================================================================
|
||||
struct JuceIStorage : public ComBaseClassHelper<IStorage>
|
||||
{
|
||||
JuceIStorage() {}
|
||||
|
||||
JUCE_COMRESULT CreateStream (const WCHAR*, DWORD, DWORD, DWORD, IStream**) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT OpenStream (const WCHAR*, void*, DWORD, DWORD, IStream**) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT CreateStorage (const WCHAR*, DWORD, DWORD, DWORD, IStorage**) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT OpenStorage (const WCHAR*, IStorage*, DWORD, SNB, DWORD, IStorage**) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT CopyTo (DWORD, IID const*, SNB, IStorage*) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT MoveElementTo (const OLECHAR*,IStorage*, const OLECHAR*, DWORD) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT Commit (DWORD) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT Revert() { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT EnumElements (DWORD, void*, DWORD, IEnumSTATSTG**) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT DestroyElement (const OLECHAR*) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT RenameElement (const WCHAR*, const WCHAR*) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT SetElementTimes (const WCHAR*, FILETIME const*, FILETIME const*, FILETIME const*) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT SetClass (REFCLSID) { return S_OK; }
|
||||
JUCE_COMRESULT SetStateBits (DWORD, DWORD) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT Stat (STATSTG*, DWORD) { return E_NOTIMPL; }
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct JuceOleInPlaceFrame : public ComBaseClassHelper<IOleInPlaceFrame>
|
||||
{
|
||||
JuceOleInPlaceFrame (HWND hwnd) : window (hwnd) {}
|
||||
|
||||
JUCE_COMRESULT GetWindow (HWND* lphwnd) { *lphwnd = window; return S_OK; }
|
||||
JUCE_COMRESULT ContextSensitiveHelp (BOOL) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT GetBorder (LPRECT) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT RequestBorderSpace (LPCBORDERWIDTHS) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT SetBorderSpace (LPCBORDERWIDTHS) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT SetActiveObject (IOleInPlaceActiveObject* a, LPCOLESTR) { activeObject = a; return S_OK; }
|
||||
JUCE_COMRESULT InsertMenus (HMENU, LPOLEMENUGROUPWIDTHS) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT SetMenu (HMENU, HOLEMENU, HWND) { return S_OK; }
|
||||
JUCE_COMRESULT RemoveMenus (HMENU) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT SetStatusText (LPCOLESTR) { return S_OK; }
|
||||
JUCE_COMRESULT EnableModeless (BOOL) { return S_OK; }
|
||||
JUCE_COMRESULT TranslateAccelerator (LPMSG, WORD) { return E_NOTIMPL; }
|
||||
|
||||
HRESULT OfferKeyTranslation (LPMSG lpmsg)
|
||||
{
|
||||
if (activeObject != nullptr)
|
||||
return activeObject->TranslateAcceleratorW (lpmsg);
|
||||
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
HWND window;
|
||||
ComSmartPtr<IOleInPlaceActiveObject> activeObject;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct JuceIOleInPlaceSite : public ComBaseClassHelper<IOleInPlaceSite>
|
||||
{
|
||||
JuceIOleInPlaceSite (HWND hwnd)
|
||||
: window (hwnd),
|
||||
frame (new JuceOleInPlaceFrame (window))
|
||||
{}
|
||||
|
||||
~JuceIOleInPlaceSite()
|
||||
{
|
||||
frame->Release();
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetWindow (HWND* lphwnd) { *lphwnd = window; return S_OK; }
|
||||
JUCE_COMRESULT ContextSensitiveHelp (BOOL) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT CanInPlaceActivate() { return S_OK; }
|
||||
JUCE_COMRESULT OnInPlaceActivate() { return S_OK; }
|
||||
JUCE_COMRESULT OnUIActivate() { return S_OK; }
|
||||
|
||||
JUCE_COMRESULT GetWindowContext (LPOLEINPLACEFRAME* lplpFrame, LPOLEINPLACEUIWINDOW* lplpDoc, LPRECT, LPRECT, LPOLEINPLACEFRAMEINFO lpFrameInfo)
|
||||
{
|
||||
/* Note: if you call AddRef on the frame here, then some types of object (e.g. web browser control) cause leaks..
|
||||
If you don't call AddRef then others crash (e.g. QuickTime).. Bit of a catch-22, so letting it leak is probably preferable.
|
||||
*/
|
||||
if (lplpFrame != nullptr) { frame->AddRef(); *lplpFrame = frame; }
|
||||
if (lplpDoc != nullptr) *lplpDoc = nullptr;
|
||||
lpFrameInfo->fMDIApp = FALSE;
|
||||
lpFrameInfo->hwndFrame = window;
|
||||
lpFrameInfo->haccel = 0;
|
||||
lpFrameInfo->cAccelEntries = 0;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Scroll (SIZE) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT OnUIDeactivate (BOOL) { return S_OK; }
|
||||
JUCE_COMRESULT OnInPlaceDeactivate() { return S_OK; }
|
||||
JUCE_COMRESULT DiscardUndoState() { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT DeactivateAndUndo() { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT OnPosRectChange (LPCRECT) { return S_OK; }
|
||||
|
||||
LRESULT offerEventToActiveXControl (::MSG& msg)
|
||||
{
|
||||
if (frame != nullptr)
|
||||
return frame->OfferKeyTranslation (&msg);
|
||||
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
HWND window;
|
||||
JuceOleInPlaceFrame* frame;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct JuceIOleClientSite : public ComBaseClassHelper<IOleClientSite>
|
||||
{
|
||||
JuceIOleClientSite (HWND window) : inplaceSite (new JuceIOleInPlaceSite (window))
|
||||
{}
|
||||
|
||||
~JuceIOleClientSite()
|
||||
{
|
||||
inplaceSite->Release();
|
||||
}
|
||||
|
||||
JUCE_COMRESULT QueryInterface (REFIID type, void** result)
|
||||
{
|
||||
if (type == __uuidof (IOleInPlaceSite))
|
||||
{
|
||||
inplaceSite->AddRef();
|
||||
*result = static_cast<IOleInPlaceSite*> (inplaceSite);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return ComBaseClassHelper <IOleClientSite>::QueryInterface (type, result);
|
||||
}
|
||||
|
||||
JUCE_COMRESULT SaveObject() { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT GetMoniker (DWORD, DWORD, IMoniker**) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT GetContainer (LPOLECONTAINER* ppContainer) { *ppContainer = nullptr; return E_NOINTERFACE; }
|
||||
JUCE_COMRESULT ShowObject() { return S_OK; }
|
||||
JUCE_COMRESULT OnShowWindow (BOOL) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT RequestNewObjectLayout() { return E_NOTIMPL; }
|
||||
|
||||
LRESULT offerEventToActiveXControl (::MSG& msg)
|
||||
{
|
||||
if (inplaceSite != nullptr)
|
||||
return inplaceSite->offerEventToActiveXControl (msg);
|
||||
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
JuceIOleInPlaceSite* inplaceSite;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
static Array<ActiveXControlComponent*> activeXComps;
|
||||
|
||||
static inline HWND getHWND (const ActiveXControlComponent* const component)
|
||||
{
|
||||
HWND hwnd = {};
|
||||
const IID iid = __uuidof (IOleWindow);
|
||||
|
||||
if (auto* window = (IOleWindow*) component->queryInterface (&iid))
|
||||
{
|
||||
window->GetWindow (&hwnd);
|
||||
window->Release();
|
||||
}
|
||||
|
||||
return hwnd;
|
||||
}
|
||||
|
||||
static inline void offerActiveXMouseEventToPeer (ComponentPeer* peer, HWND hwnd, UINT message, LPARAM lParam)
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case WM_MOUSEMOVE:
|
||||
case WM_LBUTTONDOWN:
|
||||
case WM_MBUTTONDOWN:
|
||||
case WM_RBUTTONDOWN:
|
||||
case WM_LBUTTONUP:
|
||||
case WM_MBUTTONUP:
|
||||
case WM_RBUTTONUP:
|
||||
{
|
||||
RECT activeXRect, peerRect;
|
||||
GetWindowRect (hwnd, &activeXRect);
|
||||
GetWindowRect ((HWND) peer->getNativeHandle(), &peerRect);
|
||||
|
||||
peer->handleMouseEvent (MouseInputSource::InputSourceType::mouse,
|
||||
{ (float) (GET_X_LPARAM (lParam) + activeXRect.left - peerRect.left),
|
||||
(float) (GET_Y_LPARAM (lParam) + activeXRect.top - peerRect.top) },
|
||||
ComponentPeer::getCurrentModifiersRealtime(),
|
||||
MouseInputSource::invalidPressure,
|
||||
MouseInputSource::invalidOrientation,
|
||||
getMouseEventTime());
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class ActiveXControlComponent::Pimpl : public ComponentMovementWatcher
|
||||
{
|
||||
public:
|
||||
Pimpl (HWND hwnd, ActiveXControlComponent& activeXComp)
|
||||
: ComponentMovementWatcher (&activeXComp),
|
||||
owner (activeXComp),
|
||||
storage (new ActiveXHelpers::JuceIStorage()),
|
||||
clientSite (new ActiveXHelpers::JuceIOleClientSite (hwnd))
|
||||
{
|
||||
}
|
||||
|
||||
~Pimpl()
|
||||
{
|
||||
if (control != nullptr)
|
||||
{
|
||||
control->Close (OLECLOSE_NOSAVE);
|
||||
control->Release();
|
||||
}
|
||||
|
||||
clientSite->Release();
|
||||
storage->Release();
|
||||
}
|
||||
|
||||
void setControlBounds (Rectangle<int> newBounds) const
|
||||
{
|
||||
if (controlHWND != 0)
|
||||
MoveWindow (controlHWND, newBounds.getX(), newBounds.getY(), newBounds.getWidth(), newBounds.getHeight(), TRUE);
|
||||
}
|
||||
|
||||
void setControlVisible (bool shouldBeVisible) const
|
||||
{
|
||||
if (controlHWND != 0)
|
||||
ShowWindow (controlHWND, shouldBeVisible ? SW_SHOWNA : SW_HIDE);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
|
||||
{
|
||||
if (auto* peer = owner.getTopLevelComponent()->getPeer())
|
||||
setControlBounds (peer->getAreaCoveredBy(owner));
|
||||
}
|
||||
|
||||
void componentPeerChanged() override
|
||||
{
|
||||
componentMovedOrResized (true, true);
|
||||
}
|
||||
|
||||
void componentVisibilityChanged() override
|
||||
{
|
||||
setControlVisible (owner.isShowing());
|
||||
componentPeerChanged();
|
||||
}
|
||||
|
||||
// intercepts events going to an activeX control, so we can sneakily use the mouse events
|
||||
static LRESULT CALLBACK activeXHookWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
for (auto* ax : ActiveXHelpers::activeXComps)
|
||||
{
|
||||
if (ax->control != nullptr && ax->control->controlHWND == hwnd)
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case WM_MOUSEMOVE:
|
||||
case WM_LBUTTONDOWN:
|
||||
case WM_MBUTTONDOWN:
|
||||
case WM_RBUTTONDOWN:
|
||||
case WM_LBUTTONUP:
|
||||
case WM_MBUTTONUP:
|
||||
case WM_RBUTTONUP:
|
||||
case WM_LBUTTONDBLCLK:
|
||||
case WM_MBUTTONDBLCLK:
|
||||
case WM_RBUTTONDBLCLK:
|
||||
if (ax->isShowing())
|
||||
{
|
||||
if (auto* peer = ax->getPeer())
|
||||
{
|
||||
ActiveXHelpers::offerActiveXMouseEventToPeer (peer, hwnd, message, lParam);
|
||||
|
||||
if (! ax->areMouseEventsAllowed())
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return CallWindowProc (ax->control->originalWndProc, hwnd, message, wParam, lParam);
|
||||
}
|
||||
}
|
||||
|
||||
return DefWindowProc (hwnd, message, wParam, lParam);
|
||||
}
|
||||
|
||||
ActiveXControlComponent& owner;
|
||||
HWND controlHWND = {};
|
||||
IStorage* storage = nullptr;
|
||||
ActiveXHelpers::JuceIOleClientSite* clientSite = nullptr;
|
||||
IOleObject* control = nullptr;
|
||||
WNDPROC originalWndProc = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
ActiveXControlComponent::ActiveXControlComponent()
|
||||
{
|
||||
ActiveXHelpers::activeXComps.add (this);
|
||||
}
|
||||
|
||||
ActiveXControlComponent::~ActiveXControlComponent()
|
||||
{
|
||||
deleteControl();
|
||||
ActiveXHelpers::activeXComps.removeFirstMatchingValue (this);
|
||||
}
|
||||
|
||||
void ActiveXControlComponent::paint (Graphics& g)
|
||||
{
|
||||
if (control == nullptr)
|
||||
g.fillAll (Colours::lightgrey);
|
||||
}
|
||||
|
||||
bool ActiveXControlComponent::createControl (const void* controlIID)
|
||||
{
|
||||
deleteControl();
|
||||
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
auto controlBounds = peer->getAreaCoveredBy (*this);
|
||||
auto hwnd = (HWND) peer->getNativeHandle();
|
||||
|
||||
std::unique_ptr<Pimpl> newControl (new Pimpl (hwnd, *this));
|
||||
|
||||
HRESULT hr = OleCreate (*(const IID*) controlIID, __uuidof (IOleObject), 1 /*OLERENDER_DRAW*/, 0,
|
||||
newControl->clientSite, newControl->storage,
|
||||
(void**) &(newControl->control));
|
||||
|
||||
if (hr == S_OK)
|
||||
{
|
||||
newControl->control->SetHostNames (L"JUCE", 0);
|
||||
|
||||
if (OleSetContainedObject (newControl->control, TRUE) == S_OK)
|
||||
{
|
||||
RECT rect;
|
||||
rect.left = controlBounds.getX();
|
||||
rect.top = controlBounds.getY();
|
||||
rect.right = controlBounds.getRight();
|
||||
rect.bottom = controlBounds.getBottom();
|
||||
|
||||
if (newControl->control->DoVerb (OLEIVERB_SHOW, 0, newControl->clientSite, 0, hwnd, &rect) == S_OK)
|
||||
{
|
||||
control.reset (newControl.release());
|
||||
control->controlHWND = ActiveXHelpers::getHWND (this);
|
||||
|
||||
if (control->controlHWND != 0)
|
||||
{
|
||||
control->setControlBounds (controlBounds);
|
||||
|
||||
control->originalWndProc = (WNDPROC) GetWindowLongPtr ((HWND) control->controlHWND, GWLP_WNDPROC);
|
||||
SetWindowLongPtr ((HWND) control->controlHWND, GWLP_WNDPROC, (LONG_PTR) Pimpl::activeXHookWndProc);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// the component must have already been added to a real window when you call this!
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ActiveXControlComponent::deleteControl()
|
||||
{
|
||||
control = nullptr;
|
||||
}
|
||||
|
||||
void* ActiveXControlComponent::queryInterface (const void* iid) const
|
||||
{
|
||||
void* result = nullptr;
|
||||
|
||||
if (control != nullptr && control->control != nullptr
|
||||
&& SUCCEEDED (control->control->QueryInterface (*(const IID*) iid, &result)))
|
||||
return result;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ActiveXControlComponent::setMouseEventsAllowed (const bool eventsCanReachControl)
|
||||
{
|
||||
mouseEventsAllowed = eventsCanReachControl;
|
||||
}
|
||||
|
||||
intptr_t ActiveXControlComponent::offerEventToActiveXControl (void* ptr)
|
||||
{
|
||||
if (control != nullptr && control->clientSite != nullptr)
|
||||
return (intptr_t) control->clientSite->offerEventToActiveXControl (*reinterpret_cast<::MSG*> (ptr));
|
||||
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
intptr_t ActiveXControlComponent::offerEventToActiveXControlStatic (void* ptr)
|
||||
{
|
||||
for (auto* ax : ActiveXHelpers::activeXComps)
|
||||
{
|
||||
auto result = ax->offerEventToActiveXControl (ptr);
|
||||
|
||||
if (result != S_FALSE)
|
||||
return result;
|
||||
}
|
||||
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
LRESULT juce_offerEventToActiveXControl (::MSG& msg)
|
||||
{
|
||||
if (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST)
|
||||
return ActiveXControlComponent::offerEventToActiveXControlStatic (&msg);
|
||||
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
} // namespace juce
|
243
modules/juce_gui_extra/native/juce_win32_SystemTrayIcon.cpp
Normal file
243
modules/juce_gui_extra/native/juce_win32_SystemTrayIcon.cpp
Normal file
@ -0,0 +1,243 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
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 void* getUser32Function (const char*);
|
||||
|
||||
namespace IconConverters
|
||||
{
|
||||
extern HICON createHICONFromImage (const Image&, BOOL isIcon, int hotspotX, int hotspotY);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class SystemTrayIconComponent::Pimpl
|
||||
{
|
||||
public:
|
||||
Pimpl (SystemTrayIconComponent& owner_, HICON hicon, HWND hwnd)
|
||||
: owner (owner_),
|
||||
originalWndProc ((WNDPROC) GetWindowLongPtr (hwnd, GWLP_WNDPROC)),
|
||||
taskbarCreatedMessage (RegisterWindowMessage (TEXT ("TaskbarCreated")))
|
||||
{
|
||||
SetWindowLongPtr (hwnd, GWLP_WNDPROC, (LONG_PTR) hookedWndProc);
|
||||
|
||||
zerostruct (iconData);
|
||||
iconData.cbSize = sizeof (iconData);
|
||||
iconData.hWnd = hwnd;
|
||||
iconData.uID = (UINT) (pointer_sized_int) hwnd;
|
||||
iconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
|
||||
iconData.uCallbackMessage = WM_TRAYNOTIFY;
|
||||
iconData.hIcon = hicon;
|
||||
|
||||
notify (NIM_ADD);
|
||||
|
||||
// In order to receive the "TaskbarCreated" message, we need to request that it's not filtered out.
|
||||
// (Need to load dynamically, as ChangeWindowMessageFilter is only available in Vista and later)
|
||||
typedef BOOL (WINAPI* ChangeWindowMessageFilterType) (UINT, DWORD);
|
||||
|
||||
if (ChangeWindowMessageFilterType changeWindowMessageFilter
|
||||
= (ChangeWindowMessageFilterType) getUser32Function ("ChangeWindowMessageFilter"))
|
||||
changeWindowMessageFilter (taskbarCreatedMessage, 1 /* MSGFLT_ADD */);
|
||||
}
|
||||
|
||||
~Pimpl()
|
||||
{
|
||||
SetWindowLongPtr (iconData.hWnd, GWLP_WNDPROC, (LONG_PTR) originalWndProc);
|
||||
|
||||
iconData.uFlags = 0;
|
||||
notify (NIM_DELETE);
|
||||
DestroyIcon (iconData.hIcon);
|
||||
}
|
||||
|
||||
void updateIcon (HICON hicon)
|
||||
{
|
||||
HICON oldIcon = iconData.hIcon;
|
||||
|
||||
iconData.hIcon = hicon;
|
||||
iconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
|
||||
notify (NIM_MODIFY);
|
||||
|
||||
DestroyIcon (oldIcon);
|
||||
}
|
||||
|
||||
void setToolTip (const String& toolTip)
|
||||
{
|
||||
iconData.uFlags = NIF_TIP;
|
||||
toolTip.copyToUTF16 (iconData.szTip, sizeof (iconData.szTip) - 1);
|
||||
notify (NIM_MODIFY);
|
||||
}
|
||||
|
||||
void handleTaskBarEvent (const LPARAM lParam)
|
||||
{
|
||||
if (owner.isCurrentlyBlockedByAnotherModalComponent())
|
||||
{
|
||||
if (lParam == WM_LBUTTONDOWN || lParam == WM_RBUTTONDOWN
|
||||
|| lParam == WM_LBUTTONDBLCLK || lParam == WM_RBUTTONDBLCLK)
|
||||
{
|
||||
if (auto* current = Component::getCurrentlyModalComponent())
|
||||
current->inputAttemptWhenModal();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ModifierKeys eventMods (ComponentPeer::getCurrentModifiersRealtime());
|
||||
|
||||
if (lParam == WM_LBUTTONDOWN || lParam == WM_LBUTTONDBLCLK)
|
||||
eventMods = eventMods.withFlags (ModifierKeys::leftButtonModifier);
|
||||
else if (lParam == WM_RBUTTONDOWN || lParam == WM_RBUTTONDBLCLK)
|
||||
eventMods = eventMods.withFlags (ModifierKeys::rightButtonModifier);
|
||||
else if (lParam == WM_LBUTTONUP || lParam == WM_RBUTTONUP)
|
||||
eventMods = eventMods.withoutMouseButtons();
|
||||
|
||||
const Time eventTime (getMouseEventTime());
|
||||
|
||||
const MouseEvent e (Desktop::getInstance().getMainMouseSource(), {}, eventMods,
|
||||
MouseInputSource::invalidPressure, MouseInputSource::invalidOrientation,
|
||||
MouseInputSource::invalidRotation, MouseInputSource::invalidTiltX, MouseInputSource::invalidTiltY,
|
||||
&owner, &owner, eventTime, {}, eventTime, 1, false);
|
||||
|
||||
if (lParam == WM_LBUTTONDOWN || lParam == WM_RBUTTONDOWN)
|
||||
{
|
||||
SetFocus (iconData.hWnd);
|
||||
SetForegroundWindow (iconData.hWnd);
|
||||
owner.mouseDown (e);
|
||||
}
|
||||
else if (lParam == WM_LBUTTONUP || lParam == WM_RBUTTONUP)
|
||||
{
|
||||
owner.mouseUp (e);
|
||||
}
|
||||
else if (lParam == WM_LBUTTONDBLCLK || lParam == WM_RBUTTONDBLCLK)
|
||||
{
|
||||
owner.mouseDoubleClick (e);
|
||||
}
|
||||
else if (lParam == WM_MOUSEMOVE)
|
||||
{
|
||||
owner.mouseMove (e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Pimpl* getPimpl (HWND hwnd)
|
||||
{
|
||||
if (JuceWindowIdentifier::isJUCEWindow (hwnd))
|
||||
if (ComponentPeer* peer = (ComponentPeer*) GetWindowLongPtr (hwnd, 8))
|
||||
if (SystemTrayIconComponent* const iconComp = dynamic_cast<SystemTrayIconComponent*> (&(peer->getComponent())))
|
||||
return iconComp->pimpl.get();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK hookedWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (Pimpl* const p = getPimpl (hwnd))
|
||||
return p->windowProc (hwnd, message, wParam, lParam);
|
||||
|
||||
return DefWindowProcW (hwnd, message, wParam, lParam);
|
||||
}
|
||||
|
||||
LRESULT windowProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (message == WM_TRAYNOTIFY)
|
||||
{
|
||||
handleTaskBarEvent (lParam);
|
||||
}
|
||||
else if (message == taskbarCreatedMessage)
|
||||
{
|
||||
iconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
|
||||
notify (NIM_ADD);
|
||||
}
|
||||
|
||||
return CallWindowProc (originalWndProc, hwnd, message, wParam, lParam);
|
||||
}
|
||||
|
||||
void showBubble (const String& title, const String& content)
|
||||
{
|
||||
iconData.uFlags = 0x10 /*NIF_INFO*/;
|
||||
title.copyToUTF16 (iconData.szInfoTitle, sizeof (iconData.szInfoTitle) - 1);
|
||||
content.copyToUTF16 (iconData.szInfo, sizeof (iconData.szInfo) - 1);
|
||||
notify (NIM_MODIFY);
|
||||
}
|
||||
|
||||
SystemTrayIconComponent& owner;
|
||||
NOTIFYICONDATA iconData;
|
||||
|
||||
private:
|
||||
WNDPROC originalWndProc;
|
||||
const DWORD taskbarCreatedMessage;
|
||||
enum { WM_TRAYNOTIFY = WM_USER + 100 };
|
||||
|
||||
void notify (DWORD message) noexcept { Shell_NotifyIcon (message, &iconData); }
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
void SystemTrayIconComponent::setIconImage (const Image& newImage)
|
||||
{
|
||||
if (newImage.isValid())
|
||||
{
|
||||
HICON hicon = IconConverters::createHICONFromImage (newImage, TRUE, 0, 0);
|
||||
|
||||
if (pimpl == nullptr)
|
||||
pimpl.reset (new Pimpl (*this, hicon, (HWND) getWindowHandle()));
|
||||
else
|
||||
pimpl->updateIcon (hicon);
|
||||
}
|
||||
else
|
||||
{
|
||||
pimpl.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::setIconTooltip (const String& tooltip)
|
||||
{
|
||||
if (pimpl != nullptr)
|
||||
pimpl->setToolTip (tooltip);
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::setHighlighted (bool)
|
||||
{
|
||||
// N/A on Windows.
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::showInfoBubble (const String& title, const String& content)
|
||||
{
|
||||
if (pimpl != nullptr)
|
||||
pimpl->showBubble (title, content);
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::hideInfoBubble()
|
||||
{
|
||||
showInfoBubble (String(), String());
|
||||
}
|
||||
|
||||
void* SystemTrayIconComponent::getNativeHandle() const
|
||||
{
|
||||
return pimpl != nullptr ? &(pimpl->iconData) : nullptr;
|
||||
}
|
||||
|
||||
} // namespace juce
|
435
modules/juce_gui_extra/native/juce_win32_WebBrowserComponent.cpp
Normal file
435
modules/juce_gui_extra/native/juce_win32_WebBrowserComponent.cpp
Normal file
@ -0,0 +1,435 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
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_MINGW
|
||||
JUCE_DECLARE_UUID_GETTER (IOleClientSite, "00000118-0000-0000-c000-000000000046")
|
||||
JUCE_DECLARE_UUID_GETTER (IDispatch, "00020400-0000-0000-c000-000000000046")
|
||||
|
||||
#ifndef WebBrowser
|
||||
class WebBrowser;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
JUCE_DECLARE_UUID_GETTER (DWebBrowserEvents2, "34A715A0-6587-11D0-924A-0020AFC7AC4D")
|
||||
JUCE_DECLARE_UUID_GETTER (IConnectionPointContainer, "B196B284-BAB4-101A-B69C-00AA00341D07")
|
||||
JUCE_DECLARE_UUID_GETTER (IWebBrowser2, "D30C1661-CDAF-11D0-8A3E-00C04FC9E26E")
|
||||
JUCE_DECLARE_UUID_GETTER (WebBrowser, "8856F961-340A-11D0-A96B-00C04FD705A2")
|
||||
|
||||
class WebBrowserComponent::Pimpl : public ActiveXControlComponent
|
||||
{
|
||||
public:
|
||||
Pimpl() {}
|
||||
|
||||
~Pimpl()
|
||||
{
|
||||
if (connectionPoint != nullptr)
|
||||
connectionPoint->Unadvise (adviseCookie);
|
||||
|
||||
if (browser != nullptr)
|
||||
browser->Release();
|
||||
}
|
||||
|
||||
void createBrowser()
|
||||
{
|
||||
auto webCLSID = __uuidof (WebBrowser);
|
||||
createControl (&webCLSID);
|
||||
|
||||
auto iidWebBrowser2 = __uuidof (IWebBrowser2);
|
||||
auto iidConnectionPointContainer = __uuidof (IConnectionPointContainer);
|
||||
|
||||
browser = (IWebBrowser2*) queryInterface (&iidWebBrowser2);
|
||||
|
||||
if (auto connectionPointContainer = (IConnectionPointContainer*) queryInterface (&iidConnectionPointContainer))
|
||||
{
|
||||
connectionPointContainer->FindConnectionPoint (__uuidof (DWebBrowserEvents2), &connectionPoint);
|
||||
|
||||
if (connectionPoint != nullptr)
|
||||
{
|
||||
auto* owner = dynamic_cast<WebBrowserComponent*> (getParentComponent());
|
||||
jassert (owner != nullptr);
|
||||
|
||||
auto handler = new EventHandler (*owner);
|
||||
connectionPoint->Advise (handler, &adviseCookie);
|
||||
handler->Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void goToURL (const String& url,
|
||||
const StringArray* headers,
|
||||
const MemoryBlock* postData)
|
||||
{
|
||||
if (browser != nullptr)
|
||||
{
|
||||
LPSAFEARRAY sa = nullptr;
|
||||
|
||||
VARIANT headerFlags, frame, postDataVar, headersVar; // (_variant_t isn't available in all compilers)
|
||||
VariantInit (&headerFlags);
|
||||
VariantInit (&frame);
|
||||
VariantInit (&postDataVar);
|
||||
VariantInit (&headersVar);
|
||||
|
||||
if (headers != nullptr)
|
||||
{
|
||||
V_VT (&headersVar) = VT_BSTR;
|
||||
V_BSTR (&headersVar) = SysAllocString ((const OLECHAR*) headers->joinIntoString ("\r\n").toWideCharPointer());
|
||||
}
|
||||
|
||||
if (postData != nullptr && postData->getSize() > 0)
|
||||
{
|
||||
sa = SafeArrayCreateVector (VT_UI1, 0, (ULONG) postData->getSize());
|
||||
|
||||
if (sa != nullptr)
|
||||
{
|
||||
void* data = nullptr;
|
||||
SafeArrayAccessData (sa, &data);
|
||||
jassert (data != nullptr);
|
||||
|
||||
if (data != nullptr)
|
||||
{
|
||||
postData->copyTo (data, 0, postData->getSize());
|
||||
SafeArrayUnaccessData (sa);
|
||||
|
||||
VARIANT postDataVar2;
|
||||
VariantInit (&postDataVar2);
|
||||
V_VT (&postDataVar2) = VT_ARRAY | VT_UI1;
|
||||
V_ARRAY (&postDataVar2) = sa;
|
||||
|
||||
postDataVar = postDataVar2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto urlBSTR = SysAllocString ((const OLECHAR*) url.toWideCharPointer());
|
||||
browser->Navigate (urlBSTR, &headerFlags, &frame, &postDataVar, &headersVar);
|
||||
SysFreeString (urlBSTR);
|
||||
|
||||
if (sa != nullptr)
|
||||
SafeArrayDestroy (sa);
|
||||
|
||||
VariantClear (&headerFlags);
|
||||
VariantClear (&frame);
|
||||
VariantClear (&postDataVar);
|
||||
VariantClear (&headersVar);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
IWebBrowser2* browser = nullptr;
|
||||
|
||||
private:
|
||||
IConnectionPoint* connectionPoint = nullptr;
|
||||
DWORD adviseCookie = 0;
|
||||
|
||||
//==============================================================================
|
||||
struct EventHandler : public ComBaseClassHelper<IDispatch>,
|
||||
public ComponentMovementWatcher
|
||||
{
|
||||
EventHandler (WebBrowserComponent& w) : ComponentMovementWatcher (&w), owner (w) {}
|
||||
|
||||
JUCE_COMRESULT GetTypeInfoCount (UINT*) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT GetTypeInfo (UINT, LCID, ITypeInfo**) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT GetIDsOfNames (REFIID, LPOLESTR*, UINT, LCID, DISPID*) { return E_NOTIMPL; }
|
||||
|
||||
JUCE_COMRESULT Invoke (DISPID dispIdMember, REFIID /*riid*/, LCID /*lcid*/, WORD /*wFlags*/, DISPPARAMS* pDispParams,
|
||||
VARIANT* /*pVarResult*/, EXCEPINFO* /*pExcepInfo*/, UINT* /*puArgErr*/)
|
||||
{
|
||||
if (dispIdMember == DISPID_BEFORENAVIGATE2)
|
||||
{
|
||||
*pDispParams->rgvarg->pboolVal
|
||||
= owner.pageAboutToLoad (getStringFromVariant (pDispParams->rgvarg[5].pvarVal)) ? VARIANT_FALSE
|
||||
: VARIANT_TRUE;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
if (dispIdMember == 273 /*DISPID_NEWWINDOW3*/)
|
||||
{
|
||||
owner.newWindowAttemptingToLoad (pDispParams->rgvarg[0].bstrVal);
|
||||
*pDispParams->rgvarg[3].pboolVal = VARIANT_TRUE;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
if (dispIdMember == DISPID_DOCUMENTCOMPLETE)
|
||||
{
|
||||
owner.pageFinishedLoading (getStringFromVariant (pDispParams->rgvarg[0].pvarVal));
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
if (dispIdMember == 271 /*DISPID_NAVIGATEERROR*/)
|
||||
{
|
||||
int statusCode = pDispParams->rgvarg[1].pvarVal->intVal;
|
||||
*pDispParams->rgvarg[0].pboolVal = VARIANT_FALSE;
|
||||
|
||||
// IWebBrowser2 also reports http status codes here, we need
|
||||
// report only network erros
|
||||
if (statusCode < 0)
|
||||
{
|
||||
LPTSTR messageBuffer = nullptr;
|
||||
auto size = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr, statusCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
(LPTSTR) &messageBuffer, 0, nullptr);
|
||||
|
||||
String message (messageBuffer, size);
|
||||
LocalFree (messageBuffer);
|
||||
|
||||
if (! owner.pageLoadHadNetworkError (message))
|
||||
*pDispParams->rgvarg[0].pboolVal = VARIANT_TRUE;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
if (dispIdMember == 263 /*DISPID_WINDOWCLOSING*/)
|
||||
{
|
||||
owner.windowCloseRequest();
|
||||
|
||||
// setting this bool tells the browser to ignore the event - we'll handle it.
|
||||
if (pDispParams->cArgs > 0 && pDispParams->rgvarg[0].vt == (VT_BYREF | VT_BOOL))
|
||||
*pDispParams->rgvarg[0].pboolVal = VARIANT_TRUE;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
void componentMovedOrResized (bool, bool) override {}
|
||||
void componentPeerChanged() override {}
|
||||
void componentVisibilityChanged() override { owner.visibilityChanged(); }
|
||||
|
||||
private:
|
||||
WebBrowserComponent& owner;
|
||||
|
||||
static String getStringFromVariant (VARIANT* v)
|
||||
{
|
||||
return (v->vt & VT_BYREF) != 0 ? *v->pbstrVal
|
||||
: v->bstrVal;
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EventHandler)
|
||||
};
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
WebBrowserComponent::WebBrowserComponent (const bool unloadPageWhenBrowserIsHidden_)
|
||||
: browser (new Pimpl()),
|
||||
blankPageShown (false),
|
||||
unloadPageWhenBrowserIsHidden (unloadPageWhenBrowserIsHidden_)
|
||||
{
|
||||
setOpaque (true);
|
||||
addAndMakeVisible (browser.get());
|
||||
}
|
||||
|
||||
WebBrowserComponent::~WebBrowserComponent()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void WebBrowserComponent::goToURL (const String& url,
|
||||
const StringArray* headers,
|
||||
const MemoryBlock* postData)
|
||||
{
|
||||
lastURL = url;
|
||||
|
||||
if (headers != nullptr)
|
||||
lastHeaders = *headers;
|
||||
else
|
||||
lastHeaders.clear();
|
||||
|
||||
if (postData != nullptr)
|
||||
lastPostData = *postData;
|
||||
else
|
||||
lastPostData.reset();
|
||||
|
||||
blankPageShown = false;
|
||||
|
||||
if (browser->browser == nullptr)
|
||||
checkWindowAssociation();
|
||||
|
||||
browser->goToURL (url, headers, postData);
|
||||
}
|
||||
|
||||
void WebBrowserComponent::stop()
|
||||
{
|
||||
if (browser->browser != nullptr)
|
||||
browser->browser->Stop();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::goBack()
|
||||
{
|
||||
lastURL.clear();
|
||||
blankPageShown = false;
|
||||
|
||||
if (browser->browser != nullptr)
|
||||
browser->browser->GoBack();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::goForward()
|
||||
{
|
||||
lastURL.clear();
|
||||
|
||||
if (browser->browser != nullptr)
|
||||
browser->browser->GoForward();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::refresh()
|
||||
{
|
||||
if (browser->browser != nullptr)
|
||||
browser->browser->Refresh();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void WebBrowserComponent::paint (Graphics& g)
|
||||
{
|
||||
if (browser->browser == nullptr)
|
||||
{
|
||||
g.fillAll (Colours::white);
|
||||
checkWindowAssociation();
|
||||
}
|
||||
}
|
||||
|
||||
void WebBrowserComponent::checkWindowAssociation()
|
||||
{
|
||||
if (isShowing())
|
||||
{
|
||||
if (browser->browser == nullptr && getPeer() != nullptr)
|
||||
{
|
||||
browser->createBrowser();
|
||||
reloadLastURL();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (blankPageShown)
|
||||
goBack();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (browser != nullptr && unloadPageWhenBrowserIsHidden && ! blankPageShown)
|
||||
{
|
||||
// when the component becomes invisible, some stuff like flash
|
||||
// carries on playing audio, so we need to force it onto a blank
|
||||
// page to avoid this..
|
||||
|
||||
blankPageShown = true;
|
||||
browser->goToURL ("about:blank", 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebBrowserComponent::reloadLastURL()
|
||||
{
|
||||
if (lastURL.isNotEmpty())
|
||||
{
|
||||
goToURL (lastURL, &lastHeaders, &lastPostData);
|
||||
lastURL.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void WebBrowserComponent::parentHierarchyChanged()
|
||||
{
|
||||
checkWindowAssociation();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::resized()
|
||||
{
|
||||
browser->setSize (getWidth(), getHeight());
|
||||
}
|
||||
|
||||
void WebBrowserComponent::visibilityChanged()
|
||||
{
|
||||
checkWindowAssociation();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::focusGained (FocusChangeType)
|
||||
{
|
||||
auto iidOleObject = __uuidof (IOleObject);
|
||||
auto iidOleWindow = __uuidof (IOleWindow);
|
||||
|
||||
if (auto oleObject = (IOleObject*) browser->queryInterface (&iidOleObject))
|
||||
{
|
||||
if (auto oleWindow = (IOleWindow*) browser->queryInterface (&iidOleWindow))
|
||||
{
|
||||
IOleClientSite* oleClientSite = nullptr;
|
||||
|
||||
if (SUCCEEDED (oleObject->GetClientSite (&oleClientSite)))
|
||||
{
|
||||
HWND hwnd;
|
||||
oleWindow->GetWindow (&hwnd);
|
||||
oleObject->DoVerb (OLEIVERB_UIACTIVATE, nullptr, oleClientSite, 0, hwnd, nullptr);
|
||||
oleClientSite->Release();
|
||||
}
|
||||
|
||||
oleWindow->Release();
|
||||
}
|
||||
|
||||
oleObject->Release();
|
||||
}
|
||||
}
|
||||
|
||||
void WebBrowserComponent::clearCookies()
|
||||
{
|
||||
HeapBlock<::INTERNET_CACHE_ENTRY_INFOA> entry;
|
||||
::DWORD entrySize = sizeof (::INTERNET_CACHE_ENTRY_INFOA);
|
||||
::HANDLE urlCacheHandle = ::FindFirstUrlCacheEntryA ("cookie:", entry.getData(), &entrySize);
|
||||
|
||||
if (urlCacheHandle == nullptr && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
|
||||
{
|
||||
entry.realloc (1, entrySize);
|
||||
urlCacheHandle = ::FindFirstUrlCacheEntryA ("cookie:", entry.getData(), &entrySize);
|
||||
}
|
||||
|
||||
if (urlCacheHandle != nullptr)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
::DeleteUrlCacheEntryA (entry.getData()->lpszSourceUrlName);
|
||||
|
||||
if (::FindNextUrlCacheEntryA (urlCacheHandle, entry.getData(), &entrySize) == 0)
|
||||
{
|
||||
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
|
||||
{
|
||||
entry.realloc (1, entrySize);
|
||||
|
||||
if (::FindNextUrlCacheEntryA (urlCacheHandle, entry.getData(), &entrySize) != 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
FindCloseUrlCache (urlCacheHandle);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
Reference in New Issue
Block a user