build(deps): vendor JUCE 7.0.12

This commit is contained in:
2026-09-08 14:55:21 +02:00
parent 5b99f51bd1
commit d6705ab29f
3591 changed files with 1218267 additions and 0 deletions
@@ -0,0 +1,132 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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 ();
}
}
@@ -0,0 +1,132 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
package com.rmsl.juce;
import android.graphics.Bitmap;
import android.net.http.SslError;
import android.os.Message;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.webkit.SslErrorHandler;
import android.webkit.WebChromeClient;
//==============================================================================
public class JuceWebView21
{
static public class Client extends WebViewClient
{
public Client (long hostToUse)
{
host = hostToUse;
}
public void hostDeleted ()
{
synchronized (hostLock)
{
host = 0;
}
}
public void onPageFinished (WebView view, String url)
{
if (host == 0)
return;
webViewPageLoadFinished (host, view, url);
}
public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error)
{
if (host == 0)
return;
webViewReceivedSslError (host, view, handler, error);
}
public void onPageStarted (WebView view, String url, Bitmap favicon)
{
if (host != 0)
webViewPageLoadStarted (host, view, url);
}
public WebResourceResponse shouldInterceptRequest (WebView view, String url)
{
synchronized (hostLock)
{
if (host != 0)
{
boolean shouldLoad = webViewPageLoadStarted (host, view, url);
if (shouldLoad)
return null;
}
}
return new WebResourceResponse ("text/html", null, null);
}
private native boolean webViewPageLoadStarted (long host, WebView view, String url);
private native void webViewPageLoadFinished (long host, WebView view, String url);
private native void webViewReceivedSslError (long host, WebView view, SslErrorHandler handler, SslError error);
private long host;
private final Object hostLock = new Object ();
}
static public class ChromeClient extends WebChromeClient
{
public ChromeClient (long hostToUse)
{
host = hostToUse;
}
@Override
public void onCloseWindow (WebView window)
{
webViewCloseWindowRequest (host, window);
}
@Override
public boolean onCreateWindow (WebView view, boolean isDialog,
boolean isUserGesture, Message resultMsg)
{
webViewCreateWindowRequest (host, view);
return false;
}
private native void webViewCloseWindowRequest (long host, WebView view);
private native void webViewCreateWindowRequest (long host, WebView view);
private long host;
private final Object hostLock = new Object ();
}
}
@@ -0,0 +1,41 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
package com.rmsl.juce;
import com.google.firebase.iid.*;
public final class JuceFirebaseInstanceIdService extends FirebaseInstanceIdService
{
private native void firebaseInstanceIdTokenRefreshed (String token);
@Override
public void onTokenRefresh()
{
String token = FirebaseInstanceId.getInstance().getToken();
firebaseInstanceIdTokenRefreshed (token);
}
}
@@ -0,0 +1,60 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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());
}
}
@@ -0,0 +1,544 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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 final : public ComBaseClassHelper<IStorage>
{
JuceIStorage() = default;
JUCE_COMRESULT CreateStream (const WCHAR*, DWORD, DWORD, DWORD, IStream**) override { return E_NOTIMPL; }
JUCE_COMRESULT OpenStream (const WCHAR*, void*, DWORD, DWORD, IStream**) override { return E_NOTIMPL; }
JUCE_COMRESULT CreateStorage (const WCHAR*, DWORD, DWORD, DWORD, IStorage**) override { return E_NOTIMPL; }
JUCE_COMRESULT OpenStorage (const WCHAR*, IStorage*, DWORD, SNB, DWORD, IStorage**) override { return E_NOTIMPL; }
JUCE_COMRESULT CopyTo (DWORD, IID const*, SNB, IStorage*) override { return E_NOTIMPL; }
JUCE_COMRESULT MoveElementTo (const OLECHAR*,IStorage*, const OLECHAR*, DWORD) override { return E_NOTIMPL; }
JUCE_COMRESULT Commit (DWORD) override { return E_NOTIMPL; }
JUCE_COMRESULT Revert() override { return E_NOTIMPL; }
JUCE_COMRESULT EnumElements (DWORD, void*, DWORD, IEnumSTATSTG**) override { return E_NOTIMPL; }
JUCE_COMRESULT DestroyElement (const OLECHAR*) override { return E_NOTIMPL; }
JUCE_COMRESULT RenameElement (const WCHAR*, const WCHAR*) override { return E_NOTIMPL; }
JUCE_COMRESULT SetElementTimes (const WCHAR*, FILETIME const*, FILETIME const*, FILETIME const*) override { return E_NOTIMPL; }
JUCE_COMRESULT SetClass (REFCLSID) override { return S_OK; }
JUCE_COMRESULT SetStateBits (DWORD, DWORD) override { return E_NOTIMPL; }
JUCE_COMRESULT Stat (STATSTG*, DWORD) override { return E_NOTIMPL; }
};
//==============================================================================
struct JuceOleInPlaceFrame final : public ComBaseClassHelper<IOleInPlaceFrame>
{
JuceOleInPlaceFrame (HWND hwnd) : window (hwnd) {}
JUCE_COMRESULT GetWindow (HWND* lphwnd) override { *lphwnd = window; return S_OK; }
JUCE_COMRESULT ContextSensitiveHelp (BOOL) override { return E_NOTIMPL; }
JUCE_COMRESULT GetBorder (LPRECT) override { return E_NOTIMPL; }
JUCE_COMRESULT RequestBorderSpace (LPCBORDERWIDTHS) override { return E_NOTIMPL; }
JUCE_COMRESULT SetBorderSpace (LPCBORDERWIDTHS) override { return E_NOTIMPL; }
JUCE_COMRESULT SetActiveObject (IOleInPlaceActiveObject* a, LPCOLESTR) override { activeObject = a; return S_OK; }
JUCE_COMRESULT InsertMenus (HMENU, LPOLEMENUGROUPWIDTHS) override { return E_NOTIMPL; }
JUCE_COMRESULT SetMenu (HMENU, HOLEMENU, HWND) override { return S_OK; }
JUCE_COMRESULT RemoveMenus (HMENU) override { return E_NOTIMPL; }
JUCE_COMRESULT SetStatusText (LPCOLESTR) override { return S_OK; }
JUCE_COMRESULT EnableModeless (BOOL) override { return S_OK; }
JUCE_COMRESULT TranslateAccelerator (LPMSG, WORD) override { return E_NOTIMPL; }
HRESULT OfferKeyTranslation (LPMSG lpmsg)
{
if (activeObject != nullptr)
return activeObject->TranslateAcceleratorW (lpmsg);
return S_FALSE;
}
HWND window;
ComSmartPtr<IOleInPlaceActiveObject> activeObject;
};
//==============================================================================
struct JuceIOleInPlaceSite final : public ComBaseClassHelper<IOleInPlaceSite>
{
JuceIOleInPlaceSite (HWND hwnd)
: window (hwnd),
frame (new JuceOleInPlaceFrame (window))
{}
~JuceIOleInPlaceSite() override
{
frame->Release();
}
JUCE_COMRESULT GetWindow (HWND* lphwnd) override { *lphwnd = window; return S_OK; }
JUCE_COMRESULT ContextSensitiveHelp (BOOL) override { return E_NOTIMPL; }
JUCE_COMRESULT CanInPlaceActivate() override { return S_OK; }
JUCE_COMRESULT OnInPlaceActivate() override { return S_OK; }
JUCE_COMRESULT OnUIActivate() override { return S_OK; }
JUCE_COMRESULT GetWindowContext (LPOLEINPLACEFRAME* lplpFrame, LPOLEINPLACEUIWINDOW* lplpDoc, LPRECT, LPRECT, LPOLEINPLACEFRAMEINFO lpFrameInfo) override
{
/* 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) override { return E_NOTIMPL; }
JUCE_COMRESULT OnUIDeactivate (BOOL) override { return S_OK; }
JUCE_COMRESULT OnInPlaceDeactivate() override { return S_OK; }
JUCE_COMRESULT DiscardUndoState() override { return E_NOTIMPL; }
JUCE_COMRESULT DeactivateAndUndo() override { return E_NOTIMPL; }
JUCE_COMRESULT OnPosRectChange (LPCRECT) override { return S_OK; }
LRESULT offerEventToActiveXControl (::MSG& msg)
{
if (frame != nullptr)
return frame->OfferKeyTranslation (&msg);
return S_FALSE;
}
HWND window;
JuceOleInPlaceFrame* frame;
};
//==============================================================================
struct JuceIOleClientSite final : public ComBaseClassHelper<IOleClientSite>
{
JuceIOleClientSite (HWND window) : inplaceSite (new JuceIOleInPlaceSite (window))
{}
~JuceIOleClientSite() override
{
inplaceSite->Release();
if (dispatchEventHandler != nullptr)
{
dispatchEventHandler->Release();
dispatchEventHandler = nullptr;
}
}
JUCE_COMRESULT QueryInterface (REFIID type, void** result) override
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
if (type == __uuidof (IOleInPlaceSite))
{
inplaceSite->AddRef();
*result = static_cast<IOleInPlaceSite*> (inplaceSite);
return S_OK;
}
else if (type == __uuidof (IDispatch) && dispatchEventHandler != nullptr)
{
dispatchEventHandler->AddRef();
*result = dispatchEventHandler;
return S_OK;
}
return ComBaseClassHelper <IOleClientSite>::QueryInterface (type, result);
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
JUCE_COMRESULT SaveObject() override { return E_NOTIMPL; }
JUCE_COMRESULT GetMoniker (DWORD, DWORD, IMoniker**) override { return E_NOTIMPL; }
JUCE_COMRESULT GetContainer (LPOLECONTAINER* ppContainer) override { *ppContainer = nullptr; return E_NOINTERFACE; }
JUCE_COMRESULT ShowObject() override { return S_OK; }
JUCE_COMRESULT OnShowWindow (BOOL) override { return E_NOTIMPL; }
JUCE_COMRESULT RequestNewObjectLayout() override { return E_NOTIMPL; }
LRESULT offerEventToActiveXControl (::MSG& msg)
{
if (inplaceSite != nullptr)
return inplaceSite->offerEventToActiveXControl (msg);
return S_FALSE;
}
void setEventHandler (void* eventHandler)
{
IDispatch* newEventHandler = nullptr;
if (eventHandler != nullptr)
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
auto iidIDispatch = __uuidof (IDispatch);
if (static_cast<IUnknown*> (eventHandler)->QueryInterface (iidIDispatch, (void**) &newEventHandler) != S_OK
|| newEventHandler == nullptr)
return;
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
if (dispatchEventHandler != nullptr)
dispatchEventHandler->Release();
dispatchEventHandler = newEventHandler;
}
JuceIOleInPlaceSite* inplaceSite;
IDispatch* dispatchEventHandler = nullptr;
};
//==============================================================================
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::defaultPressure,
MouseInputSource::defaultOrientation,
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 the wndproc of the ActiveX HWND isn't set back to it's original
// wndproc, then clientSite will leak when control is released
if (controlHWND != nullptr)
SetWindowLongPtr ((HWND) controlHWND, GWLP_WNDPROC, (LONG_PTR) originalWndProc);
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 = std::move (newControl);
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;
}
void ActiveXControlComponent::setEventHandler (void* eventHandler)
{
if (control->clientSite != nullptr)
control->clientSite->setEventHandler (eventHandler);
}
LRESULT juce_offerEventToActiveXControl (::MSG& msg);
LRESULT juce_offerEventToActiveXControl (::MSG& msg)
{
if (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST)
return ActiveXControlComponent::offerEventToActiveXControlStatic (&msg);
return S_FALSE;
}
} // namespace juce
@@ -0,0 +1,190 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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()
{
AccessibilityHandler::setNativeChildForComponent (*this, nullptr);
}
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));
AccessibilityHandler::setNativeChildForComponent (*this, getView());
}
else
{
AccessibilityHandler::setNativeChildForComponent (*this, nullptr);
}
}
}
void* AndroidViewComponent::getView() const
{
return pimpl == nullptr ? nullptr : (void*) pimpl->view;
}
void AndroidViewComponent::resizeToFitView()
{
if (pimpl != nullptr)
setBounds (pimpl->getViewBounds());
}
void AndroidViewComponent::paint (Graphics&) {}
std::unique_ptr<AccessibilityHandler> AndroidViewComponent::createAccessibilityHandler()
{
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
}
} // namespace juce
@@ -0,0 +1,276 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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_MAX_ALLOWED >= MAC_OS_VERSION_12_0
if (@available (macOS 12.0, *))
return kIOMainPortDefault;
#endif
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
return kIOMasterPortDefault;
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}();
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)
{
[[maybe_unused]] HRESULT hr = (*cfPlugInInterface)->QueryInterface (cfPlugInInterface,
CFUUIDGetUUIDBytes (kIOHIDDeviceInterfaceID),
device);
(*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
@@ -0,0 +1,181 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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());
}
void HWNDComponent::updateHWNDBounds()
{
if (pimpl != nullptr)
pimpl->componentMovedOrResized (true, true);
}
} // namespace juce
@@ -0,0 +1,197 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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 NSViewAttachment final : public ReferenceCountedObject,
public ComponentMovementWatcher
{
public:
NSViewAttachment (NSView* v, Component& comp)
: ComponentMovementWatcher (&comp),
view (v), owner (comp)
{
[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 (std::exchange (currentPeer, peer) != peer)
{
if (peer != nullptr)
{
auto peerView = (NSView*) peer->getNativeHandle();
[peerView addSubview: view];
if (@available (macOS 10.10, *))
[view setAccessibilityParent:static_cast<id> (owner.getAccessibilityHandler()->getNativeImplementation())];
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 = nullptr;
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()
{
AccessibilityHandler::setNativeChildForComponent (*this, nullptr);
}
void NSViewComponent::setView (void* view)
{
if (view != getView())
{
auto old = attachment;
attachment = nullptr;
if (view != nullptr)
attachment = attachViewToComponent (*this, view);
old = nullptr;
AccessibilityHandler::setNativeChildForComponent (*this, getView());
}
}
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::resizeViewToFit()
{
if (attachment != nullptr)
static_cast<NSViewAttachment*> (attachment.get())->componentMovedOrResized (true, true);
}
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);
}
std::unique_ptr<AccessibilityHandler> NSViewComponent::createAccessibilityHandler()
{
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
}
} // namespace juce
@@ -0,0 +1,111 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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.
==============================================================================
*/
#if JUCE_MAC
namespace juce
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
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);
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;
};
} // namespace juce
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,919 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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 PushNotificationsDelegateDetails
{
//==============================================================================
using Action = PushNotifications::Settings::Action;
using Category = PushNotifications::Settings::Category;
static void* actionToNSAction (const Action& a)
{
if (@available (iOS 10, *))
{
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)];
}
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
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 = varToNSDictionary (a.parameters);
action.activationMode = a.triggerInBackground ? UIUserNotificationActivationModeBackground
: UIUserNotificationActivationModeForeground;
action.destructive = (bool) a.destructive;
[action autorelease];
return action;
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
static void* categoryToNSCategory (const Category& c)
{
if (@available (iOS 10, *))
{
auto actions = [NSMutableArray arrayWithCapacity: (NSUInteger) c.actions.size()];
for (const auto& a : c.actions)
{
auto* action = (UNNotificationAction*) actionToNSAction (a);
[actions addObject: action];
}
return [UNNotificationCategory categoryWithIdentifier: juceStringToNS (c.identifier)
actions: actions
intentIdentifiers: @[]
options: c.sendDismissAction ? UNNotificationCategoryOptionCustomDismissAction : 0];
}
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
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);
[actions addObject: action];
}
[category setActions: actions forContext: UIUserNotificationActionContextDefault];
[category setActions: actions forContext: UIUserNotificationActionContextMinimal];
[category autorelease];
return category;
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
//==============================================================================
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
static 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: (double) triggerTime.toMilliseconds() / 1000.0];
notification.userInfo = varToNSDictionary (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;
}
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
static 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*) varToNSDictionary (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;
}
static 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 {};
}
//==============================================================================
static 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());
}
//==============================================================================
static 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.;
}
static 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;
}
static PushNotifications::Notification unNotificationToJuceNotification (UNNotification* n)
{
return unNotificationRequestToJuceNotification (n.request);
}
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
static 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;
}
static 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;
}
static 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;
}
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
static 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;
}
static 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;
}
static 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;
}
private:
~PushNotificationsDelegateDetails() = delete;
};
//==============================================================================
bool PushNotifications::Notification::isValid() const noexcept
{
if (@available (iOS 10, *))
return title.isNotEmpty() && body.isNotEmpty() && identifier.isNotEmpty() && category.isNotEmpty();
return title.isNotEmpty() && body.isNotEmpty() && category.isNotEmpty();
}
//==============================================================================
struct PushNotifications::Pimpl
{
Pimpl (PushNotifications& p)
: owner (p)
{
Class::setThis (delegate.get(), this);
auto 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
}
void requestPermissionsWithSettings (const PushNotifications::Settings& settingsToUse)
{
settings = settingsToUse;
auto categories = [NSMutableSet setWithCapacity: (NSUInteger) settings.categories.size()];
if (@available (iOS 10, *))
{
for (const auto& c : settings.categories)
{
auto* category = (UNNotificationCategory*) PushNotificationsDelegateDetails::categoryToNSCategory (c);
[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();
}];
}
else
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
for (const auto& c : settings.categories)
{
auto* category = (UIUserNotificationCategory*) PushNotificationsDelegateDetails::categoryToNSCategory (c);
[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];
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
[[UIApplication sharedApplication] registerForRemoteNotifications];
}
void requestSettingsUsed()
{
if (@available (iOS 10, *))
{
[[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); });
}
];
}];
}
else
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
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); });
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
}
bool areNotificationsEnabled() const { return true; }
void sendLocalNotification (const Notification& n)
{
if (@available (iOS 10, *))
{
UNNotificationRequest* request = PushNotificationsDelegateDetails::juceNotificationToUNNotificationRequest (n);
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest: request
withCompletionHandler: ^(NSError* error)
{
jassert (error == nil);
if (error != nil)
NSLog (nsStringLiteral ("addNotificationRequest error: %@"), error);
}];
}
else
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
auto* notification = PushNotificationsDelegateDetails::juceNotificationToUILocalNotification (n);
[[UIApplication sharedApplication] scheduleLocalNotification: notification];
[notification release];
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
}
void getDeliveredNotifications() const
{
if (@available (iOS 10, *))
{
[[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); });
}];
}
else
{
// Not supported on this platform
jassertfalse;
owner.listeners.call ([] (Listener& l) { l.deliveredNotificationsListReceived ({}); });
}
}
void removeAllDeliveredNotifications()
{
if (@available (iOS 10, *))
{
[[UNUserNotificationCenter currentNotificationCenter] removeAllDeliveredNotifications];
}
else
{
// Not supported on this platform
jassertfalse;
}
}
void removeDeliveredNotification ([[maybe_unused]] const String& identifier)
{
if (@available (iOS 10, *))
{
NSArray<NSString*>* identifiers = [NSArray arrayWithObject: juceStringToNS (identifier)];
[[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers: identifiers];
}
else
{
// Not supported on this platform
jassertfalse;
}
}
void setupChannels ([[maybe_unused]] const Array<ChannelGroup>& groups, [[maybe_unused]] const Array<Channel>& channels)
{
}
void getPendingLocalNotifications() const
{
if (@available (iOS 10, *))
{
[[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); });
}
];
}
else
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
Array<PushNotifications::Notification> notifs;
for (UILocalNotification* n in [UIApplication sharedApplication].scheduledLocalNotifications)
notifs.add (PushNotificationsDelegateDetails::uiLocalNotificationToJuceNotification (n));
owner.listeners.call ([&] (Listener& l) { l.pendingLocalNotificationsListReceived (notifs); });
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
}
void removePendingLocalNotification (const String& identifier)
{
if (@available (iOS 10, *))
{
NSArray<NSString*>* identifiers = [NSArray arrayWithObject: juceStringToNS (identifier)];
[[UNUserNotificationCenter currentNotificationCenter] removePendingNotificationRequestsWithIdentifiers: identifiers];
}
else
{
// Not supported on this platform
jassertfalse;
}
}
void removeAllPendingLocalNotifications()
{
if (@available (iOS 10, *))
{
[[UNUserNotificationCenter currentNotificationCenter] removeAllPendingNotificationRequests];
}
else
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
[[UIApplication sharedApplication] cancelAllLocalNotifications];
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
}
String getDeviceToken()
{
// You need to call requestPermissionsWithSettings() first.
jassert (initialised);
return deviceToken;
}
void subscribeToTopic ([[maybe_unused]] const String& topic) {}
void unsubscribeFromTopic ([[maybe_unused]] const String& topic) {}
void sendUpstreamMessage ([[maybe_unused]] const String& serverSenderId,
[[maybe_unused]] const String& collapseKey,
[[maybe_unused]] const String& messageId,
[[maybe_unused]] const String& messageType,
[[maybe_unused]] int timeToLive,
[[maybe_unused]] const StringPairArray& additionalData)
{
}
private:
//==============================================================================
void registeredForRemoteNotifications (NSData* deviceTokenToUse)
{
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 ([[maybe_unused]] NSError* error)
{
deviceToken.clear();
}
void didReceiveRemoteNotification (NSDictionary* userInfo)
{
auto n = PushNotificationsDelegateDetails::nsDictionaryToJuceNotification (userInfo);
owner.listeners.call ([&] (Listener& l) { l.handleNotification (false, n); });
}
void didReceiveRemoteNotificationFetchCompletionHandler (NSDictionary* userInfo,
void (^completionHandler)(UIBackgroundFetchResult result))
{
didReceiveRemoteNotification (userInfo);
completionHandler (UIBackgroundFetchResultNewData);
}
void handleActionForRemoteNotificationCompletionHandler (NSString* actionIdentifier,
NSDictionary* userInfo,
NSDictionary* responseInfo,
void (^completionHandler)())
{
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();
}
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
void didRegisterUserNotificationSettings (UIUserNotificationSettings*)
{
requestSettingsUsed();
}
void didReceiveLocalNotification (UILocalNotification* notification)
{
auto n = PushNotificationsDelegateDetails::uiLocalNotificationToJuceNotification (notification);
owner.listeners.call ([&] (Listener& l) { l.handleNotification (true, n); });
}
void handleActionForLocalNotificationCompletionHandler (NSString* actionIdentifier,
UILocalNotification* notification,
void (^completionHandler)())
{
handleActionForLocalNotificationWithResponseCompletionHandler (actionIdentifier,
notification,
nil,
completionHandler);
}
void handleActionForLocalNotificationWithResponseCompletionHandler (NSString* actionIdentifier,
UILocalNotification* notification,
NSDictionary* responseInfo,
void (^completionHandler)())
{
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();
}
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
void willPresentNotificationWithCompletionHandler ([[maybe_unused]] UNNotification* notification,
void (^completionHandler)(UNNotificationPresentationOptions options))
{
NSUInteger options = NSUInteger ((int)settings.allowBadge << 0
| (int)settings.allowSound << 1
| (int)settings.allowAlert << 2);
completionHandler (options);
}
void didReceiveNotificationResponseWithCompletionHandler (UNNotificationResponse* response,
void (^completionHandler)())
{
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();
}
//==============================================================================
struct Class final : public ObjCClass<NSObject<UIApplicationDelegate, UNUserNotificationCenterDelegate>>
{
Class()
: ObjCClass ("JucePushNotificationsDelegate_")
{
addIvar<Pimpl*> ("self");
addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), [] (id self, SEL, UIApplication*, NSData* data)
{
getThis (self).registeredForRemoteNotifications (data);
});
addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), [] (id self, SEL, UIApplication*, NSError* error)
{
getThis (self).failedToRegisterForRemoteNotifications (error);
});
addMethod (@selector (application:didReceiveRemoteNotification:), [] (id self, SEL, UIApplication*, NSDictionary* userInfo)
{
getThis (self).didReceiveRemoteNotification (userInfo);
});
addMethod (@selector (application:didReceiveRemoteNotification:fetchCompletionHandler:), [] (id self, SEL, UIApplication*, NSDictionary* userInfo, void (^completionHandler)(UIBackgroundFetchResult result))
{
getThis (self).didReceiveRemoteNotificationFetchCompletionHandler (userInfo, completionHandler);
});
addMethod (@selector (application:handleActionWithIdentifier:forRemoteNotification:withResponseInfo:completionHandler:), [] (id self, SEL, UIApplication*, NSString* actionIdentifier, NSDictionary* userInfo, NSDictionary* responseInfo, void (^completionHandler)())
{
getThis (self).handleActionForRemoteNotificationCompletionHandler (actionIdentifier, userInfo, responseInfo, completionHandler);
});
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
addMethod (@selector (application:didRegisterUserNotificationSettings:), [] (id self, SEL, UIApplication*, UIUserNotificationSettings* settingsToUse)
{
getThis (self).didRegisterUserNotificationSettings (settingsToUse);
});
addMethod (@selector (application:didReceiveLocalNotification:), [] (id self, SEL, UIApplication*, UILocalNotification* notification)
{
getThis (self).didReceiveLocalNotification (notification);
});
addMethod (@selector (application:handleActionWithIdentifier:forLocalNotification:completionHandler:), [] (id self, SEL, UIApplication*, NSString* actionIdentifier, UILocalNotification* notification, void (^completionHandler)())
{
getThis (self).handleActionForLocalNotificationCompletionHandler (actionIdentifier, notification, completionHandler);
});
addMethod (@selector (application:handleActionWithIdentifier:forLocalNotification:withResponseInfo:completionHandler:), [] (id self, SEL, UIApplication*, NSString* actionIdentifier, UILocalNotification* notification, NSDictionary* responseInfo, void (^completionHandler)())
{
getThis (self). handleActionForLocalNotificationWithResponseCompletionHandler (actionIdentifier, notification, responseInfo, completionHandler);
});
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
addMethod (@selector (userNotificationCenter:willPresentNotification:withCompletionHandler:), [] (id self, SEL, UNUserNotificationCenter*, UNNotification* notification, void (^completionHandler)(UNNotificationPresentationOptions options))
{
getThis (self).willPresentNotificationWithCompletionHandler (notification, completionHandler);
});
addMethod (@selector (userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:), [] (id self, SEL, UNUserNotificationCenter*, UNNotificationResponse* response, void (^completionHandler)())
{
getThis (self).didReceiveNotificationResponseWithCompletionHandler (response, completionHandler);
});
registerClass();
}
//==============================================================================
static Pimpl& getThis (id self) { return *getIvar<Pimpl*> (self, "self"); }
static void setThis (id self, Pimpl* d) { object_setInstanceVariable (self, "self", d); }
};
//==============================================================================
static Class& getClass()
{
static Class c;
return c;
}
NSUniquePtr<NSObject<UIApplicationDelegate, UNUserNotificationCenterDelegate>> delegate { [getClass().createInstance() init] };
PushNotifications& owner;
bool initialised = false;
String deviceToken;
PushNotifications::Settings settings;
};
} // namespace juce
@@ -0,0 +1,563 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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;
//==============================================================================
static NSUserNotification* juceNotificationToNSUserNotification (const PushNotifications::Notification& n)
{
auto notification = [[NSUserNotification alloc] init];
notification.title = juceStringToNS (n.title);
notification.subtitle = juceStringToNS (n.subtitle);
notification.informativeText = juceStringToNS (n.body);
notification.userInfo = varToNSDictionary (n.properties);
auto triggerTime = Time::getCurrentTime() + RelativeTime (n.triggerIntervalSec);
notification.deliveryDate = [NSDate dateWithTimeIntervalSince1970: (double) triggerTime.toMilliseconds() / 1000.0];
if (n.repeat && n.triggerIntervalSec >= 60)
{
auto dateComponents = [[NSDateComponents alloc] init];
auto intervalSec = NSInteger (n.triggerIntervalSec);
dateComponents.second = intervalSec;
dateComponents.nanosecond = NSInteger ((n.triggerIntervalSec - (double) 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 (@available (macOS 10.9, *))
{
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 (@available (macOS 10.10, *))
{
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;
}
//==============================================================================
static PushNotifications::Notification nsUserNotificationToJuceNotification (NSUserNotification* n)
{
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 = (double) n.deliveryRepeatInterval.second + ((double) n.deliveryRepeatInterval.nanosecond / 1000000000.0);
}
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 (@available (macOS 10.9, *))
{
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 (@available (macOS 10.9, *))
{
if (n.hasReplyButton)
action.style = Action::text;
if (n.responsePlaceholder != nil)
action.textInputPlaceholder = nsStringToJuce (n.responsePlaceholder);
}
actions.add (action);
}
if (@available (macOS 10.10, *))
{
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;
}
//==============================================================================
static 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());
}
static 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 final : public ObjCClass<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>>
{
Class() : ObjCClass<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>> ("JucePushNotificationsDelegate_")
{
addIvar<PushNotificationsDelegate*> ("self");
addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), registeredForRemoteNotifications);
addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), failedToRegisterForRemoteNotifications);
addMethod (@selector (application:didReceiveRemoteNotification:), didReceiveRemoteNotification);
addMethod (@selector (userNotificationCenter:didDeliverNotification:), didDeliverNotification);
addMethod (@selector (userNotificationCenter:didActivateNotification:), didActivateNotification);
addMethod (@selector (userNotificationCenter:shouldPresentNotification:), shouldPresentNotification);
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 (@available (macOS 10.7, *))
{
settings = settingsToUse;
NSRemoteNotificationType types = NSUInteger ((bool) settings.allowBadge);
if (@available (macOS 10.8, *))
{
types |= (NSUInteger) ((settings.allowSound ? NSRemoteNotificationTypeSound : 0)
| (settings.allowAlert ? NSRemoteNotificationTypeAlert : 0));
}
[[NSApplication sharedApplication] registerForRemoteNotificationTypes: types];
}
}
void requestSettingsUsed()
{
if (@available (macOS 10.7, *))
{
settings.allowBadge = [NSApplication sharedApplication].enabledRemoteNotificationTypes & NSRemoteNotificationTypeBadge;
if (@available (macOS 10.8, *))
{
settings.allowSound = [NSApplication sharedApplication].enabledRemoteNotificationTypes & NSRemoteNotificationTypeSound;
settings.allowAlert = [NSApplication sharedApplication].enabledRemoteNotificationTypes & NSRemoteNotificationTypeAlert;
}
owner.listeners.call ([&] (Listener& l) { l.notificationSettingsReceived (settings); });
}
else
{
// no settings available
owner.listeners.call ([] (Listener& l) { l.notificationSettingsReceived ({}); });
}
}
bool areNotificationsEnabled() const { return true; }
void sendLocalNotification (const Notification& n)
{
auto* notification = PushNotificationsDelegateDetailsOsx::juceNotificationToNSUserNotification (n);
[[NSUserNotificationCenter defaultUserNotificationCenter] scheduleNotification: notification];
}
void getDeliveredNotifications() const
{
Array<PushNotifications::Notification> notifs;
for (NSUserNotification* n in [NSUserNotificationCenter defaultUserNotificationCenter].deliveredNotifications)
notifs.add (PushNotificationsDelegateDetailsOsx::nsUserNotificationToJuceNotification (n));
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);
[[NSUserNotificationCenter defaultUserNotificationCenter] removeDeliveredNotification: nsNotification];
}
void setupChannels ([[maybe_unused]] const Array<ChannelGroup>& groups, [[maybe_unused]] const Array<Channel>& channels)
{
}
void getPendingLocalNotifications() const
{
Array<PushNotifications::Notification> notifs;
for (NSUserNotification* n in [NSUserNotificationCenter defaultUserNotificationCenter].scheduledNotifications)
notifs.add (PushNotificationsDelegateDetailsOsx::nsUserNotificationToJuceNotification (n));
owner.listeners.call ([&] (Listener& l) { l.pendingLocalNotificationsListReceived (notifs); });
}
void removePendingLocalNotification (const String& identifier)
{
PushNotifications::Notification n;
n.identifier = identifier;
auto nsNotification = PushNotificationsDelegateDetailsOsx::juceNotificationToNSUserNotification (n);
[[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 ([[maybe_unused]] NSError* error) override
{
deviceToken.clear();
}
void didReceiveRemoteNotification (NSDictionary* userInfo) override
{
auto n = PushNotificationsDelegateDetailsOsx::nsDictionaryToJuceNotification (userInfo);
owner.listeners.call ([&] (Listener& l) { l.handleNotification (true, n); });
}
void didDeliverNotification ([[maybe_unused]] NSUserNotification* notification) override
{
}
void didActivateNotification (NSUserNotification* notification) override
{
auto n = PushNotificationsDelegateDetailsOsx::nsUserNotificationToJuceNotification (notification);
if (notification.activationType == NSUserNotificationActivationTypeContentsClicked)
{
owner.listeners.call ([&] (Listener& l) { l.handleNotification (notification.remote, n); });
}
else
{
const auto actionIdentifier = nsStringToJuce ([&]
{
if (@available (macOS 10.10, *))
if (notification.additionalActivationAction != nil)
return notification.additionalActivationAction.identifier;
return 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 ([[maybe_unused]] const String& topic) {}
void unsubscribeFromTopic ([[maybe_unused]] const String& topic) {}
void sendUpstreamMessage ([[maybe_unused]] const String& serverSenderId,
[[maybe_unused]] const String& collapseKey,
[[maybe_unused]] const String& messageId,
[[maybe_unused]] const String& messageType,
[[maybe_unused]] int timeToLive,
[[maybe_unused]] const StringPairArray& additionalData)
{
}
private:
PushNotifications& owner;
bool initialised = false;
String deviceToken;
PushNotifications::Settings settings;
};
} // namespace juce
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
@@ -0,0 +1,151 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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
@@ -0,0 +1,445 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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 ("-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 (ScaledImage (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 (ScaledImage (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 API_AVAILABLE (macos (10.10)) ButtonBasedStatusItem final : 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:);
[button sendActionOn: NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown | NSEventMaskScrollWheel];
}
~ButtonBasedStatusItem() override
{
[statusItem.get() button].image = nullptr;
}
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::defaultOrientation, MouseInputSource::defaultRotation,
MouseInputSource::defaultTiltX, MouseInputSource::defaultTiltY,
&owner, &owner, now, {}, now, 1, false });
owner.mouseUp ({ mouseSource, {},
eventMods.withoutMouseButtons(),
pressure,
MouseInputSource::defaultOrientation, MouseInputSource::defaultRotation,
MouseInputSource::defaultTiltX, MouseInputSource::defaultTiltY,
&owner, &owner, now, {}, now, 1, false });
}
else if (type == NSEventTypeMouseMoved)
{
owner.mouseMove (MouseEvent (mouseSource, {}, eventMods, pressure,
MouseInputSource::defaultOrientation, MouseInputSource::defaultRotation,
MouseInputSource::defaultTiltX, MouseInputSource::defaultTiltY,
&owner, &owner, now, {}, now, 1, false));
}
}
}
//==============================================================================
class ButtonEventForwarderClass final : public ObjCClass<NSObject>
{
public:
ButtonEventForwarderClass() : ObjCClass<NSObject> ("JUCEButtonEventForwarderClass_")
{
addIvar<ButtonBasedStatusItem*> ("owner");
addMethod (@selector (handleEvent:), handleEvent);
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 final : 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::defaultOrientation, MouseInputSource::defaultRotation,
MouseInputSource::defaultTiltX, MouseInputSource::defaultTiltY,
&owner, &owner, now, {}, now, 1, false));
owner.mouseUp (MouseEvent (mouseSource, {}, eventMods.withoutMouseButtons(), pressure,
MouseInputSource::defaultOrientation, MouseInputSource::defaultRotation,
MouseInputSource::defaultTiltX, MouseInputSource::defaultTiltY,
&owner, &owner, now, {}, now, 1, false));
}
else if (type == NSEventTypeMouseMoved)
{
owner.mouseMove (MouseEvent (mouseSource, {}, eventMods, pressure,
MouseInputSource::defaultOrientation, MouseInputSource::defaultRotation,
MouseInputSource::defaultTiltX, MouseInputSource::defaultTiltY,
&owner, &owner, now, {}, now, 1, false));
}
}
}
//==============================================================================
struct SystemTrayViewClass final : public ObjCClass<NSControl>
{
SystemTrayViewClass() : ObjCClass<NSControl> ("JUCESystemTrayView_")
{
addIvar<ViewBasedStatusItem*> ("owner");
addIvar<NSImage*> ("image");
addMethod (@selector (mouseDown:), handleEventDown);
addMethod (@selector (rightMouseDown:), handleEventDown);
addMethod (@selector (drawRect:), drawRect);
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
addMethod (@selector (frameChanged:), frameChanged);
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 (@available (macOS 10.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
@@ -0,0 +1,242 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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::defaultPressure, MouseInputSource::defaultOrientation,
MouseInputSource::defaultRotation, MouseInputSource::defaultTiltX, MouseInputSource::defaultTiltY,
&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
@@ -0,0 +1,142 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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()
{
AccessibilityHandler::setNativeChildForComponent (*this, nullptr);
}
void UIViewComponent::setView (void* view)
{
if (view != getView())
{
pimpl.reset();
if (view != nullptr)
pimpl.reset (new Pimpl ((UIView*) view, *this));
AccessibilityHandler::setNativeChildForComponent (*this, getView());
}
}
void* UIViewComponent::getView() const
{
return pimpl == nullptr ? nullptr : pimpl->view;
}
void UIViewComponent::resizeToFitView()
{
if (pimpl != nullptr)
setBounds (pimpl->getViewBounds());
}
void UIViewComponent::paint (Graphics&) {}
std::unique_ptr<AccessibilityHandler> UIViewComponent::createAccessibilityHandler()
{
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
}
} // namespace juce
@@ -0,0 +1,709 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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 uint8 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 uint8 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") \
METHOD (setUserAgentString, "setUserAgentString", "(Ljava/lang/String;)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, const String& userAgent)
: 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);
if (userAgent.isNotEmpty())
env->CallVoidMethod (settings, WebSettings.setUserAgentString, javaString (userAgent).get());
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 final : 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 (generatedCallback<&Pimpl::webViewReceivedHttpError>, "webViewReceivedHttpError", "(JLandroid/webkit/WebView;Landroid/webkit/WebResourceRequest;Landroid/webkit/WebResourceResponse;)V") \
CALLBACK (generatedCallback<&Pimpl::webViewPageLoadStarted>, "webViewPageLoadStarted", "(JLandroid/webkit/WebView;Ljava/lang/String;)Z") \
CALLBACK (generatedCallback<&Pimpl::webViewPageLoadFinished>, "webViewPageLoadFinished", "(JLandroid/webkit/WebView;Ljava/lang/String;)V") \
CALLBACK (generatedCallback<&Pimpl::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)
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
METHOD (constructor, "<init>", "(J)V") \
METHOD (hostDeleted, "hostDeleted", "()V") \
CALLBACK (generatedCallback<&Pimpl::webViewPageLoadStarted>, "webViewPageLoadStarted", "(JLandroid/webkit/WebView;Ljava/lang/String;)Z") \
CALLBACK (generatedCallback<&Pimpl::webViewPageLoadFinished>, "webViewPageLoadFinished", "(JLandroid/webkit/WebView;Ljava/lang/String;)V") \
CALLBACK (generatedCallback<&Pimpl::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)
#undef JNI_CLASS_MEMBERS
static jboolean webViewPageLoadStarted (JNIEnv*, Pimpl& t, jobject /*webView*/, jstring url)
{
return t.handlePageAboutToLoad (juceString (url));
}
static void webViewPageLoadFinished (JNIEnv*, Pimpl& t, jobject /*webView*/, jstring url)
{
t.owner.pageFinishedLoading (juceString (url));
}
static void webViewReceivedSslError (JNIEnv* env, Pimpl& t, jobject /*webView*/, jobject /*sslErrorHandler*/, jobject sslError)
{
const auto errorString = LocalRef<jstring> ((jstring) env->CallObjectMethod (sslError, SslError.toString));
t.owner.pageLoadHadNetworkError (juceString (errorString));
}
//==============================================================================
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
METHOD (constructor, "<init>", "(J)V") \
CALLBACK (generatedCallback<&Pimpl::webViewCloseWindowRequest>, "webViewCloseWindowRequest", "(JLandroid/webkit/WebView;)V") \
CALLBACK (generatedCallback<&Pimpl::webViewCreateWindowRequest>, "webViewCreateWindowRequest", "(JLandroid/webkit/WebView;)V") \
DECLARE_JNI_CLASS (JuceWebChromeClient, "com/rmsl/juce/JuceWebView$ChromeClient")
#undef JNI_CLASS_MEMBERS
static void webViewCloseWindowRequest (JNIEnv*, Pimpl& t, jobject)
{
t.owner.windowCloseRequest();
}
static void webViewCreateWindowRequest (JNIEnv*, Pimpl& t, jobject)
{
t.owner.newWindowAttemptingToLoad ({});
}
//==============================================================================
static void webViewReceivedHttpError (JNIEnv* env, Pimpl& t, jobject /*webView*/, jobject /*request*/, jobject errorResponse)
{
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));
t.owner.pageLoadHadNetworkError (juceString (errorString));
return;
}
}
// Should never get here!
jassertfalse;
t.owner.pageLoadHadNetworkError ({});
}
//==============================================================================
GlobalRef juceWebChromeClient;
GlobalRef juceWebViewClient;
std::unique_ptr<ConnectionThread> connectionThread;
WaitableEvent responseReadyEvent;
WeakReference<Pimpl>::Master masterReference;
friend class WeakReference<Pimpl>;
};
//==============================================================================
WebBrowserComponent::WebBrowserComponent (const Options& options)
: blankPageShown (false),
unloadPageWhenHidden (! options.keepsPageLoadedWhenBrowserIsHidden())
{
setOpaque (true);
browser.reset (new Pimpl (*this, options.getUserAgent()));
addAndMakeVisible (browser.get());
}
WebBrowserComponent::~WebBrowserComponent() = default;
//==============================================================================
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::focusGainedWithDirection (FocusChangeType, FocusChangeDirection)
{
}
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);
}
}
bool WebBrowserComponent::areOptionsSupported (const Options& options)
{
return (options.getBackend() == Options::Backend::defaultBackend);
}
} // namespace juce
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,752 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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
{
static NSURL* appendParametersToFileURL (const URL& url, NSURL* fileUrl)
{
if (@available (macOS 10.10, *))
{
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
template <class WebViewClass>
struct WebViewKeyEquivalentResponder final : public ObjCClass<WebViewClass>
{
WebViewKeyEquivalentResponder()
: ObjCClass<WebViewClass> ("WebViewKeyEquivalentResponder_")
{
this->addMethod (@selector (performKeyEquivalent:),
[] (id self, SEL selector, NSEvent* event)
{
const auto isCommandDown = [event]
{
const auto modifierFlags = [event modifierFlags];
if (@available (macOS 10.12, *))
return (modifierFlags & NSEventModifierFlagDeviceIndependentFlagsMask) == NSEventModifierFlagCommand;
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
return (modifierFlags & NSDeviceIndependentModifierFlagsMask) == NSCommandKeyMask;
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}();
if (isCommandDown)
{
auto sendAction = [&] (SEL actionSelector) -> BOOL
{
return [NSApp sendAction:actionSelector
to:[[self window] firstResponder]
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 ObjCClass<WebViewClass>::template sendSuperclassMessage<BOOL> (self, selector, event);
});
this->registerClass();
}
};
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
struct DownloadClickDetectorClass final : public ObjCClass<NSObject>
{
DownloadClickDetectorClass() : ObjCClass ("JUCEWebClickDetector_")
{
addIvar<WebBrowserComponent*> ("owner");
addMethod (@selector (webView:didFailLoadWithError:forFrame:), didFailLoadWithError);
addMethod (@selector (webView:didFailProvisionalLoadWithError:forFrame:), didFailLoadWithError);
addMethod (@selector (webView:decidePolicyForNavigationAction:request:frame:decisionListener:),
[] (id self, SEL, WebView*, NSDictionary* actionInformation, NSURLRequest*, WebFrame*, id<WebPolicyDecisionListener> listener)
{
if (getOwner (self)->pageAboutToLoad (getOriginalURL (actionInformation)))
[listener use];
else
[listener ignore];
});
addMethod (@selector (webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener:),
[] (id self, SEL, WebView*, NSDictionary* actionInformation, NSURLRequest*, NSString*, id<WebPolicyDecisionListener> listener)
{
getOwner (self)->newWindowAttemptingToLoad (getOriginalURL (actionInformation));
[listener ignore];
});
addMethod (@selector (webView: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]));
}
});
addMethod (@selector (webView:willCloseFrame:),
[] (id self, SEL, WebView*, WebFrame*)
{
getOwner (self)->windowCloseRequest();
});
addMethod (@selector (webView:runOpenPanelForFileButtonWithResultListener:allowMultipleFiles:),
[] (id, SEL, WebView*, id<WebOpenPanelResultListener> resultListener, BOOL allowMultipleFiles)
{
struct DeletedFileChooserWrapper final : private DeletedAtShutdown
{
DeletedFileChooserWrapper (std::unique_ptr<FileChooser> fc, id<WebOpenPanelResultListener> rl)
: chooser (std::move (fc)), listener (rl)
{
[listener.get() retain];
}
std::unique_ptr<FileChooser> chooser;
ObjCObjectHandle<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.get() chooseFilename: juceStringToNS (f.getFullPathName())];
delete wrapper;
});
});
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 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);
}
}
};
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
#endif
struct API_AVAILABLE (macos (10.10)) WebViewDelegateClass final : public ObjCClass<NSObject>
{
WebViewDelegateClass() : ObjCClass ("JUCEWebViewDelegate_")
{
addIvar<WebBrowserComponent*> ("owner");
addMethod (@selector (webView:decidePolicyForNavigationAction:decisionHandler:),
[] (id self, SEL, WKWebView*, WKNavigationAction* navigationAction, void (^decisionHandler) (WKNavigationActionPolicy))
{
if (getOwner (self)->pageAboutToLoad (nsStringToJuce ([[[navigationAction request] URL] absoluteString])))
decisionHandler (WKNavigationActionPolicyAllow);
else
decisionHandler (WKNavigationActionPolicyCancel);
});
addMethod (@selector (webView:didFinishNavigation:),
[] (id self, SEL, WKWebView* webview, WKNavigation*)
{
getOwner (self)->pageFinishedLoading (nsStringToJuce ([[webview URL] absoluteString]));
});
addMethod (@selector (webView:didFailNavigation:withError:),
[] (id self, SEL, WKWebView*, WKNavigation*, NSError* error)
{
displayError (getOwner (self), error);
});
addMethod (@selector (webView:didFailProvisionalNavigation:withError:),
[] (id self, SEL, WKWebView*, WKNavigation*, NSError* error)
{
displayError (getOwner (self), error);
});
addMethod (@selector (webViewDidClose:),
[] (id self, SEL, WKWebView*)
{
getOwner (self)->windowCloseRequest();
});
addMethod (@selector (webView:createWebViewWithConfiguration:forNavigationAction:windowFeatures:),
[] (id self, SEL, WKWebView*, WKWebViewConfiguration*, WKNavigationAction* navigationAction, WKWindowFeatures*)
{
getOwner (self)->newWindowAttemptingToLoad (nsStringToJuce ([[[navigationAction request] URL] absoluteString]));
return nil;
});
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
if (@available (macOS 10.12, *))
{
addMethod (@selector (webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:),
[] (id, SEL, WKWebView*, WKOpenPanelParameters* parameters, WKFrameInfo*, void (^completionHandler)(NSArray<NSURL*>*))
{
using CompletionHandlerType = decltype (completionHandler);
class DeletedFileChooserWrapper final : private DeletedAtShutdown
{
public:
DeletedFileChooserWrapper (std::unique_ptr<FileChooser> fc, CompletionHandlerType h)
: chooser (std::move (fc)), handler (h)
{
[handler.get() retain];
}
~DeletedFileChooserWrapper()
{
callHandler (nullptr);
}
void callHandler (NSArray<NSURL*>* urls)
{
if (handlerCalled)
return;
handler.get() (urls);
handlerCalled = true;
}
std::unique_ptr<FileChooser> chooser;
private:
ObjCObjectHandle<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 JUCE_MAC
if (@available (macOS 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;
});
});
}
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
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 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);
}
}
};
//==============================================================================
struct WebViewBase
{
virtual ~WebViewBase() = default;
virtual void goToURL (const String&, const StringArray*, const MemoryBlock*) = 0;
virtual void goBack() = 0;
virtual void goForward() = 0;
virtual void stop() = 0;
virtual void refresh() = 0;
virtual id getWebView() = 0;
};
#if JUCE_MAC
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
class WebViewImpl : public WebViewBase
{
public:
WebViewImpl (WebBrowserComponent* owner, const String& userAgent)
{
static WebViewKeyEquivalentResponder<WebView> webviewClass;
webView.reset ([webviewClass.createInstance() initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f)
frameName: nsEmptyString()
groupName: nsEmptyString()]);
webView.get().customUserAgent = juceStringToNS (userAgent);
static DownloadClickDetectorClass cls;
clickListener.reset ([cls.createInstance() init]);
DownloadClickDetectorClass::setOwner (clickListener.get(), owner);
[webView.get() setPolicyDelegate: clickListener.get()];
[webView.get() setFrameLoadDelegate: clickListener.get()];
[webView.get() setUIDelegate: clickListener.get()];
}
~WebViewImpl() override
{
[webView.get() setPolicyDelegate: nil];
[webView.get() setFrameLoadDelegate: nil];
[webView.get() setUIDelegate: nil];
}
void goToURL (const String& url,
const StringArray* headers,
const MemoryBlock* postData) override
{
if (url.trimStart().startsWithIgnoreCase ("javascript:"))
{
[webView.get() 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())
[[webView.get() mainFrame] loadRequest: request];
}
void goBack() override { [webView.get() goBack]; }
void goForward() override { [webView.get() goForward]; }
void stop() override { [webView.get() stopLoading: nil]; }
void refresh() override { [webView.get() reload: nil]; }
id getWebView() override { return webView.get(); }
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.get() respondsToSelector: @selector (_updateMouseoverWithFakeEvent)])
[webView.get() performSelector: @selector (_updateMouseoverWithFakeEvent)];
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
private:
ObjCObjectHandle<WebView*> webView;
ObjCObjectHandle<id> clickListener;
};
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
#endif
class API_AVAILABLE (macos (10.11)) WKWebViewImpl : public WebViewBase
{
public:
WKWebViewImpl (WebBrowserComponent* owner, const String& userAgent)
{
#if JUCE_MAC
static WebViewKeyEquivalentResponder<WKWebView> webviewClass;
webView.reset ([webviewClass.createInstance() initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f)]);
#else
webView.reset ([[WKWebView alloc] initWithFrame: CGRectMake (0, 0, 100.0f, 100.0f)]);
#endif
if (userAgent.isNotEmpty())
webView.get().customUserAgent = juceStringToNS (userAgent);
static WebViewDelegateClass cls;
webViewDelegate.reset ([cls.createInstance() init]);
WebViewDelegateClass::setOwner (webViewDelegate.get(), owner);
[webView.get() setNavigationDelegate: webViewDelegate.get()];
[webView.get() setUIDelegate: webViewDelegate.get()];
#if JUCE_DEBUG
[[[webView.get() configuration] preferences] setValue: @(true) forKey: @"developerExtrasEnabled"];
#endif
}
~WKWebViewImpl() override
{
[webView.get() setNavigationDelegate: nil];
[webView.get() setUIDelegate: nil];
}
void goToURL (const String& url,
const StringArray* headers,
const MemoryBlock* postData) override
{
auto trimmed = url.trimStart();
if (trimmed.startsWithIgnoreCase ("javascript:"))
{
[webView.get() 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.get() loadFileURL: appendParametersToFileURL (url, nsUrl) allowingReadAccessToURL: nsUrl];
}
else if (NSMutableURLRequest* request = getRequestForURL (url, headers, postData))
{
[webView.get() loadRequest: request];
}
}
void goBack() override { [webView.get() goBack]; }
void goForward() override { [webView.get() goForward]; }
void stop() override { [webView.get() stopLoading]; }
void refresh() override { [webView.get() reload]; }
id getWebView() override { return webView.get(); }
private:
ObjCObjectHandle<WKWebView*> webView;
ObjCObjectHandle<id> webViewDelegate;
};
//==============================================================================
class WebBrowserComponent::Pimpl
#if JUCE_MAC
: public NSViewComponent
#else
: public UIViewComponent
#endif
{
public:
Pimpl (WebBrowserComponent* owner, const String& userAgent)
{
if (@available (macOS 10.11, *))
webView = std::make_unique<WKWebViewImpl> (owner, userAgent);
#if JUCE_MAC
else
webView = std::make_unique<WebViewImpl> (owner, userAgent);
#endif
setView (webView->getWebView());
}
~Pimpl() override
{
webView = nullptr;
setView (nil);
}
void goToURL (const String& url,
const StringArray* headers,
const MemoryBlock* postData)
{
webView->goToURL (url, headers, postData);
}
void goBack() { webView->goBack(); }
void goForward() { webView->goForward(); }
void stop() { webView->stop(); }
void refresh() { webView->refresh(); }
private:
std::unique_ptr<WebViewBase> webView;
};
//==============================================================================
WebBrowserComponent::WebBrowserComponent (const Options& options)
: unloadPageWhenHidden (! options.keepsPageLoadedWhenBrowserIsHidden())
{
setOpaque (true);
browser.reset (new Pimpl (this, options.getUserAgent()));
addAndMakeVisible (browser.get());
}
WebBrowserComponent::~WebBrowserComponent() = default;
//==============================================================================
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::focusGainedWithDirection (FocusChangeType, FocusChangeDirection)
{
}
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];
}
//==============================================================================
bool WebBrowserComponent::areOptionsSupported (const Options& options)
{
return (options.getBackend() == Options::Backend::defaultBackend);
}
} // namespace juce
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,711 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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 final : public ReferenceCountedObject
{
SharedKeyWindow (ComponentPeer* peerToUse)
: keyPeer (peerToUse),
keyProxy (juce_createKeyProxyWindow (keyPeer)),
association (peerToUse, keyProxy)
{}
~SharedKeyWindow()
{
association = {};
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;
ScopedWindowAssociation association;
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;
}
void updateEmbeddedBounds()
{
componentMovedOrResized (owner, true, true);
}
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() * peer->getComponent().getDesktopScaleFactor();
}
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); }
void XEmbedComponent::updateEmbeddedBounds() { pimpl->updateEmbeddedBounds(); }
//==============================================================================
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