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:
Alex Birch
2018-06-17 13:34:53 +01:00
parent a2be47c887
commit dff4d13a1d
1563 changed files with 601601 additions and 3466 deletions

View 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

File diff suppressed because it is too large Load Diff

View File

@ -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

View 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

View 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

View 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

View File

@ -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

View 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

View 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

View File

@ -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

View 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

View 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

View 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

View 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

View 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

View 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

View 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