git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce
subrepo: subdir: "deps/juce" merged: "b13f9084e" upstream: origin: "https://github.com/essej/JUCE.git" branch: "sono6good" commit: "b13f9084e" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596"
This commit is contained in:
132
deps/juce/modules/juce_gui_extra/native/java/app/com/rmsl/juce/JuceWebView.java
vendored
Normal file
132
deps/juce/modules/juce_gui_extra/native/java/app/com/rmsl/juce/JuceWebView.java
vendored
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
package com.rmsl.juce;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.http.SslError;
|
||||
import android.os.Message;
|
||||
import android.webkit.WebResourceResponse;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.webkit.SslErrorHandler;
|
||||
import android.webkit.WebChromeClient;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
public class JuceWebView
|
||||
{
|
||||
static public class Client extends WebViewClient
|
||||
{
|
||||
public Client (long hostToUse)
|
||||
{
|
||||
host = hostToUse;
|
||||
}
|
||||
|
||||
public void hostDeleted ()
|
||||
{
|
||||
synchronized (hostLock)
|
||||
{
|
||||
host = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void onPageFinished (WebView view, String url)
|
||||
{
|
||||
if (host == 0)
|
||||
return;
|
||||
|
||||
webViewPageLoadFinished (host, view, url);
|
||||
}
|
||||
|
||||
public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error)
|
||||
{
|
||||
if (host == 0)
|
||||
return;
|
||||
|
||||
webViewReceivedSslError (host, view, handler, error);
|
||||
}
|
||||
|
||||
public void onPageStarted (WebView view, String url, Bitmap favicon)
|
||||
{
|
||||
if (host != 0)
|
||||
webViewPageLoadStarted (host, view, url);
|
||||
}
|
||||
|
||||
public WebResourceResponse shouldInterceptRequest (WebView view, String url)
|
||||
{
|
||||
synchronized (hostLock)
|
||||
{
|
||||
if (host != 0)
|
||||
{
|
||||
boolean shouldLoad = webViewPageLoadStarted (host, view, url);
|
||||
|
||||
if (shouldLoad)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return new WebResourceResponse ("text/html", null, null);
|
||||
}
|
||||
|
||||
private native boolean webViewPageLoadStarted (long host, WebView view, String url);
|
||||
|
||||
private native void webViewPageLoadFinished (long host, WebView view, String url);
|
||||
|
||||
private native void webViewReceivedSslError (long host, WebView view, SslErrorHandler handler, SslError error);
|
||||
|
||||
private long host;
|
||||
private final Object hostLock = new Object ();
|
||||
}
|
||||
|
||||
static public class ChromeClient extends WebChromeClient
|
||||
{
|
||||
public ChromeClient (long hostToUse)
|
||||
{
|
||||
host = hostToUse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCloseWindow (WebView window)
|
||||
{
|
||||
webViewCloseWindowRequest (host, window);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateWindow (WebView view, boolean isDialog,
|
||||
boolean isUserGesture, Message resultMsg)
|
||||
{
|
||||
webViewCreateWindowRequest (host, view);
|
||||
return false;
|
||||
}
|
||||
|
||||
private native void webViewCloseWindowRequest (long host, WebView view);
|
||||
|
||||
private native void webViewCreateWindowRequest (long host, WebView view);
|
||||
|
||||
private long host;
|
||||
private final Object hostLock = new Object ();
|
||||
}
|
||||
}
|
132
deps/juce/modules/juce_gui_extra/native/java/app/com/rmsl/juce/JuceWebView21.java
vendored
Normal file
132
deps/juce/modules/juce_gui_extra/native/java/app/com/rmsl/juce/JuceWebView21.java
vendored
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
package com.rmsl.juce;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.http.SslError;
|
||||
import android.os.Message;
|
||||
import android.webkit.WebResourceResponse;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.webkit.SslErrorHandler;
|
||||
import android.webkit.WebChromeClient;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
public class JuceWebView21
|
||||
{
|
||||
static public class Client extends WebViewClient
|
||||
{
|
||||
public Client (long hostToUse)
|
||||
{
|
||||
host = hostToUse;
|
||||
}
|
||||
|
||||
public void hostDeleted ()
|
||||
{
|
||||
synchronized (hostLock)
|
||||
{
|
||||
host = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void onPageFinished (WebView view, String url)
|
||||
{
|
||||
if (host == 0)
|
||||
return;
|
||||
|
||||
webViewPageLoadFinished (host, view, url);
|
||||
}
|
||||
|
||||
public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error)
|
||||
{
|
||||
if (host == 0)
|
||||
return;
|
||||
|
||||
webViewReceivedSslError (host, view, handler, error);
|
||||
}
|
||||
|
||||
public void onPageStarted (WebView view, String url, Bitmap favicon)
|
||||
{
|
||||
if (host != 0)
|
||||
webViewPageLoadStarted (host, view, url);
|
||||
}
|
||||
|
||||
public WebResourceResponse shouldInterceptRequest (WebView view, String url)
|
||||
{
|
||||
synchronized (hostLock)
|
||||
{
|
||||
if (host != 0)
|
||||
{
|
||||
boolean shouldLoad = webViewPageLoadStarted (host, view, url);
|
||||
|
||||
if (shouldLoad)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return new WebResourceResponse ("text/html", null, null);
|
||||
}
|
||||
|
||||
private native boolean webViewPageLoadStarted (long host, WebView view, String url);
|
||||
|
||||
private native void webViewPageLoadFinished (long host, WebView view, String url);
|
||||
|
||||
private native void webViewReceivedSslError (long host, WebView view, SslErrorHandler handler, SslError error);
|
||||
|
||||
private long host;
|
||||
private final Object hostLock = new Object ();
|
||||
}
|
||||
|
||||
static public class ChromeClient extends WebChromeClient
|
||||
{
|
||||
public ChromeClient (long hostToUse)
|
||||
{
|
||||
host = hostToUse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCloseWindow (WebView window)
|
||||
{
|
||||
webViewCloseWindowRequest (host, window);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateWindow (WebView view, boolean isDialog,
|
||||
boolean isUserGesture, Message resultMsg)
|
||||
{
|
||||
webViewCreateWindowRequest (host, view);
|
||||
return false;
|
||||
}
|
||||
|
||||
private native void webViewCloseWindowRequest (long host, WebView view);
|
||||
|
||||
private native void webViewCreateWindowRequest (long host, WebView view);
|
||||
|
||||
private long host;
|
||||
private final Object hostLock = new Object ();
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
package com.rmsl.juce;
|
||||
|
||||
import com.google.firebase.iid.*;
|
||||
|
||||
public final class JuceFirebaseInstanceIdService extends FirebaseInstanceIdService
|
||||
{
|
||||
private native void firebaseInstanceIdTokenRefreshed (String token);
|
||||
|
||||
@Override
|
||||
public void onTokenRefresh()
|
||||
{
|
||||
String token = FirebaseInstanceId.getInstance().getToken();
|
||||
|
||||
firebaseInstanceIdTokenRefreshed (token);
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
package com.rmsl.juce;
|
||||
|
||||
import com.google.firebase.messaging.*;
|
||||
|
||||
public final class JuceFirebaseMessagingService extends FirebaseMessagingService
|
||||
{
|
||||
private native void firebaseRemoteMessageReceived (RemoteMessage message);
|
||||
private native void firebaseRemoteMessagesDeleted();
|
||||
private native void firebaseRemoteMessageSent (String messageId);
|
||||
private native void firebaseRemoteMessageSendError (String messageId, String error);
|
||||
|
||||
@Override
|
||||
public void onMessageReceived (RemoteMessage message)
|
||||
{
|
||||
firebaseRemoteMessageReceived (message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeletedMessages()
|
||||
{
|
||||
firebaseRemoteMessagesDeleted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessageSent (String messageId)
|
||||
{
|
||||
firebaseRemoteMessageSent (messageId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendError (String messageId, Exception e)
|
||||
{
|
||||
firebaseRemoteMessageSendError (messageId, e.toString());
|
||||
}
|
||||
}
|
176
deps/juce/modules/juce_gui_extra/native/juce_AndroidViewComponent.cpp
vendored
Normal file
176
deps/juce/modules/juce_gui_extra/native/juce_AndroidViewComponent.cpp
vendored
Normal file
@ -0,0 +1,176 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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 (const LocalRef<jobject>& v, Component& comp)
|
||||
: ComponentMovementWatcher (&comp),
|
||||
view (v),
|
||||
owner (comp)
|
||||
{
|
||||
if (owner.isShowing())
|
||||
componentPeerChanged();
|
||||
}
|
||||
|
||||
~Pimpl() override
|
||||
{
|
||||
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().getPrimaryDisplay()->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);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
env->CallVoidMethod (peerView, AndroidViewGroup.addView, view.get());
|
||||
componentMovedOrResized (false, false);
|
||||
}
|
||||
}
|
||||
|
||||
void removeFromParent()
|
||||
{
|
||||
auto* env = getEnv();
|
||||
auto parentView = env->CallObjectMethod (view, AndroidView.getParent);
|
||||
|
||||
if (parentView != nullptr)
|
||||
{
|
||||
// Assuming a parent is always of ViewGroup type
|
||||
env->CallVoidMethod (parentView, AndroidViewGroup.removeView, view.get());
|
||||
}
|
||||
}
|
||||
|
||||
Component& owner;
|
||||
ComponentPeer* currentPeer = nullptr;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
AndroidViewComponent::AndroidViewComponent()
|
||||
{
|
||||
}
|
||||
|
||||
AndroidViewComponent::~AndroidViewComponent() {}
|
||||
|
||||
void AndroidViewComponent::setView (void* view)
|
||||
{
|
||||
if (view != getView())
|
||||
{
|
||||
pimpl.reset();
|
||||
|
||||
if (view != nullptr)
|
||||
{
|
||||
// explicitly create a new local ref here so that we don't
|
||||
// delete the users pointer
|
||||
auto* env = getEnv();
|
||||
auto localref = LocalRef<jobject>(env->NewLocalRef((jobject) view));
|
||||
|
||||
pimpl.reset (new Pimpl (localref, *this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
1648
deps/juce/modules/juce_gui_extra/native/juce_android_PushNotifications.cpp
vendored
Normal file
1648
deps/juce/modules/juce_gui_extra/native/juce_android_PushNotifications.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
725
deps/juce/modules/juce_gui_extra/native/juce_android_WebBrowserComponent.cpp
vendored
Normal file
725
deps/juce/modules/juce_gui_extra/native/juce_android_WebBrowserComponent.cpp
vendored
Normal file
@ -0,0 +1,725 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
// This byte-code is generated from native/java/com/rmsl/juce/JuceWebView.java with min sdk version 16
|
||||
// See juce_core/native/java/README.txt on how to generate this byte-code.
|
||||
static const unsigned char JuceWebView16ByteCode[] =
|
||||
{31,139,8,8,150,114,161,94,0,3,74,117,99,101,87,101,98,86,105,101,119,49,54,66,121,116,101,67,111,100,101,46,100,101,120,0,125,
|
||||
150,93,108,20,85,20,199,207,124,236,78,119,218,110,183,5,74,191,40,109,69,168,72,89,176,162,165,11,88,40,159,101,81,161,88,226,
|
||||
106,34,211,221,107,59,101,118,102,153,153,109,27,67,16,161,137,134,240,96,4,222,72,140,9,18,35,62,18,195,131,15,4,53,250,226,155,
|
||||
209,23,30,212,4,195,131,15,198,24,98,20,19,255,119,238,221,101,129,226,110,126,123,206,61,231,220,123,207,189,247,236,204,
|
||||
45,176,121,115,195,224,38,242,254,253,180,235,204,157,47,183,223,188,240,115,199,231,119,79,174,251,240,23,243,246,209,206,219,
|
||||
79,221,168,39,42,17,209,252,196,179,45,36,63,247,76,162,17,18,246,165,92,42,68,141,144,55,32,117,200,215,85,162,37,144,39,32,
|
||||
53,30,131,159,108,29,81,8,217,16,71,27,244,129,181,96,0,188,0,118,130,55,192,9,240,1,184,6,126,0,247,64,171,65,244,28,56,10,22,
|
||||
192,71,224,107,112,27,212,97,220,149,96,16,236,1,99,224,69,48,14,94,5,71,65,1,216,192,3,62,152,7,111,131,115,224,60,184,4,62,
|
||||
6,87,193,117,112,19,124,7,126,4,191,130,223,192,63,160,62,65,212,9,214,130,109,96,31,176,64,17,204,131,147,224,12,56,11,222,7,87,
|
||||
193,55,224,39,240,23,104,54,197,126,96,73,132,212,9,67,18,204,4,51,97,155,169,129,196,62,38,65,19,72,129,102,192,55,126,137,
|
||||
220,235,101,160,21,44,7,43,65,76,142,119,57,38,108,149,67,106,147,250,103,176,183,75,253,26,244,78,169,127,1,189,67,234,223,66,
|
||||
239,146,250,247,208,187,165,126,11,250,10,169,95,174,177,223,169,209,255,132,222,35,243,227,227,244,74,157,39,197,215,182,58,
|
||||
90,99,138,250,229,58,87,71,82,180,99,164,144,8,53,35,89,39,219,9,82,165,140,211,64,36,27,104,125,36,53,26,150,50,19,141,35,226,
|
||||
76,244,91,19,201,58,74,71,50,65,27,34,105,208,70,57,239,96,36,99,180,37,146,245,180,53,146,58,109,139,246,94,204,155,170,206,
|
||||
79,145,22,147,123,201,107,58,68,227,138,72,51,26,79,145,231,87,241,47,192,255,149,244,215,75,127,170,198,127,1,254,63,164,159,
|
||||
103,189,0,253,172,121,95,63,111,138,62,151,76,30,175,69,122,187,41,234,161,148,226,190,62,140,87,74,241,61,127,45,165,80,174,
|
||||
69,212,137,142,17,248,248,171,77,81,7,227,56,140,210,72,156,212,141,73,172,62,22,249,6,76,81,111,194,103,192,215,18,213,87,101,
|
||||
158,231,171,243,168,15,205,163,97,30,53,154,71,156,149,66,59,77,81,167,135,183,107,180,66,105,69,250,185,29,42,117,43,73,140,
|
||||
208,173,172,145,245,168,224,155,192,156,90,212,62,96,138,122,30,31,81,137,247,192,153,168,155,225,75,70,150,210,68,146,244,131,
|
||||
253,127,243,253,212,163,248,9,83,172,173,54,126,8,163,137,232,38,68,39,177,199,122,180,222,163,166,168,183,241,210,35,99,251,
|
||||
42,25,199,141,5,227,162,113,101,54,206,207,182,255,46,63,27,158,147,74,51,232,247,4,175,97,229,240,105,172,68,29,95,64,127,12,
|
||||
184,81,211,227,67,90,3,241,118,41,215,68,123,47,154,52,132,185,186,213,102,165,91,237,83,227,212,161,109,194,46,42,244,76,179,
|
||||
209,219,127,183,17,214,53,242,185,183,4,115,244,70,187,196,191,61,82,226,89,99,10,191,216,213,100,244,28,172,253,156,122,168,125,
|
||||
238,161,54,175,17,3,79,2,165,166,205,45,122,85,170,164,73,189,89,214,30,63,111,173,234,173,232,98,12,174,55,227,219,36,107,
|
||||
211,64,230,75,96,141,111,177,93,59,220,70,13,163,211,190,87,100,163,142,205,220,144,226,82,42,99,148,26,43,231,217,17,54,57,97,
|
||||
179,185,245,51,214,172,69,90,54,155,165,246,172,229,22,124,207,46,164,167,124,171,52,109,231,131,244,14,59,44,90,165,12,117,
|
||||
86,93,46,11,211,211,97,88,74,143,7,206,46,223,247,252,12,45,173,58,189,32,125,128,5,129,53,197,50,212,83,181,206,177,201,99,118,
|
||||
88,237,176,23,118,135,249,139,68,32,165,218,148,51,180,106,145,136,67,44,240,202,126,158,65,150,60,55,192,76,109,139,68,241,
|
||||
165,101,168,251,49,158,202,248,253,217,188,87,76,251,197,192,73,207,96,75,210,53,251,178,234,193,76,250,254,47,82,198,116,62,62,
|
||||
134,15,80,176,156,89,251,88,218,114,93,47,180,66,219,115,211,187,220,188,227,5,182,59,53,234,88,65,192,211,125,52,102,159,
|
||||
235,50,95,250,123,23,241,31,96,197,73,25,192,16,178,44,203,207,51,109,123,232,88,42,135,227,161,207,172,98,134,90,132,217,177,
|
||||
220,169,244,75,147,51,44,31,62,104,67,28,210,200,144,50,65,234,196,24,105,19,99,89,210,241,147,165,24,255,205,194,154,133,53,
|
||||
203,173,188,169,228,72,207,69,238,92,54,151,203,82,189,149,207,227,224,119,59,214,84,64,49,198,143,153,140,55,173,89,59,239,185,
|
||||
100,76,139,19,39,125,218,11,66,170,231,191,59,153,195,66,86,160,58,222,200,122,249,99,148,224,218,97,239,149,128,81,157,29,
|
||||
236,180,45,199,155,162,70,59,128,193,223,195,130,176,236,51,210,93,171,200,168,209,115,71,177,111,236,136,237,22,188,57,74,162,
|
||||
137,85,134,53,237,151,81,129,187,241,39,8,166,49,69,163,104,143,135,150,207,103,108,241,220,67,44,207,236,89,86,168,84,36,37,
|
||||
124,22,148,157,240,64,48,69,173,193,180,87,118,10,251,220,144,161,200,74,225,33,118,188,140,217,201,20,246,172,103,21,40,17,178,
|
||||
121,254,47,40,58,164,135,211,118,64,90,217,119,40,54,107,57,101,228,56,139,243,166,246,185,74,165,85,19,173,140,212,81,113,
|
||||
213,36,93,241,45,151,62,158,48,159,170,186,136,214,135,28,149,213,84,58,60,178,164,248,156,216,13,71,89,107,36,213,229,25,117,
|
||||
102,110,128,2,101,196,72,230,232,45,125,248,233,117,131,92,27,136,188,155,51,234,94,120,7,201,72,110,221,223,221,69,25,117,120,
|
||||
200,72,158,237,162,253,218,240,208,147,70,242,221,28,141,106,195,171,87,69,182,131,220,185,98,235,123,51,26,109,90,58,208,27,163,
|
||||
206,149,231,113,13,48,146,164,54,40,67,109,245,106,163,218,167,39,214,45,87,42,138,170,38,149,161,46,181,45,209,134,23,189,
|
||||
166,146,170,180,104,239,156,210,47,24,218,105,188,167,128,174,220,48,20,229,22,94,104,122,76,133,183,14,222,123,70,92,122,57,9,
|
||||
229,147,58,68,128,115,9,69,185,14,126,79,240,231,99,51,34,111,153,149,247,179,82,35,71,72,220,107,249,51,179,114,183,229,207,
|
||||
203,218,251,109,229,142,27,163,251,247,220,56,221,191,235,106,41,161,243,231,188,210,35,238,11,23,160,199,123,164,29,29,149,148,
|
||||
176,243,123,149,218,35,230,229,119,99,77,198,243,119,191,222,35,239,22,220,32,251,70,119,144,148,200,149,223,195,255,3,213,
|
||||
111,243,3,192,11,0,0,0,0};
|
||||
|
||||
//==============================================================================
|
||||
// This byte-code is generated from native/javacore/app/com/rmsl/juce/JuceWebView21.java with min sdk version 21
|
||||
// See juce_core/native/java/README.txt on how to generate this byte-code.
|
||||
static const unsigned char JuceWebView21ByteCode[] =
|
||||
{31,139,8,8,45,103,161,94,0,3,74,117,99,101,87,101,98,86,105,101,119,50,49,46,100,101,120,0,141,151,93,140,27,87,21,199,207,
|
||||
204,216,30,219,99,59,182,55,251,145,143,221,110,210,173,178,105,154,186,155,164,52,169,211,106,241,38,219,221,48,41,52,155,108,
|
||||
138,43,85,154,181,47,235,73,188,51,206,204,120,119,65,162,132,80,148,138,34,148,168,20,181,125,129,135,16,129,4,18,168,125,136,
|
||||
42,224,133,207,74,60,160,138,135,208,71,210,151,162,128,242,148,86,136,7,254,247,99,28,111,18,34,108,253,124,206,61,231,220,
|
||||
143,115,239,153,241,76,147,173,103,159,216,255,36,253,228,121,251,31,197,127,189,252,251,226,31,94,89,242,217,87,31,123,227,
|
||||
151,175,95,184,28,222,168,188,152,39,234,16,209,250,226,129,50,169,207,247,115,68,243,36,237,67,188,173,17,149,32,111,66,38,32,
|
||||
175,233,68,195,144,215,33,13,200,75,248,105,101,136,110,65,222,74,17,125,6,82,38,81,1,148,192,35,96,18,236,5,115,224,37,176,14,
|
||||
190,7,126,1,62,4,159,130,209,52,209,83,224,52,248,22,248,41,248,19,184,9,114,24,191,2,102,65,29,120,224,28,232,130,175,129,
|
||||
243,224,34,120,29,92,2,63,0,111,131,31,130,171,224,93,240,62,248,0,124,8,62,2,55,192,39,224,54,160,44,145,5,6,193,4,120,20,28,
|
||||
2,243,224,203,160,5,190,14,46,131,119,192,143,193,175,193,7,224,175,224,35,240,49,248,4,220,2,183,65,201,66,206,96,22,188,12,
|
||||
214,193,101,75,238,25,210,37,164,69,106,106,130,153,176,237,132,227,160,2,216,4,138,36,247,157,31,204,0,216,12,6,213,153,240,
|
||||
253,31,1,91,192,86,176,19,36,129,174,206,48,165,198,111,165,164,125,64,217,183,170,113,248,103,155,210,59,136,217,174,244,117,
|
||||
232,99,74,63,223,167,127,23,250,168,210,223,130,254,144,210,175,64,223,161,244,159,245,233,215,160,143,43,253,119,125,246,63,
|
||||
247,233,215,161,63,172,114,226,99,78,40,253,227,148,220,143,61,98,95,202,180,87,237,205,30,33,101,91,199,247,89,145,167,33,114,
|
||||
225,251,185,91,228,92,16,237,172,178,91,162,98,185,204,208,62,33,139,180,95,200,36,213,148,156,17,227,202,184,28,250,61,38,100,
|
||||
142,14,8,153,167,39,133,180,232,115,66,102,233,41,33,53,122,90,200,52,29,17,114,19,29,21,210,164,89,33,83,244,156,56,79,185,
|
||||
142,114,111,61,132,158,242,124,248,135,207,122,16,141,253,57,82,243,75,127,182,207,63,7,255,11,202,159,87,254,114,159,255,69,
|
||||
248,47,42,63,63,255,18,244,225,220,29,125,60,39,251,236,206,241,120,67,232,111,91,114,172,78,81,67,123,39,198,235,20,121,93,189,
|
||||
132,118,189,44,107,48,129,17,248,248,87,45,185,222,5,28,104,103,58,77,250,84,1,89,38,133,239,231,150,220,123,233,203,192,87,22,
|
||||
181,27,207,115,173,55,79,226,174,121,12,204,163,139,121,146,34,82,163,63,90,50,255,147,159,55,104,84,27,194,242,235,53,157,
|
||||
198,180,2,70,24,211,118,137,58,78,17,95,111,6,115,26,162,253,23,75,94,43,11,211,58,241,30,83,72,251,16,124,5,97,233,44,150,40,
|
||||
241,194,228,191,121,93,36,68,252,223,44,153,91,127,252,65,140,38,163,203,136,46,136,154,225,249,222,176,228,117,179,208,185,
|
||||
103,236,64,39,243,156,249,170,249,166,121,117,53,53,128,21,77,222,166,94,191,127,254,159,253,54,247,250,241,92,116,250,212,146,
|
||||
53,94,214,78,94,192,14,232,11,175,162,63,6,156,50,18,169,131,198,102,226,237,160,168,163,54,114,134,87,228,247,145,156,209,
|
||||
89,40,211,220,155,89,58,136,185,199,244,146,54,166,239,212,211,180,213,56,140,211,48,104,95,201,220,49,121,59,15,235,46,117,
|
||||
239,222,134,57,119,139,221,230,223,73,37,177,239,57,233,151,167,83,16,247,242,254,207,127,238,106,167,19,27,219,188,214,248,169,
|
||||
104,125,109,110,73,40,153,196,106,99,221,196,105,24,74,31,80,245,204,239,131,70,47,50,214,77,113,15,211,85,164,161,164,166,252,
|
||||
3,248,150,212,53,144,193,157,99,144,239,255,97,215,115,163,103,41,55,211,10,252,21,54,211,118,153,23,81,74,73,237,24,149,143,
|
||||
117,27,236,52,91,90,116,217,218,190,169,199,207,56,171,14,105,54,25,182,109,211,22,219,241,154,129,239,54,43,203,129,211,105,
|
||||
185,141,176,82,115,163,21,167,83,165,82,207,229,177,168,114,42,112,171,180,109,131,169,21,69,157,202,66,216,62,26,4,126,80,165,
|
||||
205,61,167,31,86,142,179,48,116,150,89,149,198,123,214,53,182,116,214,141,122,29,230,96,111,179,224,62,17,88,106,127,42,85,122,
|
||||
248,62,17,39,88,232,119,131,6,59,193,206,117,89,136,160,137,7,6,133,29,223,11,177,156,145,251,68,241,125,169,210,216,255,240,
|
||||
196,139,120,212,110,248,43,149,96,37,108,87,206,96,63,43,27,54,117,98,227,130,39,30,28,171,162,70,31,20,85,165,157,118,211,105,
|
||||
175,186,103,43,142,231,249,145,19,185,190,87,57,234,53,218,126,232,122,203,51,109,39,12,249,162,239,141,153,247,60,22,40,255,
|
||||
142,251,248,143,179,149,37,21,192,16,50,104,243,130,168,184,62,58,118,186,209,66,20,48,103,165,74,101,105,110,59,222,114,229,
|
||||
139,75,103,88,35,218,104,67,28,150,81,37,109,145,244,197,99,100,44,30,179,41,129,31,155,146,252,215,134,21,37,182,104,115,43,
|
||||
111,106,117,74,212,133,187,110,215,235,54,89,78,163,129,26,153,109,59,203,33,37,25,175,8,202,11,17,31,22,153,95,113,86,221,134,
|
||||
239,81,106,153,69,167,130,54,153,45,89,51,148,104,249,97,68,22,255,61,194,218,44,98,77,74,243,134,237,55,206,82,134,107,39,
|
||||
253,83,24,33,237,134,71,92,167,237,47,83,222,13,97,8,158,67,169,116,3,70,9,207,89,97,148,247,189,25,108,39,59,237,122,77,127,
|
||||
141,10,104,34,249,168,175,253,37,212,240,44,46,175,176,133,41,242,178,189,16,57,1,159,113,192,247,78,176,6,115,87,89,115,14,87,
|
||||
130,40,106,42,223,49,198,133,78,102,32,107,148,50,1,11,187,237,232,120,184,76,67,97,203,239,182,155,243,94,196,80,159,157,72,
|
||||
149,49,101,165,221,246,157,38,101,34,182,206,175,178,149,54,37,162,150,27,82,58,242,229,182,147,209,197,118,36,87,157,118,23,
|
||||
185,172,162,96,104,203,90,92,174,189,132,226,49,183,198,174,190,228,98,223,176,242,241,196,248,164,189,100,135,238,114,196,89,
|
||||
143,40,251,189,169,15,223,229,233,229,159,90,147,251,185,166,85,204,130,62,92,213,207,172,237,165,87,180,121,179,80,167,111,
|
||||
107,137,218,19,83,79,115,245,113,225,254,45,85,245,223,124,3,1,7,200,44,60,243,133,177,237,116,88,175,77,155,133,239,108,39,
|
||||
219,168,77,239,54,11,23,235,116,194,168,29,154,20,182,35,70,109,207,46,161,213,245,218,33,115,244,153,215,254,110,208,225,161,
|
||||
189,59,146,180,237,161,55,112,223,55,11,164,23,180,233,145,188,190,73,127,36,145,153,26,214,98,69,215,139,218,244,118,125,196,
|
||||
26,201,145,110,224,233,74,43,39,191,121,62,113,41,109,92,208,73,3,41,237,87,105,77,187,142,127,255,100,74,135,55,11,239,103,
|
||||
105,83,121,99,44,237,74,6,81,224,181,172,166,189,7,110,130,43,22,191,201,15,162,199,143,248,255,123,81,253,71,104,125,50,126,
|
||||
223,224,247,250,248,157,131,223,227,251,223,59,226,119,15,254,63,30,191,127,164,232,206,59,136,81,148,58,255,239,210,198,229,
|
||||
179,22,30,59,40,53,46,237,252,57,75,43,202,231,17,254,92,172,143,203,121,249,59,139,161,226,249,115,81,98,92,206,197,159,157,72,
|
||||
245,221,175,22,206,215,202,223,143,254,11,250,146,74,12,88,13,0,0,0,0};
|
||||
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (constructor, "<init>", "(Landroid/content/Context;)V") \
|
||||
METHOD (getSettings, "getSettings", "()Landroid/webkit/WebSettings;") \
|
||||
METHOD (canGoBack, "canGoBack", "()Z") \
|
||||
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, CALLBACK) \
|
||||
METHOD (constructor, "<init>", "()V")
|
||||
|
||||
DECLARE_JNI_CLASS (AndroidWebChromeClient, "android/webkit/WebChromeClient")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (constructor, "<init>", "()V")
|
||||
|
||||
DECLARE_JNI_CLASS (AndroidWebViewClient, "android/webkit/WebViewClient")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
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, CALLBACK) \
|
||||
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, CALLBACK) \
|
||||
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, CALLBACK) \
|
||||
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)
|
||||
: owner (o)
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
setView (env->NewObject (AndroidWebView, AndroidWebView.constructor, getMainActivity().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,
|
||||
reinterpret_cast<jlong> (this))));
|
||||
env->CallVoidMethod ((jobject) getView(), AndroidWebView.setWebChromeClient, juceWebChromeClient.get());
|
||||
|
||||
auto sdkVersion = getAndroidSDKVersion();
|
||||
|
||||
if (sdkVersion >= 21)
|
||||
juceWebViewClient = GlobalRef (LocalRef<jobject> (env->NewObject (JuceWebViewClient21, JuceWebViewClient21.constructor,
|
||||
reinterpret_cast<jlong> (this))));
|
||||
else
|
||||
juceWebViewClient = GlobalRef (LocalRef<jobject> (env->NewObject (JuceWebViewClient16, JuceWebViewClient16.constructor,
|
||||
reinterpret_cast<jlong> (this))));
|
||||
|
||||
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, getAndroidSDKVersion() >= 21 ? JuceWebViewClient21.hostDeleted
|
||||
: JuceWebViewClient16.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;
|
||||
|
||||
auto* env = getEnv();
|
||||
auto view = (jobject) getView();
|
||||
|
||||
if (env->CallBooleanMethod (view, AndroidWebView.canGoBack))
|
||||
env->CallVoidMethod (view, AndroidWebView.goBack);
|
||||
else
|
||||
owner.reloadLastURL();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
WebBrowserComponent& owner;
|
||||
|
||||
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() override
|
||||
{
|
||||
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;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (threadShouldExit())
|
||||
return;
|
||||
|
||||
char buffer [8192];
|
||||
auto 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;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (constructor, "<init>", "(J)V") \
|
||||
METHOD (hostDeleted, "hostDeleted", "()V") \
|
||||
CALLBACK (webViewReceivedHttpError, "webViewReceivedHttpError", "(JLandroid/webkit/WebView;Landroid/webkit/WebResourceRequest;Landroid/webkit/WebResourceResponse;)V") \
|
||||
CALLBACK (webViewPageLoadStarted, "webViewPageLoadStarted", "(JLandroid/webkit/WebView;Ljava/lang/String;)Z") \
|
||||
CALLBACK (webViewPageLoadFinished, "webViewPageLoadFinished", "(JLandroid/webkit/WebView;Ljava/lang/String;)V") \
|
||||
CALLBACK (webViewReceivedSslError, "webViewReceivedSslError", "(JLandroid/webkit/WebView;Landroid/webkit/SslErrorHandler;Landroid/net/http/SslError;)V") \
|
||||
|
||||
DECLARE_JNI_CLASS_WITH_BYTECODE (JuceWebViewClient21, "com/rmsl/juce/JuceWebView21$Client", 21, JuceWebView21ByteCode, sizeof (JuceWebView21ByteCode))
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (constructor, "<init>", "(J)V") \
|
||||
METHOD (hostDeleted, "hostDeleted", "()V") \
|
||||
CALLBACK (webViewPageLoadStarted, "webViewPageLoadStarted", "(JLandroid/webkit/WebView;Ljava/lang/String;)Z") \
|
||||
CALLBACK (webViewPageLoadFinished, "webViewPageLoadFinished", "(JLandroid/webkit/WebView;Ljava/lang/String;)V") \
|
||||
CALLBACK (webViewReceivedSslError, "webViewReceivedSslError", "(JLandroid/webkit/WebView;Landroid/webkit/SslErrorHandler;Landroid/net/http/SslError;)V") \
|
||||
|
||||
DECLARE_JNI_CLASS_WITH_BYTECODE (JuceWebViewClient16, "com/rmsl/juce/JuceWebView$Client", 16, JuceWebView16ByteCode, sizeof (JuceWebView16ByteCode))
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
static jboolean JNICALL webViewPageLoadStarted (JNIEnv*, jobject /*activity*/, jlong host, jobject /*webView*/, jstring url)
|
||||
{
|
||||
if (auto* myself = reinterpret_cast<WebBrowserComponent::Pimpl*> (host))
|
||||
return myself->handlePageAboutToLoad (juceString (url));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void JNICALL webViewPageLoadFinished (JNIEnv*, jobject /*activity*/, jlong host, jobject /*webView*/, jstring url)
|
||||
{
|
||||
if (auto* myself = reinterpret_cast<WebBrowserComponent::Pimpl*> (host))
|
||||
myself->owner.pageFinishedLoading (juceString (url));
|
||||
}
|
||||
|
||||
static void JNICALL webViewReceivedHttpError (JNIEnv*, jobject /*activity*/, jlong host, jobject /*webView*/, jobject /*request*/, jobject errorResponse)
|
||||
{
|
||||
if (auto* myself = reinterpret_cast<WebBrowserComponent::Pimpl*> (host))
|
||||
myself->webReceivedHttpError (errorResponse);
|
||||
}
|
||||
|
||||
static void JNICALL webViewReceivedSslError (JNIEnv*, jobject /*activity*/, jlong host, jobject /*webView*/, jobject /*sslErrorHandler*/, jobject sslError)
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
if (auto* myself = reinterpret_cast<WebBrowserComponent::Pimpl*> (host))
|
||||
{
|
||||
auto errorString = LocalRef<jstring> ((jstring) env->CallObjectMethod (sslError, SslError.toString));
|
||||
|
||||
myself->owner.pageLoadHadNetworkError (juceString (errorString));
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (constructor, "<init>", "(J)V") \
|
||||
CALLBACK (webViewCloseWindowRequest, "webViewCloseWindowRequest", "(JLandroid/webkit/WebView;)V") \
|
||||
CALLBACK (webViewCreateWindowRequest, "webViewCreateWindowRequest", "(JLandroid/webkit/WebView;)V") \
|
||||
|
||||
DECLARE_JNI_CLASS (JuceWebChromeClient, "com/rmsl/juce/JuceWebView$ChromeClient")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
static void JNICALL webViewCloseWindowRequest (JNIEnv*, jobject /*activity*/, jlong host, jobject /*webView*/)
|
||||
{
|
||||
if (auto* myself = reinterpret_cast<WebBrowserComponent::Pimpl*> (host))
|
||||
myself->owner.windowCloseRequest();
|
||||
}
|
||||
|
||||
static void JNICALL webViewCreateWindowRequest (JNIEnv*, jobject /*activity*/, jlong host, jobject /*webView*/)
|
||||
{
|
||||
if (auto* myself = reinterpret_cast<WebBrowserComponent::Pimpl*> (host))
|
||||
myself->owner.newWindowAttemptingToLoad ({});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void webReceivedHttpError (jobject errorResponse)
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
LocalRef<jclass> responseClass (env->FindClass ("android/webkit/WebResourceResponse"));
|
||||
|
||||
if (responseClass != nullptr)
|
||||
{
|
||||
jmethodID method = env->GetMethodID (responseClass, "getReasonPhrase", "()Ljava/lang/String;");
|
||||
|
||||
if (method != nullptr)
|
||||
{
|
||||
auto errorString = LocalRef<jstring> ((jstring) env->CallObjectMethod (errorResponse, method));
|
||||
|
||||
owner.pageLoadHadNetworkError (juceString (errorString));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Should never get here!
|
||||
jassertfalse;
|
||||
owner.pageLoadHadNetworkError ({});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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),
|
||||
unloadPageWhenHidden (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()
|
||||
{
|
||||
browser->goBack();
|
||||
|
||||
lastURL.clear();
|
||||
blankPageShown = false;
|
||||
}
|
||||
|
||||
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 (unloadPageWhenHidden && ! 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", nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebBrowserComponent::reloadLastURL()
|
||||
{
|
||||
if (lastURL.isNotEmpty())
|
||||
{
|
||||
goToURL (lastURL, &lastHeaders, lastPostData.isEmpty() ? 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));
|
||||
|
||||
jmethodID clearCookiesMethod = nullptr;
|
||||
|
||||
if (getAndroidSDKVersion() >= 21)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
WebBrowserComponent::Pimpl::JuceWebViewClient16_Class WebBrowserComponent::Pimpl::JuceWebViewClient16;
|
||||
WebBrowserComponent::Pimpl::JuceWebViewClient21_Class WebBrowserComponent::Pimpl::JuceWebViewClient21;
|
||||
WebBrowserComponent::Pimpl::JuceWebChromeClient_Class WebBrowserComponent::Pimpl::JuceWebChromeClient;
|
||||
} // namespace juce
|
998
deps/juce/modules/juce_gui_extra/native/juce_ios_PushNotifications.cpp
vendored
Normal file
998
deps/juce/modules/juce_gui_extra/native/juce_ios_PushNotifications.cpp
vendored
Normal file
@ -0,0 +1,998 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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 identifier, 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.get());
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
#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];
|
||||
NSDate* fireDate = n.fireDate;
|
||||
|
||||
notif.triggerIntervalSec = [dateNow timeIntervalSinceDate: 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];
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
if ([appDelegate respondsToSelector: @selector (setPushNotificationsDelegateToUse:)])
|
||||
[appDelegate performSelector: @selector (setPushNotificationsDelegateToUse:) withObject: delegate.get()];
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
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
|
||||
NSUniquePtr<NSObject<UIApplicationDelegate, UNUserNotificationCenterDelegate>> delegate;
|
||||
#else
|
||||
NSUniquePtr<NSObject<UIApplicationDelegate>> 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
|
||||
{
|
||||
deviceToken = [deviceTokenToUse]() -> String
|
||||
{
|
||||
auto length = deviceTokenToUse.length;
|
||||
|
||||
if (auto* buffer = (const unsigned char*) deviceTokenToUse.bytes)
|
||||
{
|
||||
NSMutableString* hexString = [NSMutableString stringWithCapacity: (length * 2)];
|
||||
|
||||
for (NSUInteger i = 0; i < length; ++i)
|
||||
[hexString appendFormat:@"%02x", buffer[i]];
|
||||
|
||||
return nsStringToJuce ([hexString copy]);
|
||||
}
|
||||
|
||||
return {};
|
||||
}();
|
||||
|
||||
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
|
132
deps/juce/modules/juce_gui_extra/native/juce_ios_UIViewComponent.mm
vendored
Normal file
132
deps/juce/modules/juce_gui_extra/native/juce_ios_UIViewComponent.mm
vendored
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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() override
|
||||
{
|
||||
[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
|
151
deps/juce/modules/juce_gui_extra/native/juce_linux_X11_SystemTrayIcon.cpp
vendored
Normal file
151
deps/juce/modules/juce_gui_extra/native/juce_linux_X11_SystemTrayIcon.cpp
vendored
Normal file
@ -0,0 +1,151 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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)
|
||||
{
|
||||
XWindowSystemUtilities::ScopedXLock xLock;
|
||||
|
||||
auto* display = XWindowSystem::getInstance()->getDisplay();
|
||||
|
||||
auto* screen = X11Symbols::getInstance()->xDefaultScreenOfDisplay (display);
|
||||
auto screenNumber = X11Symbols::getInstance()->xScreenNumberOfScreen (screen);
|
||||
|
||||
String screenAtom ("_NET_SYSTEM_TRAY_S");
|
||||
screenAtom << screenNumber;
|
||||
Atom selectionAtom = XWindowSystemUtilities::Atoms::getCreating (display, screenAtom.toUTF8());
|
||||
|
||||
X11Symbols::getInstance()->xGrabServer (display);
|
||||
auto managerWin = X11Symbols::getInstance()->xGetSelectionOwner (display, selectionAtom);
|
||||
|
||||
if (managerWin != None)
|
||||
X11Symbols::getInstance()->xSelectInput (display, managerWin, StructureNotifyMask);
|
||||
|
||||
X11Symbols::getInstance()->xUngrabServer (display);
|
||||
X11Symbols::getInstance()->xFlush (display);
|
||||
|
||||
if (managerWin != None)
|
||||
{
|
||||
XEvent ev = { 0 };
|
||||
ev.xclient.type = ClientMessage;
|
||||
ev.xclient.window = managerWin;
|
||||
ev.xclient.message_type = XWindowSystemUtilities::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;
|
||||
|
||||
X11Symbols::getInstance()->xSendEvent (display, managerWin, False, NoEventMask, &ev);
|
||||
X11Symbols::getInstance()->xSync (display, False);
|
||||
}
|
||||
|
||||
// For older KDE's ...
|
||||
long atomData = 1;
|
||||
Atom trayAtom = XWindowSystemUtilities::Atoms::getCreating (display, "KWM_DOCKWINDOW");
|
||||
X11Symbols::getInstance()->xChangeProperty (display, windowH, trayAtom, trayAtom,
|
||||
32, PropModeReplace, (unsigned char*) &atomData, 1);
|
||||
|
||||
// For more recent KDE's...
|
||||
trayAtom = XWindowSystemUtilities::Atoms::getCreating (display, "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR");
|
||||
X11Symbols::getInstance()->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
|
||||
if (auto* hints = X11Symbols::getInstance()->xAllocSizeHints())
|
||||
{
|
||||
hints->flags = PMinSize;
|
||||
hints->min_width = 22;
|
||||
hints->min_height = 22;
|
||||
X11Symbols::getInstance()->xSetWMNormalHints (display, windowH, hints);
|
||||
X11Symbols::getInstance()->xFree (hints);
|
||||
}
|
||||
}
|
||||
|
||||
Image image;
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
void SystemTrayIconComponent::setIconImage (const Image& colourImage, const Image&)
|
||||
{
|
||||
pimpl.reset();
|
||||
|
||||
if (colourImage.isValid())
|
||||
{
|
||||
if (! isOnDesktop())
|
||||
addToDesktop (0);
|
||||
|
||||
pimpl.reset (new Pimpl (colourImage, (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
|
1024
deps/juce/modules/juce_gui_extra/native/juce_linux_X11_WebBrowserComponent.cpp
vendored
Normal file
1024
deps/juce/modules/juce_gui_extra/native/juce_linux_X11_WebBrowserComponent.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
702
deps/juce/modules/juce_gui_extra/native/juce_linux_XEmbedComponent.cpp
vendored
Normal file
702
deps/juce/modules/juce_gui_extra/native/juce_linux_XEmbedComponent.cpp
vendored
Normal file
@ -0,0 +1,702 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
::Window juce_createKeyProxyWindow (ComponentPeer*);
|
||||
void juce_deleteKeyProxyWindow (::Window);
|
||||
|
||||
//==============================================================================
|
||||
enum
|
||||
{
|
||||
maxXEmbedVersionToSupport = 0
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
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 XEmbedComponent::Pimpl : private ComponentListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
struct SharedKeyWindow : public ReferenceCountedObject
|
||||
{
|
||||
SharedKeyWindow (ComponentPeer* peerToUse)
|
||||
: keyPeer (peerToUse),
|
||||
keyProxy (juce_createKeyProxyWindow (keyPeer))
|
||||
{}
|
||||
|
||||
~SharedKeyWindow()
|
||||
{
|
||||
juce_deleteKeyProxyWindow (keyProxy);
|
||||
|
||||
auto& keyWindows = getKeyWindows();
|
||||
keyWindows.remove (keyPeer);
|
||||
}
|
||||
|
||||
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:
|
||||
//==============================================================================
|
||||
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),
|
||||
infoAtom (XWindowSystem::getInstance()->getAtoms().XembedInfo),
|
||||
messageTypeAtom (XWindowSystem::getInstance()->getAtoms().XembedMsgType),
|
||||
clientInitiated (isClientInitiated),
|
||||
wantsFocus (wantsKeyboardFocus),
|
||||
allowResize (shouldAllowResize)
|
||||
{
|
||||
getWidgets().add (this);
|
||||
|
||||
createHostWindow();
|
||||
|
||||
if (clientInitiated)
|
||||
setClient (x11Window, true);
|
||||
|
||||
owner.setWantsKeyboardFocus (wantsFocus);
|
||||
owner.addComponentListener (this);
|
||||
}
|
||||
|
||||
~Pimpl() override
|
||||
{
|
||||
owner.removeComponentListener (this);
|
||||
setClient (0, true);
|
||||
|
||||
if (host != 0)
|
||||
{
|
||||
auto dpy = getDisplay();
|
||||
|
||||
X11Symbols::getInstance()->xDestroyWindow (dpy, host);
|
||||
X11Symbols::getInstance()->xSync (dpy, false);
|
||||
|
||||
auto mask = NoEventMask | KeyPressMask | KeyReleaseMask
|
||||
| EnterWindowMask | LeaveWindowMask | PointerMotionMask
|
||||
| KeymapStateMask | ExposureMask | StructureNotifyMask
|
||||
| FocusChangeMask;
|
||||
|
||||
XEvent event;
|
||||
while (X11Symbols::getInstance()->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();
|
||||
X11Symbols::getInstance()->xResizeWindow (dpy, client, static_cast<unsigned int> (newBounds.getWidth()),
|
||||
static_cast<unsigned int> (newBounds.getHeight()));
|
||||
}
|
||||
|
||||
auto eventMask = StructureNotifyMask | PropertyChangeMask | FocusChangeMask;
|
||||
|
||||
XWindowAttributes clientAttr;
|
||||
X11Symbols::getInstance()->xGetWindowAttributes (dpy, client, &clientAttr);
|
||||
|
||||
if ((eventMask & clientAttr.your_event_mask) != eventMask)
|
||||
X11Symbols::getInstance()->xSelectInput (dpy, client, clientAttr.your_event_mask | eventMask);
|
||||
|
||||
getXEmbedMappedFlag();
|
||||
|
||||
if (shouldReparent)
|
||||
X11Symbols::getInstance()->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;
|
||||
Atom infoAtom, messageTypeAtom;
|
||||
|
||||
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 (X11Symbols::getInstance()->xGetWindowAttributes (dpy, host, &attr))
|
||||
{
|
||||
Rectangle<int> currentBounds (attr.x, attr.y, attr.width, attr.height);
|
||||
if (currentBounds != newBounds)
|
||||
{
|
||||
X11Symbols::getInstance()->xMoveResizeWindow (dpy, host, newBounds.getX(), newBounds.getY(),
|
||||
static_cast<unsigned int> (newBounds.getWidth()),
|
||||
static_cast<unsigned int> (newBounds.getHeight()));
|
||||
}
|
||||
}
|
||||
|
||||
if (client != 0 && X11Symbols::getInstance()->xGetWindowAttributes (dpy, client, &attr))
|
||||
{
|
||||
Rectangle<int> currentBounds (attr.x, attr.y, attr.width, attr.height);
|
||||
|
||||
if ((currentBounds.getWidth() != newBounds.getWidth()
|
||||
|| currentBounds.getHeight() != newBounds.getHeight()))
|
||||
{
|
||||
X11Symbols::getInstance()->xMoveResizeWindow (dpy, client, 0, 0,
|
||||
static_cast<unsigned int> (newBounds.getWidth()),
|
||||
static_cast<unsigned int> (newBounds.getHeight()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void createHostWindow()
|
||||
{
|
||||
auto dpy = getDisplay();
|
||||
int defaultScreen = X11Symbols::getInstance()->xDefaultScreen (dpy);
|
||||
Window root = X11Symbols::getInstance()->xRootWindow (dpy, defaultScreen);
|
||||
|
||||
XSetWindowAttributes swa;
|
||||
swa.border_pixel = 0;
|
||||
swa.background_pixmap = None;
|
||||
swa.override_redirect = True;
|
||||
swa.event_mask = SubstructureNotifyMask | StructureNotifyMask | FocusChangeMask;
|
||||
|
||||
host = X11Symbols::getInstance()->xCreateWindow (dpy, root, 0, 0, 1, 1, 0, CopyFromParent,
|
||||
InputOutput, CopyFromParent,
|
||||
CWEventMask | CWBorderPixel | CWBackPixmap | CWOverrideRedirect,
|
||||
&swa);
|
||||
}
|
||||
|
||||
void removeClient()
|
||||
{
|
||||
if (client != 0)
|
||||
{
|
||||
auto dpy = getDisplay();
|
||||
X11Symbols::getInstance()->xSelectInput (dpy, client, 0);
|
||||
|
||||
keyWindow = nullptr;
|
||||
|
||||
int defaultScreen = X11Symbols::getInstance()->xDefaultScreen (dpy);
|
||||
Window root = X11Symbols::getInstance()->xRootWindow (dpy, defaultScreen);
|
||||
|
||||
if (hasBeenMapped)
|
||||
{
|
||||
X11Symbols::getInstance()->xUnmapWindow (dpy, client);
|
||||
hasBeenMapped = false;
|
||||
}
|
||||
|
||||
X11Symbols::getInstance()->xReparentWindow (dpy, client, root, 0, 0);
|
||||
client = 0;
|
||||
|
||||
X11Symbols::getInstance()->xSync (dpy, False);
|
||||
}
|
||||
}
|
||||
|
||||
void updateMapping()
|
||||
{
|
||||
if (client != 0)
|
||||
{
|
||||
const bool shouldBeMapped = getXEmbedMappedFlag();
|
||||
|
||||
if (shouldBeMapped != hasBeenMapped)
|
||||
{
|
||||
hasBeenMapped = shouldBeMapped;
|
||||
|
||||
if (shouldBeMapped)
|
||||
X11Symbols::getInstance()->xMapWindow (getDisplay(), client);
|
||||
else
|
||||
X11Symbols::getInstance()->xUnmapWindow (getDisplay(), client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Window getParentX11Window()
|
||||
{
|
||||
if (auto* peer = owner.getPeer())
|
||||
return reinterpret_cast<Window> (peer->getNativeHandle());
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Display* getDisplay() { return XWindowSystem::getInstance()->getDisplay(); }
|
||||
|
||||
//==============================================================================
|
||||
bool getXEmbedMappedFlag()
|
||||
{
|
||||
XWindowSystemUtilities::GetXProperty embedInfo (getDisplay(), client, infoAtom, 0, 2, false, infoAtom);
|
||||
|
||||
if (embedInfo.success && embedInfo.actualFormat == 32
|
||||
&& embedInfo.numItems >= 2 && embedInfo.data != nullptr)
|
||||
{
|
||||
long version;
|
||||
memcpy (&version, embedInfo.data, sizeof (long));
|
||||
|
||||
supportsXembed = true;
|
||||
xembedVersion = jmin ((int) maxXEmbedVersionToSupport, (int) version);
|
||||
|
||||
long flags;
|
||||
memcpy (&flags, embedInfo.data + sizeof (long), sizeof (long));
|
||||
|
||||
return ((flags & XEMBED_MAPPED) != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
supportsXembed = false;
|
||||
xembedVersion = maxXEmbedVersionToSupport;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void propertyChanged (const Atom& a)
|
||||
{
|
||||
if (a == infoAtom)
|
||||
updateMapping();
|
||||
}
|
||||
|
||||
void configureNotify()
|
||||
{
|
||||
XWindowAttributes attr;
|
||||
auto dpy = getDisplay();
|
||||
|
||||
if (X11Symbols::getInstance()->xGetWindowAttributes (dpy, client, &attr))
|
||||
{
|
||||
XWindowAttributes hostAttr;
|
||||
|
||||
if (X11Symbols::getInstance()->xGetWindowAttributes (dpy, host, &hostAttr))
|
||||
if (attr.width != hostAttr.width || attr.height != hostAttr.height)
|
||||
X11Symbols::getInstance()->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 ? peer->getPlatformScaleFactor()
|
||||
: displays.getPrimaryDisplay()->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 = X11Symbols::getInstance()->xRootWindow (dpy, DefaultScreen (dpy));
|
||||
Rectangle<int> newBounds = getX11BoundsFromJuce();
|
||||
|
||||
if (newPeer == nullptr)
|
||||
X11Symbols::getInstance()->xUnmapWindow (dpy, host);
|
||||
|
||||
Window newParent = (newPeer != nullptr ? getParentX11Window() : rootWindow);
|
||||
X11Symbols::getInstance()->xReparentWindow (dpy, host, newParent, newBounds.getX(), newBounds.getY());
|
||||
|
||||
lastPeer = newPeer;
|
||||
|
||||
if (newPeer != nullptr)
|
||||
{
|
||||
if (wantsFocus)
|
||||
{
|
||||
keyWindow = SharedKeyWindow::getKeyWindowForPeer (newPeer);
|
||||
updateKeyFocus();
|
||||
}
|
||||
|
||||
componentMovedOrResized (owner, true, true);
|
||||
X11Symbols::getInstance()->xMapWindow (dpy, host);
|
||||
|
||||
broughtToFront();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateKeyFocus()
|
||||
{
|
||||
if (lastPeer != nullptr && lastPeer->isFocused())
|
||||
X11Symbols::getInstance()->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;
|
||||
|
||||
default:
|
||||
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;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
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 == messageTypeAtom && 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;
|
||||
|
||||
default:
|
||||
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 = messageTypeAtom;
|
||||
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;
|
||||
|
||||
X11Symbols::getInstance()->xSendEvent (dpy, client, False, NoEventMask, (XEvent*) &msg);
|
||||
X11Symbols::getInstance()->xSync (dpy, False);
|
||||
}
|
||||
|
||||
Rectangle<int> getX11BoundsFromJuce()
|
||||
{
|
||||
if (auto* peer = owner.getPeer())
|
||||
{
|
||||
auto r = peer->getComponent().getLocalArea (&owner, owner.getLocalBounds());
|
||||
return r * peer->getPlatformScaleFactor();
|
||||
}
|
||||
|
||||
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(); }
|
||||
void XEmbedComponent::removeClient() { pimpl->setClient (0, true); }
|
||||
|
||||
//==============================================================================
|
||||
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
|
273
deps/juce/modules/juce_gui_extra/native/juce_mac_AppleRemote.mm
vendored
Normal file
273
deps/juce/modules/juce_gui_extra/native/juce_mac_AppleRemote.mm
vendored
Normal file
@ -0,0 +1,273 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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;
|
||||
|
||||
const auto defaultPort =
|
||||
#if defined (MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_VERSION_12_0
|
||||
kIOMainPortDefault;
|
||||
#else
|
||||
kIOMasterPortDefault;
|
||||
#endif
|
||||
|
||||
if (IOServiceGetMatchingServices (defaultPort, 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;
|
||||
|
||||
CFObjectHolder<CFArrayRef> elements;
|
||||
auto device122 = (IOHIDDeviceInterface122**) device;
|
||||
|
||||
if ((*device122)->copyMatchingElements (device122, nullptr, &elements.object) != kIOReturnSuccess)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < CFArrayGetCount (elements.object); ++i)
|
||||
{
|
||||
auto element = (CFDictionaryRef) CFArrayGetValueAtIndex (elements.object, i);
|
||||
|
||||
// get the cookie
|
||||
CFTypeRef object = CFDictionaryGetValue (element, CFSTR (kIOHIDElementCookieKey));
|
||||
|
||||
if (object == nullptr || CFGetTypeID (object) != CFNumberGetTypeID())
|
||||
continue;
|
||||
|
||||
long number;
|
||||
if (! CFNumberGetValue ((CFNumberRef) object, kCFNumberLongType, &number))
|
||||
continue;
|
||||
|
||||
cookies.add ((int) number);
|
||||
}
|
||||
|
||||
if ((*(IOHIDDeviceInterface**) device)
|
||||
->open ((IOHIDDeviceInterface**) device,
|
||||
openInExclusiveMode ? kIOHIDOptionsTypeSeizeDevice
|
||||
: kIOHIDOptionsTypeNone) == KERN_SUCCESS)
|
||||
{
|
||||
queue = (*(IOHIDDeviceInterface**) device)->allocQueue ((IOHIDDeviceInterface**) device);
|
||||
|
||||
if (queue != nullptr)
|
||||
{
|
||||
(*(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, nullptr) == 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
|
346
deps/juce/modules/juce_gui_extra/native/juce_mac_CarbonViewWrapperComponent.h
vendored
Normal file
346
deps/juce/modules/juce_gui_extra/native/juce_mac_CarbonViewWrapperComponent.h
vendored
Normal file
@ -0,0 +1,346 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
259
deps/juce/modules/juce_gui_extra/native/juce_mac_NSViewComponent.mm
vendored
Normal file
259
deps/juce/modules/juce_gui_extra/native/juce_mac_NSViewComponent.mm
vendored
Normal file
@ -0,0 +1,259 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
static const auto nsViewFrameChangedSelector = @selector (frameChanged:);
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
struct NSViewCallbackInterface
|
||||
{
|
||||
virtual ~NSViewCallbackInterface() = default;
|
||||
virtual void frameChanged() = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct NSViewFrameChangeCallbackClass : public ObjCClass<NSObject>
|
||||
{
|
||||
NSViewFrameChangeCallbackClass()
|
||||
: ObjCClass ("JUCE_NSViewCallback_")
|
||||
{
|
||||
addIvar<NSViewCallbackInterface*> ("target");
|
||||
|
||||
addMethod (nsViewFrameChangedSelector, frameChanged, "v@:@");
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
static void setTarget (id self, NSViewCallbackInterface* c)
|
||||
{
|
||||
object_setInstanceVariable (self, "target", c);
|
||||
}
|
||||
|
||||
private:
|
||||
static void frameChanged (id self, SEL, NSNotification*)
|
||||
{
|
||||
if (auto* target = getIvar<NSViewCallbackInterface*> (self, "target"))
|
||||
target->frameChanged();
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (NSViewFrameChangeCallbackClass)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class NSViewFrameWatcher : private NSViewCallbackInterface
|
||||
{
|
||||
public:
|
||||
NSViewFrameWatcher (NSView* viewToWatch, std::function<void()> viewResizedIn)
|
||||
: viewResized (std::move (viewResizedIn)), callback (makeCallbackForView (viewToWatch))
|
||||
{
|
||||
}
|
||||
|
||||
~NSViewFrameWatcher() override
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver: callback];
|
||||
[callback release];
|
||||
callback = nil;
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (NSViewFrameWatcher)
|
||||
JUCE_DECLARE_NON_MOVEABLE (NSViewFrameWatcher)
|
||||
|
||||
private:
|
||||
id makeCallbackForView (NSView* view)
|
||||
{
|
||||
static NSViewFrameChangeCallbackClass cls;
|
||||
auto* result = [cls.createInstance() init];
|
||||
NSViewFrameChangeCallbackClass::setTarget (result, this);
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver: result
|
||||
selector: nsViewFrameChangedSelector
|
||||
name: NSViewFrameDidChangeNotification
|
||||
object: view];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void frameChanged() override { viewResized(); }
|
||||
|
||||
std::function<void()> viewResized;
|
||||
id callback;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class NSViewAttachment : public ReferenceCountedObject,
|
||||
public ComponentMovementWatcher
|
||||
{
|
||||
public:
|
||||
NSViewAttachment (NSView* v, Component& comp)
|
||||
: ComponentMovementWatcher (&comp),
|
||||
view (v), owner (comp),
|
||||
currentPeer (nullptr)
|
||||
{
|
||||
[view retain];
|
||||
[view setPostsFrameChangedNotifications: YES];
|
||||
updateAlpha();
|
||||
|
||||
if (owner.isShowing())
|
||||
componentPeerChanged();
|
||||
}
|
||||
|
||||
~NSViewAttachment() override
|
||||
{
|
||||
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 if we're listening to the
|
||||
// top-level comp we still want the NSView to track its size.
|
||||
if (comp.isOnDesktop() && wasResized)
|
||||
componentMovedOrResized (wasMoved, wasResized);
|
||||
}
|
||||
|
||||
void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
|
||||
{
|
||||
if (auto* peer = owner.getTopLevelComponent()->getPeer())
|
||||
{
|
||||
const auto newArea = peer->getAreaCoveredBy (owner);
|
||||
|
||||
if (convertToRectInt ([view frame]) != newArea)
|
||||
[view setFrame: makeNSRect (newArea)];
|
||||
}
|
||||
}
|
||||
|
||||
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 updateAlpha()
|
||||
{
|
||||
[view setAlphaValue: (CGFloat) owner.getAlpha()];
|
||||
}
|
||||
|
||||
NSView* const view;
|
||||
|
||||
using Ptr = ReferenceCountedObjectPtr<NSViewAttachment>;
|
||||
|
||||
private:
|
||||
Component& owner;
|
||||
ComponentPeer* currentPeer;
|
||||
NSViewFrameWatcher frameWatcher { view, [this] { owner.childBoundsChanged (nullptr); } };
|
||||
|
||||
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() = default;
|
||||
NSViewComponent::~NSViewComponent() = default;
|
||||
|
||||
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* view = static_cast<NSViewAttachment*> (attachment.get())->view;
|
||||
auto r = [view frame];
|
||||
setBounds (Rectangle<int> ((int) r.size.width, (int) r.size.height));
|
||||
|
||||
if (auto* peer = getTopLevelComponent()->getPeer())
|
||||
{
|
||||
const auto position = peer->getAreaCoveredBy (*this).getPosition();
|
||||
[view setFrameOrigin: convertToCGPoint (position)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
568
deps/juce/modules/juce_gui_extra/native/juce_mac_PushNotifications.cpp
vendored
Normal file
568
deps/juce/modules/juce_gui_extra/native/juce_mac_PushNotifications.cpp
vendored
Normal file
@ -0,0 +1,568 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
|
||||
|
||||
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];
|
||||
NSDate* deliveryDate = n.deliveryDate;
|
||||
|
||||
notif.triggerIntervalSec = [dateNow timeIntervalSinceDate: 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];
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
if ([appDelegate respondsToSelector: @selector (setPushNotificationsDelegate:)])
|
||||
[appDelegate performSelector: @selector (setPushNotificationsDelegate:) withObject: delegate.get()];
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
[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:
|
||||
NSUniquePtr<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>> 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, "c@:@@");
|
||||
|
||||
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 |= (NSUInteger) ((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
|
||||
{
|
||||
deviceToken = [deviceTokenToUse]() -> String
|
||||
{
|
||||
auto length = deviceTokenToUse.length;
|
||||
|
||||
if (auto* buffer = (const unsigned char*) deviceTokenToUse.bytes)
|
||||
{
|
||||
NSMutableString* hexString = [NSMutableString stringWithCapacity: (length * 2)];
|
||||
|
||||
for (NSUInteger i = 0; i < length; ++i)
|
||||
[hexString appendFormat:@"%02x", buffer[i]];
|
||||
|
||||
return nsStringToJuce ([hexString copy]);
|
||||
}
|
||||
|
||||
return {};
|
||||
}();
|
||||
|
||||
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*) 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
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
444
deps/juce/modules/juce_gui_extra/native/juce_mac_SystemTrayIcon.cpp
vendored
Normal file
444
deps/juce/modules/juce_gui_extra/native/juce_mac_SystemTrayIcon.cpp
vendored
Normal file
@ -0,0 +1,444 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability", "-Wdeprecated-declarations")
|
||||
|
||||
extern NSMenu* createNSMenu (const PopupMenu&, const String& name, int topLevelMenuId,
|
||||
int topLevelIndex, bool addDelegate);
|
||||
|
||||
//==============================================================================
|
||||
struct StatusItemContainer : public Timer
|
||||
{
|
||||
//==============================================================================
|
||||
StatusItemContainer (SystemTrayIconComponent& iconComp, const Image& im)
|
||||
: owner (iconComp), statusIcon (imageToNSImage (im))
|
||||
{
|
||||
}
|
||||
|
||||
virtual void configureIcon() = 0;
|
||||
virtual void setHighlighted (bool shouldHighlight) = 0;
|
||||
|
||||
//==============================================================================
|
||||
void setIconSize()
|
||||
{
|
||||
[statusIcon.get() setSize: NSMakeSize (20.0f, 20.0f)];
|
||||
}
|
||||
|
||||
void updateIcon (const Image& newImage)
|
||||
{
|
||||
statusIcon.reset (imageToNSImage (newImage));
|
||||
setIconSize();
|
||||
configureIcon();
|
||||
}
|
||||
|
||||
void showMenu (const PopupMenu& menu)
|
||||
{
|
||||
if (NSMenu* m = createNSMenu (menu, "MenuBarItem", -2, -3, true))
|
||||
{
|
||||
setHighlighted (true);
|
||||
stopTimer();
|
||||
|
||||
// There's currently no good alternative to this.
|
||||
[statusItem.get() popUpStatusItemMenu: m];
|
||||
|
||||
startTimer (1);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void timerCallback() override
|
||||
{
|
||||
stopTimer();
|
||||
setHighlighted (false);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
SystemTrayIconComponent& owner;
|
||||
|
||||
NSUniquePtr<NSStatusItem> statusItem;
|
||||
NSUniquePtr<NSImage> statusIcon;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StatusItemContainer)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct ButtonBasedStatusItem : public StatusItemContainer
|
||||
{
|
||||
//==============================================================================
|
||||
ButtonBasedStatusItem (SystemTrayIconComponent& iconComp, const Image& im)
|
||||
: StatusItemContainer (iconComp, im)
|
||||
{
|
||||
static ButtonEventForwarderClass cls;
|
||||
eventForwarder.reset ([cls.createInstance() init]);
|
||||
ButtonEventForwarderClass::setOwner (eventForwarder.get(), this);
|
||||
|
||||
setIconSize();
|
||||
configureIcon();
|
||||
|
||||
statusItem.reset ([[[NSStatusBar systemStatusBar] statusItemWithLength: NSSquareStatusItemLength] retain]);
|
||||
auto button = [statusItem.get() button];
|
||||
button.image = statusIcon.get();
|
||||
button.target = eventForwarder.get();
|
||||
button.action = @selector (handleEvent:);
|
||||
#if defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12
|
||||
[button sendActionOn: NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown | NSEventMaskScrollWheel];
|
||||
#else
|
||||
[button sendActionOn: NSLeftMouseDownMask | NSRightMouseDownMask | NSScrollWheelMask];
|
||||
#endif
|
||||
}
|
||||
|
||||
void configureIcon() override
|
||||
{
|
||||
[statusIcon.get() setTemplate: true];
|
||||
[statusItem.get() button].image = statusIcon.get();
|
||||
}
|
||||
|
||||
void setHighlighted (bool shouldHighlight) override
|
||||
{
|
||||
[[statusItem.get() button] setHighlighted: shouldHighlight];
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void handleEvent()
|
||||
{
|
||||
auto e = [NSApp currentEvent];
|
||||
NSEventType type = [e type];
|
||||
|
||||
const bool isLeft = (type == NSEventTypeLeftMouseDown);
|
||||
const bool isRight = (type == NSEventTypeRightMouseDown);
|
||||
|
||||
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)
|
||||
{
|
||||
owner.mouseDown ({ 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 ({ 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class ButtonEventForwarderClass : public ObjCClass<NSObject>
|
||||
{
|
||||
public:
|
||||
ButtonEventForwarderClass() : ObjCClass<NSObject> ("JUCEButtonEventForwarderClass_")
|
||||
{
|
||||
addIvar<ButtonBasedStatusItem*> ("owner");
|
||||
|
||||
addMethod (@selector (handleEvent:), handleEvent, "v@:@");
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
static ButtonBasedStatusItem* getOwner (id self) { return getIvar<ButtonBasedStatusItem*> (self, "owner"); }
|
||||
static void setOwner (id self, ButtonBasedStatusItem* owner) { object_setInstanceVariable (self, "owner", owner); }
|
||||
|
||||
private:
|
||||
static void handleEvent (id self, SEL, id)
|
||||
{
|
||||
if (auto* owner = getOwner (self))
|
||||
owner->handleEvent();
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
NSUniquePtr<NSObject> eventForwarder;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct ViewBasedStatusItem : public StatusItemContainer
|
||||
{
|
||||
//==============================================================================
|
||||
ViewBasedStatusItem (SystemTrayIconComponent& iconComp, const Image& im)
|
||||
: StatusItemContainer (iconComp, im)
|
||||
{
|
||||
static SystemTrayViewClass cls;
|
||||
view.reset ([cls.createInstance() init]);
|
||||
SystemTrayViewClass::setOwner (view.get(), this);
|
||||
SystemTrayViewClass::setImage (view.get(), statusIcon.get());
|
||||
|
||||
setIconSize();
|
||||
|
||||
statusItem.reset ([[[NSStatusBar systemStatusBar] statusItemWithLength: NSSquareStatusItemLength] retain]);
|
||||
[statusItem.get() setView: view.get()];
|
||||
|
||||
SystemTrayViewClass::frameChanged (view.get(), SEL(), nullptr);
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
[[NSNotificationCenter defaultCenter] addObserver: view.get()
|
||||
selector: @selector (frameChanged:)
|
||||
name: NSWindowDidMoveNotification
|
||||
object: nil];
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
~ViewBasedStatusItem() override
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver: view.get()];
|
||||
[[NSStatusBar systemStatusBar] removeStatusItem: statusItem.get()];
|
||||
SystemTrayViewClass::setOwner (view.get(), nullptr);
|
||||
SystemTrayViewClass::setImage (view.get(), nil);
|
||||
}
|
||||
|
||||
void configureIcon() override
|
||||
{
|
||||
SystemTrayViewClass::setImage (view.get(), statusIcon.get());
|
||||
[statusItem.get() setView: view.get()];
|
||||
}
|
||||
|
||||
void setHighlighted (bool shouldHighlight) override
|
||||
{
|
||||
isHighlighted = shouldHighlight;
|
||||
[view.get() 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct SystemTrayViewClass : public ObjCClass<NSControl>
|
||||
{
|
||||
SystemTrayViewClass() : ObjCClass<NSControl> ("JUCESystemTrayView_")
|
||||
{
|
||||
addIvar<ViewBasedStatusItem*> ("owner");
|
||||
addIvar<NSImage*> ("image");
|
||||
|
||||
addMethod (@selector (mouseDown:), handleEventDown, "v@:@");
|
||||
addMethod (@selector (rightMouseDown:), handleEventDown, "v@:@");
|
||||
addMethod (@selector (drawRect:), drawRect, "v@:@");
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
addMethod (@selector (frameChanged:), frameChanged, "v@:@");
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
static ViewBasedStatusItem* getOwner (id self) { return getIvar<ViewBasedStatusItem*> (self, "owner"); }
|
||||
static NSImage* getImage (id self) { return getIvar<NSImage*> (self, "image"); }
|
||||
static void setOwner (id self, ViewBasedStatusItem* 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.get() 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.get() 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];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
NSUniquePtr<NSControl> view;
|
||||
bool isHighlighted = false;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class SystemTrayIconComponent::Pimpl
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
Pimpl (SystemTrayIconComponent& iconComp, const Image& im)
|
||||
{
|
||||
if (std::floor (NSFoundationVersionNumber) > NSFoundationVersionNumber10_10)
|
||||
statusItemHolder = std::make_unique<ButtonBasedStatusItem> (iconComp, im);
|
||||
else
|
||||
statusItemHolder = std::make_unique<ViewBasedStatusItem> (iconComp, im);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<StatusItemContainer> statusItemHolder;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
void SystemTrayIconComponent::setIconImage (const Image&, const Image& templateImage)
|
||||
{
|
||||
if (templateImage.isValid())
|
||||
{
|
||||
if (pimpl == nullptr)
|
||||
pimpl.reset (new Pimpl (*this, templateImage));
|
||||
else
|
||||
pimpl->statusItemHolder->updateIcon (templateImage);
|
||||
}
|
||||
else
|
||||
{
|
||||
pimpl.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::setIconTooltip (const String&)
|
||||
{
|
||||
// xxx not yet implemented!
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::setHighlighted (bool shouldHighlight)
|
||||
{
|
||||
if (pimpl != nullptr)
|
||||
pimpl->statusItemHolder->setHighlighted (shouldHighlight);
|
||||
}
|
||||
|
||||
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->statusItemHolder->statusItem.get() : nullptr;
|
||||
}
|
||||
|
||||
void SystemTrayIconComponent::showDropdownMenu (const PopupMenu& menu)
|
||||
{
|
||||
if (pimpl != nullptr)
|
||||
pimpl->statusItemHolder->showMenu (menu);
|
||||
}
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
} // namespace juce
|
801
deps/juce/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm
vendored
Normal file
801
deps/juce/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm
vendored
Normal file
@ -0,0 +1,801 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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_IOS || (defined (MAC_OS_X_VERSION_10_10) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10)
|
||||
#define JUCE_USE_WKWEBVIEW 1
|
||||
#endif
|
||||
|
||||
#if (defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12)
|
||||
#define WKWEBVIEW_OPENPANEL_SUPPORTED 1
|
||||
#endif
|
||||
|
||||
static NSURL* appendParametersToFileURL (const URL& url, NSURL* fileUrl)
|
||||
{
|
||||
if (@available (macOS 10.9, *))
|
||||
{
|
||||
const auto parameterNames = url.getParameterNames();
|
||||
const auto parameterValues = url.getParameterValues();
|
||||
|
||||
jassert (parameterNames.size() == parameterValues.size());
|
||||
|
||||
if (parameterNames.isEmpty())
|
||||
return fileUrl;
|
||||
|
||||
NSUniquePtr<NSURLComponents> components ([[NSURLComponents alloc] initWithURL: fileUrl resolvingAgainstBaseURL: NO]);
|
||||
NSUniquePtr<NSMutableArray> queryItems ([[NSMutableArray alloc] init]);
|
||||
|
||||
for (int i = 0; i < parameterNames.size(); ++i)
|
||||
[queryItems.get() addObject: [NSURLQueryItem queryItemWithName: juceStringToNS (parameterNames[i])
|
||||
value: juceStringToNS (parameterValues[i])]];
|
||||
|
||||
[components.get() setQueryItems: queryItems.get()];
|
||||
|
||||
return [components.get() URL];
|
||||
}
|
||||
|
||||
const auto queryString = url.getQueryString();
|
||||
|
||||
if (queryString.isNotEmpty())
|
||||
if (NSString* fileUrlString = [fileUrl absoluteString])
|
||||
return [NSURL URLWithString: [fileUrlString stringByAppendingString: juceStringToNS (queryString)]];
|
||||
|
||||
return fileUrl;
|
||||
}
|
||||
|
||||
static NSMutableURLRequest* getRequestForURL (const String& url, const StringArray* headers, const MemoryBlock* postData)
|
||||
{
|
||||
NSString* urlString = juceStringToNS (url);
|
||||
|
||||
if (@available (macOS 10.9, *))
|
||||
{
|
||||
urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters: [NSCharacterSet URLQueryAllowedCharacterSet]];
|
||||
}
|
||||
else
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
|
||||
urlString = [urlString stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
if (NSURL* nsURL = [NSURL URLWithString: urlString])
|
||||
{
|
||||
NSMutableURLRequest* r
|
||||
= [NSMutableURLRequest requestWithURL: nsURL
|
||||
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)
|
||||
{
|
||||
auto headerName = (*headers)[i].upToFirstOccurrenceOf (":", false, false).trim();
|
||||
auto headerValue = (*headers)[i].fromFirstOccurrenceOf (":", false, false).trim();
|
||||
|
||||
[r setValue: juceStringToNS (headerValue)
|
||||
forHTTPHeaderField: juceStringToNS (headerName)];
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#if JUCE_MAC
|
||||
|
||||
#if JUCE_USE_WKWEBVIEW
|
||||
using WebViewBase = ObjCClass<WKWebView>;
|
||||
#else
|
||||
using WebViewBase = ObjCClass<WebView>;
|
||||
#endif
|
||||
|
||||
struct WebViewKeyEquivalentResponder : public WebViewBase
|
||||
{
|
||||
WebViewKeyEquivalentResponder()
|
||||
: WebViewBase ("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)
|
||||
constexpr auto mask = NSEventModifierFlagDeviceIndependentFlagsMask;
|
||||
constexpr auto key = NSEventModifierFlagCommand;
|
||||
#else
|
||||
constexpr auto mask = NSDeviceIndependentModifierFlagsMask;
|
||||
constexpr auto key = NSCommandKeyMask;
|
||||
#endif
|
||||
|
||||
if (([event modifierFlags] & mask) == key)
|
||||
{
|
||||
auto sendAction = [&] (SEL actionSelector) -> BOOL
|
||||
{
|
||||
return [NSApp sendAction: actionSelector
|
||||
to: first
|
||||
from: self];
|
||||
};
|
||||
|
||||
if ([[event charactersIgnoringModifiers] isEqualToString: @"x"]) return sendAction (@selector (cut:));
|
||||
if ([[event charactersIgnoringModifiers] isEqualToString: @"c"]) return sendAction (@selector (copy:));
|
||||
if ([[event charactersIgnoringModifiers] isEqualToString: @"v"]) return sendAction (@selector (paste:));
|
||||
if ([[event charactersIgnoringModifiers] isEqualToString: @"a"]) return sendAction (@selector (selectAll:));
|
||||
}
|
||||
|
||||
return sendSuperclassMessage<BOOL> (self, selector, event);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#if JUCE_USE_WKWEBVIEW
|
||||
|
||||
struct WebViewDelegateClass : public ObjCClass<NSObject>
|
||||
{
|
||||
WebViewDelegateClass() : ObjCClass<NSObject> ("JUCEWebViewDelegate_")
|
||||
{
|
||||
addIvar<WebBrowserComponent*> ("owner");
|
||||
|
||||
addMethod (@selector (webView:decidePolicyForNavigationAction:decisionHandler:), decidePolicyForNavigationAction, "v@:@@@");
|
||||
addMethod (@selector (webView:didFinishNavigation:), didFinishNavigation, "v@:@@");
|
||||
addMethod (@selector (webView:didFailNavigation:withError:), didFailNavigation, "v@:@@@");
|
||||
addMethod (@selector (webView:didFailProvisionalNavigation:withError:), didFailProvisionalNavigation, "v@:@@@");
|
||||
addMethod (@selector (webViewDidClose:), webViewDidClose, "v@:@");
|
||||
addMethod (@selector (webView:createWebViewWithConfiguration:forNavigationAction:
|
||||
windowFeatures:), createWebView, "@@:@@@@");
|
||||
|
||||
#if WKWEBVIEW_OPENPANEL_SUPPORTED
|
||||
addMethod (@selector (webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:), runOpenPanel, "v@:@@@@");
|
||||
#endif
|
||||
|
||||
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 void decidePolicyForNavigationAction (id self, SEL, WKWebView*, WKNavigationAction* navigationAction,
|
||||
void (^decisionHandler)(WKNavigationActionPolicy))
|
||||
{
|
||||
if (getOwner (self)->pageAboutToLoad (nsStringToJuce ([[[navigationAction request] URL] absoluteString])))
|
||||
decisionHandler (WKNavigationActionPolicyAllow);
|
||||
else
|
||||
decisionHandler (WKNavigationActionPolicyCancel);
|
||||
}
|
||||
|
||||
static void didFinishNavigation (id self, SEL, WKWebView* webview, WKNavigation*)
|
||||
{
|
||||
getOwner (self)->pageFinishedLoading (nsStringToJuce ([[webview URL] absoluteString]));
|
||||
}
|
||||
|
||||
static void displayError (WebBrowserComponent* owner, NSError* error)
|
||||
{
|
||||
if ([error code] != NSURLErrorCancelled)
|
||||
{
|
||||
auto errorString = nsStringToJuce ([error localizedDescription]);
|
||||
bool proceedToErrorPage = owner->pageLoadHadNetworkError (errorString);
|
||||
|
||||
// WKWebView doesn't have an internal error page, so make a really simple one ourselves
|
||||
if (proceedToErrorPage)
|
||||
owner->goToURL ("data:text/plain;charset=UTF-8," + errorString);
|
||||
}
|
||||
}
|
||||
|
||||
static void didFailNavigation (id self, SEL, WKWebView*, WKNavigation*, NSError* error)
|
||||
{
|
||||
displayError (getOwner (self), error);
|
||||
}
|
||||
|
||||
static void didFailProvisionalNavigation (id self, SEL, WKWebView*, WKNavigation*, NSError* error)
|
||||
{
|
||||
displayError (getOwner (self), error);
|
||||
}
|
||||
|
||||
static void webViewDidClose (id self, SEL, WKWebView*)
|
||||
{
|
||||
getOwner (self)->windowCloseRequest();
|
||||
}
|
||||
|
||||
static WKWebView* createWebView (id self, SEL, WKWebView*, WKWebViewConfiguration*,
|
||||
WKNavigationAction* navigationAction, WKWindowFeatures*)
|
||||
{
|
||||
getOwner (self)->newWindowAttemptingToLoad (nsStringToJuce ([[[navigationAction request] URL] absoluteString]));
|
||||
return nil;
|
||||
}
|
||||
|
||||
#if WKWEBVIEW_OPENPANEL_SUPPORTED
|
||||
static void runOpenPanel (id, SEL, WKWebView*, WKOpenPanelParameters* parameters, WKFrameInfo*,
|
||||
void (^completionHandler)(NSArray<NSURL*>*))
|
||||
{
|
||||
using CompletionHandlerType = decltype (completionHandler);
|
||||
|
||||
class DeletedFileChooserWrapper : private DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
DeletedFileChooserWrapper (std::unique_ptr<FileChooser> fc, CompletionHandlerType h)
|
||||
: chooser (std::move (fc)), handler (h)
|
||||
{
|
||||
[handler retain];
|
||||
}
|
||||
|
||||
~DeletedFileChooserWrapper()
|
||||
{
|
||||
callHandler (nullptr);
|
||||
[handler release];
|
||||
}
|
||||
|
||||
void callHandler (NSArray<NSURL*>* urls)
|
||||
{
|
||||
if (handlerCalled)
|
||||
return;
|
||||
|
||||
handler (urls);
|
||||
handlerCalled = true;
|
||||
}
|
||||
|
||||
std::unique_ptr<FileChooser> chooser;
|
||||
|
||||
private:
|
||||
CompletionHandlerType handler;
|
||||
bool handlerCalled = false;
|
||||
};
|
||||
|
||||
auto chooser = std::make_unique<FileChooser> (TRANS("Select the file you want to upload..."),
|
||||
File::getSpecialLocation (File::userHomeDirectory), "*");
|
||||
auto* wrapper = new DeletedFileChooserWrapper (std::move (chooser), completionHandler);
|
||||
|
||||
auto flags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles
|
||||
| ([parameters allowsMultipleSelection] ? FileBrowserComponent::canSelectMultipleItems : 0);
|
||||
|
||||
#if (defined (MAC_OS_X_VERSION_10_14) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_14)
|
||||
if ([parameters allowsDirectories])
|
||||
flags |= FileBrowserComponent::canSelectDirectories;
|
||||
#endif
|
||||
|
||||
wrapper->chooser->launchAsync (flags, [wrapper] (const FileChooser&)
|
||||
{
|
||||
auto results = wrapper->chooser->getResults();
|
||||
auto urls = [NSMutableArray arrayWithCapacity: (NSUInteger) results.size()];
|
||||
|
||||
for (auto& f : results)
|
||||
[urls addObject: [NSURL fileURLWithPath: juceStringToNS (f.getFullPathName())]];
|
||||
|
||||
wrapper->callHandler (urls);
|
||||
delete wrapper;
|
||||
});
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class WebBrowserComponent::Pimpl
|
||||
#if JUCE_MAC
|
||||
: public NSViewComponent
|
||||
#else
|
||||
: public UIViewComponent
|
||||
#endif
|
||||
{
|
||||
public:
|
||||
Pimpl (WebBrowserComponent* owner)
|
||||
{
|
||||
#if JUCE_MAC
|
||||
static WebViewKeyEquivalentResponder webviewClass;
|
||||
webView = (WKWebView*) webviewClass.createInstance();
|
||||
|
||||
webView = [webView initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f)];
|
||||
#else
|
||||
webView = [[WKWebView alloc] initWithFrame: CGRectMake (0, 0, 100.0f, 100.0f)];
|
||||
#endif
|
||||
|
||||
static WebViewDelegateClass cls;
|
||||
webViewDelegate = [cls.createInstance() init];
|
||||
WebViewDelegateClass::setOwner (webViewDelegate, owner);
|
||||
|
||||
[webView setNavigationDelegate: webViewDelegate];
|
||||
[webView setUIDelegate: webViewDelegate];
|
||||
|
||||
setView (webView);
|
||||
}
|
||||
|
||||
~Pimpl()
|
||||
{
|
||||
[webView setNavigationDelegate: nil];
|
||||
[webView setUIDelegate: nil];
|
||||
|
||||
[webViewDelegate release];
|
||||
|
||||
setView (nil);
|
||||
}
|
||||
|
||||
void goToURL (const String& url,
|
||||
const StringArray* headers,
|
||||
const MemoryBlock* postData)
|
||||
{
|
||||
auto trimmed = url.trimStart();
|
||||
|
||||
if (trimmed.startsWithIgnoreCase ("javascript:"))
|
||||
{
|
||||
[webView evaluateJavaScript: juceStringToNS (url.fromFirstOccurrenceOf (":", false, false))
|
||||
completionHandler: nil];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
stop();
|
||||
|
||||
if (trimmed.startsWithIgnoreCase ("file:"))
|
||||
{
|
||||
auto file = URL (url).getLocalFile();
|
||||
|
||||
if (NSURL* nsUrl = [NSURL fileURLWithPath: juceStringToNS (file.getFullPathName())])
|
||||
[webView loadFileURL: appendParametersToFileURL (url, nsUrl) allowingReadAccessToURL: nsUrl];
|
||||
}
|
||||
else if (NSMutableURLRequest* request = getRequestForURL (url, headers, postData))
|
||||
{
|
||||
[webView loadRequest: request];
|
||||
}
|
||||
}
|
||||
|
||||
void goBack() { [webView goBack]; }
|
||||
void goForward() { [webView goForward]; }
|
||||
|
||||
void stop() { [webView stopLoading]; }
|
||||
void refresh() { [webView reload]; }
|
||||
|
||||
private:
|
||||
WKWebView* webView = nil;
|
||||
id webViewDelegate;
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
#if JUCE_MAC
|
||||
|
||||
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)
|
||||
{
|
||||
struct DeletedFileChooserWrapper : private DeletedAtShutdown
|
||||
{
|
||||
DeletedFileChooserWrapper (std::unique_ptr<FileChooser> fc, id<WebOpenPanelResultListener> rl)
|
||||
: chooser (std::move (fc)), listener (rl)
|
||||
{
|
||||
[listener retain];
|
||||
}
|
||||
|
||||
~DeletedFileChooserWrapper()
|
||||
{
|
||||
[listener release];
|
||||
}
|
||||
|
||||
std::unique_ptr<FileChooser> chooser;
|
||||
id<WebOpenPanelResultListener> listener;
|
||||
};
|
||||
|
||||
auto chooser = std::make_unique<FileChooser> (TRANS("Select the file you want to upload..."),
|
||||
File::getSpecialLocation (File::userHomeDirectory), "*");
|
||||
auto* wrapper = new DeletedFileChooserWrapper (std::move (chooser), resultListener);
|
||||
|
||||
auto flags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles
|
||||
| (allowMultipleFiles ? FileBrowserComponent::canSelectMultipleItems : 0);
|
||||
|
||||
wrapper->chooser->launchAsync (flags, [wrapper] (const FileChooser&)
|
||||
{
|
||||
for (auto& f : wrapper->chooser->getResults())
|
||||
[wrapper->listener chooseFilename: juceStringToNS (f.getFullPathName())];
|
||||
|
||||
delete wrapper;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
struct WebViewDelegateClass : public ObjCClass<NSObject>
|
||||
{
|
||||
WebViewDelegateClass() : ObjCClass<NSObject> ("JUCEWebViewDelegate_")
|
||||
{
|
||||
addIvar<WebBrowserComponent*> ("owner");
|
||||
|
||||
addMethod (@selector (gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:),
|
||||
shouldRecognizeSimultaneouslyWithGestureRecognizer, "c@:@@");
|
||||
|
||||
addMethod (@selector (webView:shouldStartLoadWithRequest:navigationType:), shouldStartLoadWithRequest, "c@:@@@");
|
||||
addMethod (@selector (webViewDidFinishLoad:), webViewDidFinishLoad, "v@:@");
|
||||
|
||||
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 BOOL shouldRecognizeSimultaneouslyWithGestureRecognizer (id, SEL, UIGestureRecognizer*, UIGestureRecognizer*)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
static BOOL shouldStartLoadWithRequest (id self, SEL, UIWebView*, NSURLRequest* request, UIWebViewNavigationType)
|
||||
{
|
||||
return getOwner (self)->pageAboutToLoad (nsStringToJuce ([[request URL] absoluteString]));
|
||||
}
|
||||
|
||||
static void webViewDidFinishLoad (id self, SEL, UIWebView* webView)
|
||||
{
|
||||
getOwner (self)->pageFinishedLoading (nsStringToJuce ([[[webView request] URL] absoluteString]));
|
||||
}
|
||||
};
|
||||
|
||||
#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()];
|
||||
|
||||
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)];
|
||||
|
||||
static WebViewDelegateClass cls;
|
||||
webViewDelegate = [cls.createInstance() init];
|
||||
WebViewDelegateClass::setOwner (webViewDelegate, owner);
|
||||
|
||||
[webView setDelegate: webViewDelegate];
|
||||
#endif
|
||||
|
||||
setView (webView);
|
||||
}
|
||||
|
||||
~Pimpl()
|
||||
{
|
||||
#if JUCE_MAC
|
||||
[webView setPolicyDelegate: nil];
|
||||
[webView setFrameLoadDelegate: nil];
|
||||
[webView setUIDelegate: nil];
|
||||
|
||||
[clickListener release];
|
||||
#else
|
||||
[webView setDelegate: nil];
|
||||
[webViewDelegate release];
|
||||
#endif
|
||||
|
||||
setView (nil);
|
||||
}
|
||||
|
||||
void goToURL (const String& url,
|
||||
const StringArray* headers,
|
||||
const MemoryBlock* postData)
|
||||
{
|
||||
if (url.trimStart().startsWithIgnoreCase ("javascript:"))
|
||||
{
|
||||
[webView stringByEvaluatingJavaScriptFromString: juceStringToNS (url.fromFirstOccurrenceOf (":", false, false))];
|
||||
return;
|
||||
}
|
||||
|
||||
stop();
|
||||
|
||||
auto getRequest = [&]() -> NSMutableURLRequest*
|
||||
{
|
||||
if (url.trimStart().startsWithIgnoreCase ("file:"))
|
||||
{
|
||||
auto file = URL (url).getLocalFile();
|
||||
|
||||
if (NSURL* nsUrl = [NSURL fileURLWithPath: juceStringToNS (file.getFullPathName())])
|
||||
return [NSMutableURLRequest requestWithURL: appendParametersToFileURL (url, nsUrl)
|
||||
cachePolicy: NSURLRequestUseProtocolCachePolicy
|
||||
timeoutInterval: 30.0];
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return getRequestForURL (url, headers, postData);
|
||||
};
|
||||
|
||||
if (NSMutableURLRequest* request = getRequest())
|
||||
{
|
||||
#if JUCE_MAC
|
||||
[[webView mainFrame] loadRequest: request];
|
||||
#else
|
||||
[webView loadRequest: request];
|
||||
#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&)
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
// 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)];
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
private:
|
||||
#if JUCE_MAC
|
||||
WebView* webView = nil;
|
||||
id clickListener;
|
||||
#else
|
||||
UIWebView* webView = nil;
|
||||
id webViewDelegate;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
WebBrowserComponent::WebBrowserComponent (bool unloadWhenHidden)
|
||||
: unloadPageWhenHidden (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 (unloadPageWhenHidden && ! 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", nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
496
deps/juce/modules/juce_gui_extra/native/juce_win32_ActiveXComponent.cpp
vendored
Normal file
496
deps/juce/modules/juce_gui_extra/native/juce_win32_ActiveXComponent.cpp
vendored
Normal file
@ -0,0 +1,496 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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 = nullptr;
|
||||
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)
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
|
||||
|
||||
if (type == __uuidof (IOleInPlaceSite))
|
||||
{
|
||||
inplaceSite->AddRef();
|
||||
*result = static_cast<IOleInPlaceSite*> (inplaceSite);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return ComBaseClassHelper <IOleClientSite>::QueryInterface (type, result);
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
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 HWND getHWND (const ActiveXControlComponent* const component)
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
|
||||
|
||||
HWND hwnd = {};
|
||||
const IID iid = __uuidof (IOleWindow);
|
||||
|
||||
if (auto* window = (IOleWindow*) component->queryInterface (&iid))
|
||||
{
|
||||
window->GetWindow (&hwnd);
|
||||
window->Release();
|
||||
}
|
||||
|
||||
return hwnd;
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
static 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 ComponentPeer::ScaleFactorListener
|
||||
{
|
||||
public:
|
||||
Pimpl (HWND hwnd, ActiveXControlComponent& activeXComp)
|
||||
: ComponentMovementWatcher (&activeXComp),
|
||||
owner (activeXComp),
|
||||
storage (new ActiveXHelpers::JuceIStorage()),
|
||||
clientSite (new ActiveXHelpers::JuceIOleClientSite (hwnd))
|
||||
{
|
||||
}
|
||||
|
||||
~Pimpl() override
|
||||
{
|
||||
if (control != nullptr)
|
||||
{
|
||||
control->Close (OLECLOSE_NOSAVE);
|
||||
control->Release();
|
||||
}
|
||||
|
||||
clientSite->Release();
|
||||
storage->Release();
|
||||
|
||||
if (currentPeer != nullptr)
|
||||
currentPeer->removeScaleFactorListener (this);
|
||||
}
|
||||
|
||||
void setControlBounds (Rectangle<int> newBounds) const
|
||||
{
|
||||
if (controlHWND != nullptr)
|
||||
{
|
||||
if (auto* peer = owner.getTopLevelComponent()->getPeer())
|
||||
newBounds = (newBounds.toDouble() * peer->getPlatformScaleFactor()).toNearestInt();
|
||||
|
||||
MoveWindow (controlHWND, newBounds.getX(), newBounds.getY(), newBounds.getWidth(), newBounds.getHeight(), TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
void setControlVisible (bool shouldBeVisible) const
|
||||
{
|
||||
if (controlHWND != nullptr)
|
||||
ShowWindow (controlHWND, shouldBeVisible ? SW_SHOWNA : SW_HIDE);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
using ComponentMovementWatcher::componentMovedOrResized;
|
||||
|
||||
void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
|
||||
{
|
||||
if (auto* peer = owner.getTopLevelComponent()->getPeer())
|
||||
setControlBounds (peer->getAreaCoveredBy (owner));
|
||||
}
|
||||
|
||||
void componentPeerChanged() override
|
||||
{
|
||||
if (currentPeer != nullptr)
|
||||
currentPeer->removeScaleFactorListener (this);
|
||||
|
||||
componentMovedOrResized (true, true);
|
||||
|
||||
currentPeer = owner.getTopLevelComponent()->getPeer();
|
||||
|
||||
if (currentPeer != nullptr)
|
||||
currentPeer->addScaleFactorListener (this);
|
||||
}
|
||||
|
||||
using ComponentMovementWatcher::componentVisibilityChanged;
|
||||
|
||||
void componentVisibilityChanged() override
|
||||
{
|
||||
setControlVisible (owner.isShowing());
|
||||
componentPeerChanged();
|
||||
}
|
||||
|
||||
void nativeScaleFactorChanged (double /*newScaleFactor*/) override
|
||||
{
|
||||
componentMovedOrResized (true, true);
|
||||
}
|
||||
|
||||
// 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;
|
||||
ComponentPeer* currentPeer = nullptr;
|
||||
HWND controlHWND = {};
|
||||
IStorage* storage = nullptr;
|
||||
ActiveXHelpers::JuceIOleClientSite* clientSite = nullptr;
|
||||
IOleObject* control = nullptr;
|
||||
WNDPROC originalWndProc = nullptr;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
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));
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
|
||||
|
||||
HRESULT hr = OleCreate (*(const IID*) controlIID, __uuidof (IOleObject), 1 /*OLERENDER_DRAW*/, nullptr,
|
||||
newControl->clientSite, newControl->storage,
|
||||
(void**) &(newControl->control));
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
if (hr == S_OK)
|
||||
{
|
||||
newControl->control->SetHostNames (L"JUCE", nullptr);
|
||||
|
||||
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, nullptr, newControl->clientSite, 0, hwnd, &rect) == S_OK)
|
||||
{
|
||||
control.reset (newControl.release());
|
||||
control->controlHWND = ActiveXHelpers::getHWND (this);
|
||||
|
||||
if (control->controlHWND != nullptr)
|
||||
{
|
||||
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
|
175
deps/juce/modules/juce_gui_extra/native/juce_win32_HWNDComponent.cpp
vendored
Normal file
175
deps/juce/modules/juce_gui_extra/native/juce_win32_HWNDComponent.cpp
vendored
Normal file
@ -0,0 +1,175 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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 HWNDComponent::Pimpl : public ComponentMovementWatcher
|
||||
{
|
||||
public:
|
||||
Pimpl (HWND h, Component& comp)
|
||||
: ComponentMovementWatcher (&comp),
|
||||
hwnd (h),
|
||||
owner (comp)
|
||||
{
|
||||
if (owner.isShowing())
|
||||
componentPeerChanged();
|
||||
}
|
||||
|
||||
~Pimpl() override
|
||||
{
|
||||
removeFromParent();
|
||||
DestroyWindow (hwnd);
|
||||
}
|
||||
|
||||
void componentMovedOrResized (bool wasMoved, bool wasResized) override
|
||||
{
|
||||
if (auto* peer = owner.getTopLevelComponent()->getPeer())
|
||||
{
|
||||
auto area = (peer->getAreaCoveredBy (owner).toFloat() * peer->getPlatformScaleFactor()).getSmallestIntegerContainer();
|
||||
|
||||
UINT flagsToSend = SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER;
|
||||
|
||||
if (! wasMoved) flagsToSend |= SWP_NOMOVE;
|
||||
if (! wasResized) flagsToSend |= SWP_NOSIZE;
|
||||
|
||||
ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { hwnd };
|
||||
|
||||
SetWindowPos (hwnd, nullptr, area.getX(), area.getY(), area.getWidth(), area.getHeight(), flagsToSend);
|
||||
}
|
||||
}
|
||||
|
||||
using ComponentMovementWatcher::componentMovedOrResized;
|
||||
|
||||
void componentPeerChanged() override
|
||||
{
|
||||
auto* peer = owner.getPeer();
|
||||
|
||||
if (currentPeer != peer)
|
||||
{
|
||||
removeFromParent();
|
||||
currentPeer = peer;
|
||||
|
||||
addToParent();
|
||||
}
|
||||
|
||||
auto isShowing = owner.isShowing();
|
||||
|
||||
ShowWindow (hwnd, isShowing ? SW_SHOWNA : SW_HIDE);
|
||||
|
||||
if (isShowing)
|
||||
InvalidateRect (hwnd, nullptr, 0);
|
||||
}
|
||||
|
||||
void componentVisibilityChanged() override
|
||||
{
|
||||
componentPeerChanged();
|
||||
}
|
||||
|
||||
using ComponentMovementWatcher::componentVisibilityChanged;
|
||||
|
||||
void componentBroughtToFront (Component& comp) override
|
||||
{
|
||||
ComponentMovementWatcher::componentBroughtToFront (comp);
|
||||
}
|
||||
|
||||
Rectangle<int> getHWNDBounds() const
|
||||
{
|
||||
if (auto* peer = owner.getPeer())
|
||||
{
|
||||
ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { hwnd };
|
||||
|
||||
RECT r;
|
||||
GetWindowRect (hwnd, &r);
|
||||
Rectangle<int> windowRectangle (r.right - r.left, r.bottom - r.top);
|
||||
|
||||
return (windowRectangle.toFloat() / peer->getPlatformScaleFactor()).toNearestInt();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
HWND hwnd;
|
||||
|
||||
private:
|
||||
void addToParent()
|
||||
{
|
||||
if (currentPeer != nullptr)
|
||||
{
|
||||
auto windowFlags = GetWindowLongPtr (hwnd, -16);
|
||||
|
||||
using FlagType = decltype (windowFlags);
|
||||
|
||||
windowFlags &= ~(FlagType) WS_POPUP;
|
||||
windowFlags |= (FlagType) WS_CHILD;
|
||||
|
||||
SetWindowLongPtr (hwnd, -16, windowFlags);
|
||||
SetParent (hwnd, (HWND) currentPeer->getNativeHandle());
|
||||
|
||||
componentMovedOrResized (true, true);
|
||||
}
|
||||
}
|
||||
|
||||
void removeFromParent()
|
||||
{
|
||||
ShowWindow (hwnd, SW_HIDE);
|
||||
SetParent (hwnd, nullptr);
|
||||
}
|
||||
|
||||
Component& owner;
|
||||
ComponentPeer* currentPeer = nullptr;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
HWNDComponent::HWNDComponent() {}
|
||||
HWNDComponent::~HWNDComponent() {}
|
||||
|
||||
void HWNDComponent::paint (Graphics&) {}
|
||||
|
||||
void HWNDComponent::setHWND (void* hwnd)
|
||||
{
|
||||
if (hwnd != getHWND())
|
||||
{
|
||||
pimpl.reset();
|
||||
|
||||
if (hwnd != nullptr)
|
||||
pimpl.reset (new Pimpl ((HWND) hwnd, *this));
|
||||
}
|
||||
}
|
||||
|
||||
void* HWNDComponent::getHWND() const
|
||||
{
|
||||
return pimpl == nullptr ? nullptr : (void*) pimpl->hwnd;
|
||||
}
|
||||
|
||||
void HWNDComponent::resizeToFit()
|
||||
{
|
||||
if (pimpl != nullptr)
|
||||
setBounds (pimpl->getHWNDBounds());
|
||||
}
|
||||
|
||||
} // namespace juce
|
242
deps/juce/modules/juce_gui_extra/native/juce_win32_SystemTrayIcon.cpp
vendored
Normal file
242
deps/juce/modules/juce_gui_extra/native/juce_win32_SystemTrayIcon.cpp
vendored
Normal file
@ -0,0 +1,242 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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& colourImage, const Image&)
|
||||
{
|
||||
if (colourImage.isValid())
|
||||
{
|
||||
HICON hicon = IconConverters::createHICONFromImage (colourImage, 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
|
994
deps/juce/modules/juce_gui_extra/native/juce_win32_WebBrowserComponent.cpp
vendored
Normal file
994
deps/juce/modules/juce_gui_extra/native/juce_win32_WebBrowserComponent.cpp
vendored
Normal file
@ -0,0 +1,994 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-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 InternalWebViewType
|
||||
{
|
||||
InternalWebViewType() {}
|
||||
virtual ~InternalWebViewType() {}
|
||||
|
||||
virtual void createBrowser() = 0;
|
||||
virtual bool hasBrowserBeenCreated() = 0;
|
||||
|
||||
virtual void goToURL (const String&, const StringArray*, const MemoryBlock*) = 0;
|
||||
|
||||
virtual void stop() = 0;
|
||||
virtual void goBack() = 0;
|
||||
virtual void goForward() = 0;
|
||||
virtual void refresh() = 0;
|
||||
|
||||
virtual void focusGained() {}
|
||||
virtual void setWebViewSize (int, int) = 0;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InternalWebViewType)
|
||||
};
|
||||
|
||||
#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 Win32WebView : public InternalWebViewType,
|
||||
public ActiveXControlComponent
|
||||
{
|
||||
public:
|
||||
Win32WebView (WebBrowserComponent& owner)
|
||||
{
|
||||
owner.addAndMakeVisible (this);
|
||||
}
|
||||
|
||||
~Win32WebView() override
|
||||
{
|
||||
if (connectionPoint != nullptr)
|
||||
connectionPoint->Unadvise (adviseCookie);
|
||||
|
||||
if (browser != nullptr)
|
||||
browser->Release();
|
||||
}
|
||||
|
||||
void createBrowser() override
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
|
||||
|
||||
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)
|
||||
{
|
||||
if (auto* owner = dynamic_cast<WebBrowserComponent*> (Component::getParentComponent()))
|
||||
{
|
||||
auto handler = new EventHandler (*owner);
|
||||
connectionPoint->Advise (handler, &adviseCookie);
|
||||
handler->Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
bool hasBrowserBeenCreated() override
|
||||
{
|
||||
return browser != nullptr;
|
||||
}
|
||||
|
||||
void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData) override
|
||||
{
|
||||
if (browser != 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)
|
||||
{
|
||||
auto 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;
|
||||
|
||||
sa = nullptr;
|
||||
postDataVar = postDataVar2;
|
||||
}
|
||||
else
|
||||
{
|
||||
SafeArrayDestroy (sa);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto urlBSTR = SysAllocString ((const OLECHAR*) url.toWideCharPointer());
|
||||
browser->Navigate (urlBSTR, &headerFlags, &frame, &postDataVar, &headersVar);
|
||||
SysFreeString (urlBSTR);
|
||||
|
||||
VariantClear (&headerFlags);
|
||||
VariantClear (&frame);
|
||||
VariantClear (&postDataVar);
|
||||
VariantClear (&headersVar);
|
||||
}
|
||||
}
|
||||
|
||||
void stop() override
|
||||
{
|
||||
if (browser != nullptr)
|
||||
browser->Stop();
|
||||
}
|
||||
|
||||
void goBack() override
|
||||
{
|
||||
if (browser != nullptr)
|
||||
browser->GoBack();
|
||||
}
|
||||
|
||||
void goForward() override
|
||||
{
|
||||
if (browser != nullptr)
|
||||
browser->GoForward();
|
||||
}
|
||||
|
||||
void refresh() override
|
||||
{
|
||||
if (browser != nullptr)
|
||||
browser->Refresh();
|
||||
}
|
||||
|
||||
void focusGained() override
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
|
||||
|
||||
auto iidOleObject = __uuidof (IOleObject);
|
||||
auto iidOleWindow = __uuidof (IOleWindow);
|
||||
|
||||
if (auto oleObject = (IOleObject*) queryInterface (&iidOleObject))
|
||||
{
|
||||
if (auto oleWindow = (IOleWindow*) 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();
|
||||
}
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
using ActiveXControlComponent::focusGained;
|
||||
|
||||
void setWebViewSize (int width, int height) override
|
||||
{
|
||||
setSize (width, height);
|
||||
}
|
||||
|
||||
private:
|
||||
IWebBrowser2* browser = nullptr;
|
||||
IConnectionPoint* connectionPoint = nullptr;
|
||||
DWORD adviseCookie = 0;
|
||||
|
||||
//==============================================================================
|
||||
struct EventHandler : public ComBaseClassHelper<IDispatch>,
|
||||
public ComponentMovementWatcher
|
||||
{
|
||||
EventHandler (WebBrowserComponent& w) : ComponentMovementWatcher (&w), owner (w) {}
|
||||
|
||||
JUCE_COMRESULT GetTypeInfoCount (UINT*) override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT GetTypeInfo (UINT, LCID, ITypeInfo**) override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT GetIDsOfNames (REFIID, LPOLESTR*, UINT, LCID, DISPID*) override { return E_NOTIMPL; }
|
||||
|
||||
JUCE_COMRESULT Invoke (DISPID dispIdMember, REFIID /*riid*/, LCID /*lcid*/, WORD /*wFlags*/, DISPPARAMS* pDispParams,
|
||||
VARIANT* /*pVarResult*/, EXCEPINFO* /*pExcepInfo*/, UINT* /*puArgErr*/) override
|
||||
{
|
||||
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 errors
|
||||
if (statusCode < 0)
|
||||
{
|
||||
LPTSTR messageBuffer = nullptr;
|
||||
auto size = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr, (DWORD) 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(); }
|
||||
|
||||
using ComponentMovementWatcher::componentVisibilityChanged;
|
||||
using ComponentMovementWatcher::componentMovedOrResized;
|
||||
|
||||
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 (Win32WebView)
|
||||
};
|
||||
|
||||
#if JUCE_USE_WIN_WEBVIEW2
|
||||
|
||||
using namespace Microsoft::WRL;
|
||||
|
||||
class WebView2 : public InternalWebViewType,
|
||||
public Component,
|
||||
public ComponentMovementWatcher
|
||||
{
|
||||
public:
|
||||
WebView2 (WebBrowserComponent& o, const WebView2Preferences& prefs)
|
||||
: ComponentMovementWatcher (&o),
|
||||
owner (o),
|
||||
preferences (prefs)
|
||||
{
|
||||
if (! createWebViewEnvironment())
|
||||
throw std::runtime_error ("Failed to create the CoreWebView2Environemnt");
|
||||
|
||||
owner.addAndMakeVisible (this);
|
||||
}
|
||||
|
||||
~WebView2() override
|
||||
{
|
||||
removeEventHandlers();
|
||||
closeWebView();
|
||||
|
||||
if (webView2LoaderHandle != nullptr)
|
||||
::FreeLibrary (webView2LoaderHandle);
|
||||
}
|
||||
|
||||
void createBrowser() override
|
||||
{
|
||||
if (webView == nullptr)
|
||||
{
|
||||
jassert (webViewEnvironment != nullptr);
|
||||
createWebView();
|
||||
}
|
||||
}
|
||||
|
||||
bool hasBrowserBeenCreated() override
|
||||
{
|
||||
return webView != nullptr || isCreating;
|
||||
}
|
||||
|
||||
void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData) override
|
||||
{
|
||||
urlRequest = { url,
|
||||
headers != nullptr ? *headers : StringArray(),
|
||||
postData != nullptr && postData->getSize() > 0 ? *postData : MemoryBlock() };
|
||||
|
||||
if (webView != nullptr)
|
||||
webView->Navigate (urlRequest.url.toWideCharPointer());
|
||||
}
|
||||
|
||||
void stop() override
|
||||
{
|
||||
if (webView != nullptr)
|
||||
webView->Stop();
|
||||
}
|
||||
|
||||
void goBack() override
|
||||
{
|
||||
if (webView != nullptr)
|
||||
{
|
||||
BOOL canGoBack = false;
|
||||
webView->get_CanGoBack (&canGoBack);
|
||||
|
||||
if (canGoBack)
|
||||
webView->GoBack();
|
||||
}
|
||||
}
|
||||
|
||||
void goForward() override
|
||||
{
|
||||
if (webView != nullptr)
|
||||
{
|
||||
BOOL canGoForward = false;
|
||||
webView->get_CanGoForward (&canGoForward);
|
||||
|
||||
if (canGoForward)
|
||||
webView->GoForward();
|
||||
}
|
||||
}
|
||||
|
||||
void refresh() override
|
||||
{
|
||||
if (webView != nullptr)
|
||||
webView->Reload();
|
||||
}
|
||||
|
||||
void setWebViewSize (int width, int height) override
|
||||
{
|
||||
setSize (width, height);
|
||||
}
|
||||
|
||||
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();
|
||||
owner.visibilityChanged();
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
template <class ArgType>
|
||||
static String getUriStringFromArgs (ArgType* args)
|
||||
{
|
||||
if (args != nullptr)
|
||||
{
|
||||
LPWSTR uri;
|
||||
args->get_Uri (&uri);
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void addEventHandlers()
|
||||
{
|
||||
if (webView != nullptr)
|
||||
{
|
||||
webView->add_NavigationStarting (Callback<ICoreWebView2NavigationStartingEventHandler> (
|
||||
[this] (ICoreWebView2*, ICoreWebView2NavigationStartingEventArgs* args) -> HRESULT
|
||||
{
|
||||
auto uriString = getUriStringFromArgs (args);
|
||||
|
||||
if (uriString.isNotEmpty() && ! owner.pageAboutToLoad (uriString))
|
||||
args->put_Cancel (true);
|
||||
|
||||
return S_OK;
|
||||
}).Get(), &navigationStartingToken);
|
||||
|
||||
webView->add_NewWindowRequested (Callback<ICoreWebView2NewWindowRequestedEventHandler> (
|
||||
[this] (ICoreWebView2*, ICoreWebView2NewWindowRequestedEventArgs* args) -> HRESULT
|
||||
{
|
||||
auto uriString = getUriStringFromArgs (args);
|
||||
|
||||
if (uriString.isNotEmpty())
|
||||
{
|
||||
owner.newWindowAttemptingToLoad (uriString);
|
||||
args->put_Handled (true);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}).Get(), &newWindowRequestedToken);
|
||||
|
||||
webView->add_WindowCloseRequested (Callback<ICoreWebView2WindowCloseRequestedEventHandler> (
|
||||
[this] (ICoreWebView2*, IUnknown*) -> HRESULT
|
||||
{
|
||||
owner.windowCloseRequest();
|
||||
return S_OK;
|
||||
}).Get(), &windowCloseRequestedToken);
|
||||
|
||||
webView->add_NavigationCompleted (Callback<ICoreWebView2NavigationCompletedEventHandler> (
|
||||
[this] (ICoreWebView2* sender, ICoreWebView2NavigationCompletedEventArgs* args) -> HRESULT
|
||||
{
|
||||
LPWSTR uri;
|
||||
sender->get_Source (&uri);
|
||||
|
||||
String uriString (uri);
|
||||
|
||||
if (uriString.isNotEmpty())
|
||||
{
|
||||
BOOL success = false;
|
||||
args->get_IsSuccess (&success);
|
||||
|
||||
COREWEBVIEW2_WEB_ERROR_STATUS errorStatus;
|
||||
args->get_WebErrorStatus (&errorStatus);
|
||||
|
||||
if (success
|
||||
|| errorStatus == COREWEBVIEW2_WEB_ERROR_STATUS_OPERATION_CANCELED) // this error seems to happen erroneously so ignore
|
||||
{
|
||||
owner.pageFinishedLoading (uriString);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto errorString = "Error code: " + String (errorStatus);
|
||||
|
||||
if (owner.pageLoadHadNetworkError (errorString))
|
||||
owner.goToURL ("data:text/plain;charset=UTF-8," + errorString);
|
||||
}
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}).Get(), &navigationCompletedToken);
|
||||
|
||||
webView->AddWebResourceRequestedFilter (L"*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_DOCUMENT);
|
||||
|
||||
webView->add_WebResourceRequested (Callback<ICoreWebView2WebResourceRequestedEventHandler> (
|
||||
[this] (ICoreWebView2*, ICoreWebView2WebResourceRequestedEventArgs* args) -> HRESULT
|
||||
{
|
||||
if (urlRequest.url.isEmpty())
|
||||
return S_OK;
|
||||
|
||||
ComSmartPtr<ICoreWebView2WebResourceRequest> request;
|
||||
args->get_Request (request.resetAndGetPointerAddress());
|
||||
|
||||
auto uriString = getUriStringFromArgs<ICoreWebView2WebResourceRequest> (request);
|
||||
|
||||
if (uriString == urlRequest.url
|
||||
|| (uriString.endsWith ("/") && uriString.upToLastOccurrenceOf ("/", false, false) == urlRequest.url))
|
||||
{
|
||||
String method ("GET");
|
||||
|
||||
if (! urlRequest.postData.isEmpty())
|
||||
{
|
||||
method = "POST";
|
||||
|
||||
ComSmartPtr<IStream> content (SHCreateMemStream ((BYTE*) urlRequest.postData.getData(),
|
||||
(UINT) urlRequest.postData.getSize()));
|
||||
request->put_Content (content);
|
||||
}
|
||||
|
||||
if (! urlRequest.headers.isEmpty())
|
||||
{
|
||||
ComSmartPtr<ICoreWebView2HttpRequestHeaders> headers;
|
||||
request->get_Headers (headers.resetAndGetPointerAddress());
|
||||
|
||||
for (auto& header : urlRequest.headers)
|
||||
{
|
||||
headers->SetHeader (header.upToFirstOccurrenceOf (":", false, false).trim().toWideCharPointer(),
|
||||
header.fromFirstOccurrenceOf (":", false, false).trim().toWideCharPointer());
|
||||
}
|
||||
}
|
||||
|
||||
request->put_Method (method.toWideCharPointer());
|
||||
|
||||
urlRequest = {};
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}).Get(), &webResourceRequestedToken);
|
||||
}
|
||||
}
|
||||
|
||||
void removeEventHandlers()
|
||||
{
|
||||
if (webView != nullptr)
|
||||
{
|
||||
if (navigationStartingToken.value != 0)
|
||||
webView->remove_NavigationStarting (navigationStartingToken);
|
||||
|
||||
if (newWindowRequestedToken.value != 0)
|
||||
webView->remove_NewWindowRequested (newWindowRequestedToken);
|
||||
|
||||
if (windowCloseRequestedToken.value != 0)
|
||||
webView->remove_WindowCloseRequested (windowCloseRequestedToken);
|
||||
|
||||
if (navigationCompletedToken.value != 0)
|
||||
webView->remove_NavigationCompleted (navigationCompletedToken);
|
||||
|
||||
if (webResourceRequestedToken.value != 0)
|
||||
{
|
||||
webView->RemoveWebResourceRequestedFilter (L"*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_DOCUMENT);
|
||||
webView->remove_WebResourceRequested (webResourceRequestedToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setWebViewPreferences()
|
||||
{
|
||||
ComSmartPtr<ICoreWebView2Controller2> controller2;
|
||||
webViewController->QueryInterface (controller2.resetAndGetPointerAddress());
|
||||
|
||||
if (controller2 != nullptr)
|
||||
{
|
||||
const auto bgColour = preferences.getBackgroundColour();
|
||||
|
||||
controller2->put_DefaultBackgroundColor ({ (BYTE) bgColour.getAlpha(),
|
||||
(BYTE) bgColour.getRed(),
|
||||
(BYTE) bgColour.getGreen(),
|
||||
(BYTE) bgColour.getBlue() });
|
||||
}
|
||||
|
||||
ComSmartPtr<ICoreWebView2Settings> settings;
|
||||
webView->get_Settings (settings.resetAndGetPointerAddress());
|
||||
|
||||
if (settings == nullptr)
|
||||
{
|
||||
settings->put_IsStatusBarEnabled (! preferences.getIsStatusBarDisabled());
|
||||
settings->put_IsBuiltInErrorPageEnabled (! preferences.getIsBuiltInErrorPageDisabled());
|
||||
}
|
||||
}
|
||||
|
||||
bool createWebViewEnvironment()
|
||||
{
|
||||
using CreateWebViewEnvironmentWithOptionsFunc = HRESULT (*) (PCWSTR, PCWSTR,
|
||||
ICoreWebView2EnvironmentOptions*,
|
||||
ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler*);
|
||||
|
||||
auto dllPath = preferences.getDLLLocation().getFullPathName();
|
||||
|
||||
if (dllPath.isEmpty())
|
||||
dllPath = "WebView2Loader.dll";
|
||||
|
||||
webView2LoaderHandle = LoadLibraryA (dllPath.toUTF8());
|
||||
|
||||
if (webView2LoaderHandle == nullptr)
|
||||
return false;
|
||||
|
||||
auto* createWebViewEnvironmentWithOptions = (CreateWebViewEnvironmentWithOptionsFunc) GetProcAddress (webView2LoaderHandle,
|
||||
"CreateCoreWebView2EnvironmentWithOptions");
|
||||
if (createWebViewEnvironmentWithOptions == nullptr)
|
||||
{
|
||||
// failed to load WebView2Loader.dll
|
||||
jassertfalse;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto options = Microsoft::WRL::Make<CoreWebView2EnvironmentOptions>();
|
||||
const auto userDataFolder = preferences.getUserDataFolder().getFullPathName();
|
||||
|
||||
auto hr = createWebViewEnvironmentWithOptions (nullptr,
|
||||
userDataFolder.isNotEmpty() ? userDataFolder.toWideCharPointer() : nullptr,
|
||||
options.Get(),
|
||||
Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
|
||||
[weakThis = WeakReference<WebView2> { this }] (HRESULT, ICoreWebView2Environment* env) -> HRESULT
|
||||
{
|
||||
if (weakThis != nullptr)
|
||||
weakThis->webViewEnvironment = env;
|
||||
|
||||
return S_OK;
|
||||
}).Get());
|
||||
|
||||
return SUCCEEDED (hr);
|
||||
}
|
||||
|
||||
void createWebView()
|
||||
{
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
isCreating = true;
|
||||
|
||||
WeakReference<WebView2> weakThis (this);
|
||||
|
||||
webViewEnvironment->CreateCoreWebView2Controller ((HWND) peer->getNativeHandle(),
|
||||
Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler> (
|
||||
[weakThis = WeakReference<WebView2> { this }] (HRESULT, ICoreWebView2Controller* controller) -> HRESULT
|
||||
{
|
||||
if (weakThis != nullptr)
|
||||
{
|
||||
weakThis->isCreating = false;
|
||||
|
||||
if (controller != nullptr)
|
||||
{
|
||||
weakThis->webViewController = controller;
|
||||
controller->get_CoreWebView2 (weakThis->webView.resetAndGetPointerAddress());
|
||||
|
||||
if (weakThis->webView != nullptr)
|
||||
{
|
||||
weakThis->addEventHandlers();
|
||||
weakThis->setWebViewPreferences();
|
||||
weakThis->componentMovedOrResized (true, true);
|
||||
|
||||
if (weakThis->urlRequest.url.isNotEmpty())
|
||||
weakThis->webView->Navigate (weakThis->urlRequest.url.toWideCharPointer());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}).Get());
|
||||
}
|
||||
}
|
||||
|
||||
void closeWebView()
|
||||
{
|
||||
if (webViewController != nullptr)
|
||||
{
|
||||
webViewController->Close();
|
||||
webViewController = nullptr;
|
||||
webView = nullptr;
|
||||
}
|
||||
|
||||
webViewEnvironment = nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void setControlBounds (Rectangle<int> newBounds) const
|
||||
{
|
||||
if (webViewController != nullptr)
|
||||
{
|
||||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE
|
||||
if (auto* peer = owner.getTopLevelComponent()->getPeer())
|
||||
newBounds = (newBounds.toDouble() * peer->getPlatformScaleFactor()).toNearestInt();
|
||||
#endif
|
||||
|
||||
webViewController->put_Bounds({ newBounds.getX(), newBounds.getY(),
|
||||
newBounds.getRight(), newBounds.getBottom() });
|
||||
}
|
||||
}
|
||||
|
||||
void setControlVisible (bool shouldBeVisible) const
|
||||
{
|
||||
if (webViewController != nullptr)
|
||||
webViewController->put_IsVisible (shouldBeVisible);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
WebBrowserComponent& owner;
|
||||
WebView2Preferences preferences;
|
||||
|
||||
HMODULE webView2LoaderHandle = nullptr;
|
||||
|
||||
ComSmartPtr<ICoreWebView2Environment> webViewEnvironment;
|
||||
ComSmartPtr<ICoreWebView2Controller> webViewController;
|
||||
ComSmartPtr<ICoreWebView2> webView;
|
||||
|
||||
EventRegistrationToken navigationStartingToken { 0 },
|
||||
newWindowRequestedToken { 0 },
|
||||
windowCloseRequestedToken { 0 },
|
||||
navigationCompletedToken { 0 },
|
||||
webResourceRequestedToken { 0 };
|
||||
|
||||
struct URLRequest
|
||||
{
|
||||
String url;
|
||||
StringArray headers;
|
||||
MemoryBlock postData;
|
||||
};
|
||||
|
||||
URLRequest urlRequest;
|
||||
|
||||
bool isCreating = false;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_WEAK_REFERENCEABLE (WebView2)
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebView2)
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
class WebBrowserComponent::Pimpl
|
||||
{
|
||||
public:
|
||||
Pimpl (WebBrowserComponent& owner,
|
||||
const WebView2Preferences& preferences,
|
||||
bool useWebView2)
|
||||
{
|
||||
if (useWebView2)
|
||||
{
|
||||
#if JUCE_USE_WIN_WEBVIEW2
|
||||
try
|
||||
{
|
||||
internal.reset (new WebView2 (owner, preferences));
|
||||
}
|
||||
catch (const std::runtime_error&) {}
|
||||
#endif
|
||||
}
|
||||
|
||||
ignoreUnused (preferences);
|
||||
|
||||
if (internal == nullptr)
|
||||
internal.reset (new Win32WebView (owner));
|
||||
}
|
||||
|
||||
InternalWebViewType& getInternalWebView()
|
||||
{
|
||||
return *internal;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<InternalWebViewType> internal;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
WebBrowserComponent::WebBrowserComponent (bool unloadWhenHidden)
|
||||
: browser (new Pimpl (*this, {}, false)),
|
||||
unloadPageWhenHidden (unloadWhenHidden)
|
||||
{
|
||||
setOpaque (true);
|
||||
}
|
||||
|
||||
WebBrowserComponent::WebBrowserComponent (ConstructWithoutPimpl args)
|
||||
: unloadPageWhenHidden (args.unloadWhenHidden)
|
||||
{
|
||||
setOpaque (true);
|
||||
}
|
||||
|
||||
WebBrowserComponent::~WebBrowserComponent()
|
||||
{
|
||||
}
|
||||
|
||||
WindowsWebView2WebBrowserComponent::WindowsWebView2WebBrowserComponent (bool unloadWhenHidden,
|
||||
const WebView2Preferences& preferences)
|
||||
: WebBrowserComponent (ConstructWithoutPimpl { unloadWhenHidden })
|
||||
{
|
||||
browser = std::make_unique<Pimpl> (*this, preferences, true);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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->getInternalWebView().hasBrowserBeenCreated())
|
||||
checkWindowAssociation();
|
||||
|
||||
browser->getInternalWebView().goToURL (url, headers, postData);
|
||||
}
|
||||
|
||||
void WebBrowserComponent::stop()
|
||||
{
|
||||
browser->getInternalWebView().stop();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::goBack()
|
||||
{
|
||||
lastURL.clear();
|
||||
blankPageShown = false;
|
||||
|
||||
browser->getInternalWebView().goBack();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::goForward()
|
||||
{
|
||||
lastURL.clear();
|
||||
|
||||
browser->getInternalWebView().goForward();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::refresh()
|
||||
{
|
||||
browser->getInternalWebView().refresh();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void WebBrowserComponent::paint (Graphics& g)
|
||||
{
|
||||
if (! browser->getInternalWebView().hasBrowserBeenCreated())
|
||||
{
|
||||
g.fillAll (Colours::white);
|
||||
checkWindowAssociation();
|
||||
}
|
||||
}
|
||||
|
||||
void WebBrowserComponent::checkWindowAssociation()
|
||||
{
|
||||
if (isShowing())
|
||||
{
|
||||
if (! browser->getInternalWebView().hasBrowserBeenCreated() && getPeer() != nullptr)
|
||||
{
|
||||
browser->getInternalWebView().createBrowser();
|
||||
reloadLastURL();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (blankPageShown)
|
||||
goBack();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (browser != nullptr && unloadPageWhenHidden && ! 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->getInternalWebView().goToURL ("about:blank", nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebBrowserComponent::reloadLastURL()
|
||||
{
|
||||
if (lastURL.isNotEmpty())
|
||||
{
|
||||
goToURL (lastURL, &lastHeaders, &lastPostData);
|
||||
lastURL.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void WebBrowserComponent::parentHierarchyChanged()
|
||||
{
|
||||
checkWindowAssociation();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::resized()
|
||||
{
|
||||
browser->getInternalWebView().setWebViewSize (getWidth(), getHeight());
|
||||
}
|
||||
|
||||
void WebBrowserComponent::visibilityChanged()
|
||||
{
|
||||
checkWindowAssociation();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::focusGained (FocusChangeType)
|
||||
{
|
||||
browser->getInternalWebView().focusGained();
|
||||
}
|
||||
|
||||
void WebBrowserComponent::clearCookies()
|
||||
{
|
||||
HeapBlock<::INTERNET_CACHE_ENTRY_INFOA> entry;
|
||||
::DWORD entrySize = sizeof (::INTERNET_CACHE_ENTRY_INFOA);
|
||||
::HANDLE urlCacheHandle = ::FindFirstUrlCacheEntryA ("cookie:", entry.getData(), &entrySize);
|
||||
|
||||
if (urlCacheHandle == nullptr && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
|
||||
{
|
||||
entry.realloc (1, entrySize);
|
||||
urlCacheHandle = ::FindFirstUrlCacheEntryA ("cookie:", entry.getData(), &entrySize);
|
||||
}
|
||||
|
||||
if (urlCacheHandle != nullptr)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
::DeleteUrlCacheEntryA (entry.getData()->lpszSourceUrlName);
|
||||
|
||||
if (::FindNextUrlCacheEntryA (urlCacheHandle, entry.getData(), &entrySize) == 0)
|
||||
{
|
||||
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
|
||||
{
|
||||
entry.realloc (1, entrySize);
|
||||
|
||||
if (::FindNextUrlCacheEntryA (urlCacheHandle, entry.getData(), &entrySize) != 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
FindCloseUrlCache (urlCacheHandle);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
Reference in New Issue
Block a user