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:
essej
2022-04-18 17:51:22 -04:00
parent 63e175fee6
commit 25bd5d8adb
3210 changed files with 1045392 additions and 0 deletions

View 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 ();
}
}

View 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 ();
}
}

View File

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

View File

@ -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());
}
}

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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