Browse Source

add native apis for setting working with osx menus

pull/2978/head
Dan Walmsley 6 years ago
parent
commit
22e14443ab
  1. 10
      native/Avalonia.Native/inc/IGetNative.h
  2. 27
      native/Avalonia.Native/inc/avalonia-native.h
  3. 7
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  4. 8
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  5. 9
      native/Avalonia.Native/src/OSX/common.h
  6. 127
      native/Avalonia.Native/src/OSX/main.mm
  7. 79
      native/Avalonia.Native/src/OSX/menu.h
  8. 211
      native/Avalonia.Native/src/OSX/menu.mm
  9. 6
      native/Avalonia.Native/src/OSX/platformthreading.mm

10
native/Avalonia.Native/inc/IGetNative.h

@ -0,0 +1,10 @@
#ifndef igetnative_h
#define igetnative_h
class IGetNative
{
public:
virtual void* GetNative() = 0;
};
#endif

27
native/Avalonia.Native/inc/avalonia-native.h

@ -22,6 +22,8 @@ struct IAvnGlContext;
struct IAvnGlDisplay;
struct IAvnGlSurfaceRenderTarget;
struct IAvnGlSurfaceRenderingSession;
struct IAvnAppMenu;
struct IAvnAppMenuItem;
struct AvnSize
{
@ -173,6 +175,9 @@ public:
virtual HRESULT CreateClipboard(IAvnClipboard** ppv) = 0;
virtual HRESULT CreateCursorFactory(IAvnCursorFactory** ppv) = 0;
virtual HRESULT ObtainGlFeature(IAvnGlFeature** ppv) = 0;
virtual HRESULT ObtainAppMenu (IAvnAppMenu** ppv) = 0;
virtual HRESULT CreateMenu (IAvnAppMenu** ppv) = 0;
virtual HRESULT CreateMenuItem (IAvnAppMenuItem** ppv) = 0;
};
AVNCOM(IAvnString, 17) : IUnknown
@ -258,6 +263,7 @@ AVNCOM(IAvnWindowEvents, 06) : IAvnWindowBaseEvents
AVNCOM(IAvnMacOptions, 07) : IUnknown
{
virtual HRESULT SetShowInDock(int show) = 0;
virtual HRESULT SetApplicationTitle (void* utf8string) = 0;
};
AVNCOM(IAvnActionCallback, 08) : IUnknown
@ -367,4 +373,25 @@ AVNCOM(IAvnGlSurfaceRenderingSession, 16) : IUnknown
virtual HRESULT GetScaling(double* ret) = 0;
};
AVNCOM(IAvnAppMenu, 17) : IUnknown
{
virtual HRESULT AddItem (IAvnAppMenuItem* item) = 0;
virtual HRESULT RemoveItem (IAvnAppMenuItem* item) = 0;
virtual HRESULT SetTitle (void* utf8String) = 0;
virtual HRESULT Clear () = 0;
};
AVNCOM(IAvnPredicateCallback, 18) : IUnknown
{
virtual bool Evaluate() = 0;
};
AVNCOM(IAvnAppMenuItem, 19) : IUnknown
{
virtual HRESULT SetSubMenu (IAvnAppMenu* menu) = 0;
virtual HRESULT SetTitle (void* utf8String) = 0;
virtual HRESULT SetGesture (void* utf8String, AvnInputModifiers modifiers) = 0;
virtual HRESULT SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback) = 0;
};
extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative();

7
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj

@ -12,6 +12,7 @@
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; };
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37DDA9AF219330F8002E132B /* AvnString.mm */; };
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37E2330E21583241000CB7E2 /* KeyTransform.mm */; };
520624B322973F4100C4DCEF /* menu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 520624B222973F4100C4DCEF /* menu.mm */; };
5B21A982216530F500CEE36E /* cursor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B21A981216530F500CEE36E /* cursor.mm */; };
5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; };
AB00E4F72147CA920032A60A /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB00E4F62147CA920032A60A /* main.mm */; };
@ -32,6 +33,8 @@
37DDA9AF219330F8002E132B /* AvnString.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnString.mm; sourceTree = "<group>"; };
37DDA9B121933371002E132B /* AvnString.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnString.h; sourceTree = "<group>"; };
37E2330E21583241000CB7E2 /* KeyTransform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyTransform.mm; sourceTree = "<group>"; };
520624B222973F4100C4DCEF /* menu.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = menu.mm; sourceTree = "<group>"; };
5296D43022F30EBC005B125D /* menu.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = menu.h; path = ../../../../../../../../System/Volumes/Data/Users/danwalmsley/repos/Avalonia/native/Avalonia.Native/src/OSX/menu.h; sourceTree = "<group>"; };
5B21A981216530F500CEE36E /* cursor.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cursor.mm; sourceTree = "<group>"; };
5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = clipboard.mm; sourceTree = "<group>"; };
5BF943652167AD1D009CAE35 /* cursor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cursor.h; sourceTree = "<group>"; };
@ -85,6 +88,8 @@
AB661C1F2148286E00291242 /* window.mm */,
37C09D8A21581EF2006A6758 /* window.h */,
AB00E4F62147CA920032A60A /* main.mm */,
520624B222973F4100C4DCEF /* menu.mm */,
5296D43022F30EBC005B125D /* menu.h */,
37A517B22159597E00FBA241 /* Screens.mm */,
37C09D8721580FE4006A6758 /* SystemDialogs.mm */,
AB7A61F02147C815003C5833 /* Products */,
@ -150,6 +155,7 @@
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
);
mainGroup = AB7A61E62147C814003C5833;
@ -173,6 +179,7 @@
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */,
AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */,
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */,
520624B322973F4100C4DCEF /* menu.mm in Sources */,
37A517B32159597E00FBA241 /* Screens.mm in Sources */,
AB00E4F72147CA920032A60A /* main.mm in Sources */,
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */,

8
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

9
native/Avalonia.Native/src/OSX/common.h

@ -19,6 +19,10 @@ extern IAvnClipboard* CreateClipboard();
extern IAvnCursorFactory* CreateCursorFactory();
extern IAvnGlFeature* GetGlFeature();
extern IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(NSWindow* window, NSView* view);
extern IAvnAppMenu* GetAppMenu();
extern IAvnAppMenu* CreateAppMenu();
extern IAvnAppMenuItem* CreateAppMenuItem();
extern void InitializeAvnApp();
extern NSApplicationActivationPolicy AvnDesiredActivationPolicy;
extern NSPoint ToNSPoint (AvnPoint p);
@ -40,4 +44,9 @@ template<typename T> inline T* objc_cast(id from) {
return nil;
}
@interface ActionCallback : NSObject
- (ActionCallback*) initWithCallback: (IAvnActionCallback*) callback;
- (void) action;
@end
#endif

127
native/Avalonia.Native/src/OSX/main.mm

@ -5,10 +5,115 @@
#define COM_GUIDS_MATERIALIZE
#include "common.h"
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
void SetProcessName(CFStringRef process_name) {
if (!process_name || CFStringGetLength(process_name) == 0) {
//NOTREACHED() << "SetProcessName given bad name.";
return;
}
if (![NSThread isMainThread]) {
//NOTREACHED() << "Should only set process name from main thread.";
return;
}
// Warning: here be dragons! This is SPI reverse-engineered from WebKit's
// plugin host, and could break at any time (although realistically it's only
// likely to break in a new major release).
// When 10.7 is available, check that this still works, and update this
// comment for 10.8.
// Private CFType used in these LaunchServices calls.
typedef CFTypeRef PrivateLSASN;
typedef PrivateLSASN (*LSGetCurrentApplicationASNType)();
typedef OSStatus (*LSSetApplicationInformationItemType)(int, PrivateLSASN,
CFStringRef,
CFStringRef,
CFDictionaryRef*);
static LSGetCurrentApplicationASNType ls_get_current_application_asn_func =
NULL;
static LSSetApplicationInformationItemType
ls_set_application_information_item_func = NULL;
static CFStringRef ls_display_name_key = NULL;
static bool did_symbol_lookup = false;
if (!did_symbol_lookup) {
did_symbol_lookup = true;
CFBundleRef launch_services_bundle =
CFBundleGetBundleWithIdentifier(CFSTR("com.apple.LaunchServices"));
if (!launch_services_bundle) {
//LOG(ERROR) << "Failed to look up LaunchServices bundle";
return;
}
ls_get_current_application_asn_func =
reinterpret_cast<LSGetCurrentApplicationASNType>(
CFBundleGetFunctionPointerForName(
launch_services_bundle, CFSTR("_LSGetCurrentApplicationASN")));
if (!ls_get_current_application_asn_func){}
//LOG(ERROR) << "Could not find _LSGetCurrentApplicationASN";
ls_set_application_information_item_func =
reinterpret_cast<LSSetApplicationInformationItemType>(
CFBundleGetFunctionPointerForName(
launch_services_bundle,
CFSTR("_LSSetApplicationInformationItem")));
if (!ls_set_application_information_item_func){}
//LOG(ERROR) << "Could not find _LSSetApplicationInformationItem";
CFStringRef* key_pointer = reinterpret_cast<CFStringRef*>(
CFBundleGetDataPointerForName(launch_services_bundle,
CFSTR("_kLSDisplayNameKey")));
ls_display_name_key = key_pointer ? *key_pointer : NULL;
if (!ls_display_name_key){}
//LOG(ERROR) << "Could not find _kLSDisplayNameKey";
// Internally, this call relies on the Mach ports that are started up by the
// Carbon Process Manager. In debug builds this usually happens due to how
// the logging layers are started up; but in release, it isn't started in as
// much of a defined order. So if the symbols had to be loaded, go ahead
// and force a call to make sure the manager has been initialized and hence
// the ports are opened.
ProcessSerialNumber psn;
GetCurrentProcess(&psn);
}
if (!ls_get_current_application_asn_func ||
!ls_set_application_information_item_func ||
!ls_display_name_key) {
return;
}
PrivateLSASN asn = ls_get_current_application_asn_func();
// Constant used by WebKit; what exactly it means is unknown.
const int magic_session_constant = -2;
OSErr err =
ls_set_application_information_item_func(magic_session_constant, asn,
ls_display_name_key,
process_name,
NULL /* optional out param */);
//LOG_IF(ERROR, err) << "Call to set process name failed, err " << err;
}
class MacOptions : public ComSingleObject<IAvnMacOptions, &IID_IAvnMacOptions>
{
public:
FORWARD_IUNKNOWN()
virtual HRESULT SetApplicationTitle(void* utf8String) override
{
auto appTitle = [NSString stringWithUTF8String:(const char*)utf8String];
[[NSProcessInfo processInfo] setProcessName:appTitle];
CFStringRef titleRef = (__bridge CFStringRef)appTitle;
SetProcessName(titleRef);
return S_OK;
}
virtual HRESULT SetShowInDock(int show) override
{
AvnDesiredActivationPolicy = show
@ -17,8 +122,6 @@ public:
}
};
/// See "Using POSIX Threads in a Cocoa Application" section here:
/// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html#//apple_ref/doc/uid/20000738-125024
@interface ThreadingInitializer : NSObject
@ -43,8 +146,6 @@ public:
close(_fds[0]);
close(_fds[1]);
}
@end
@ -123,6 +224,24 @@ public:
*ppv = rv;
return S_OK;
}
virtual HRESULT ObtainAppMenu(IAvnAppMenu** ppv) override
{
*ppv = ::GetAppMenu();
return S_OK;
}
virtual HRESULT CreateMenu (IAvnAppMenu** ppv) override
{
*ppv = ::CreateAppMenu();
return S_OK;
}
virtual HRESULT CreateMenuItem (IAvnAppMenuItem** ppv) override
{
*ppv = ::CreateAppMenuItem();
return S_OK;
}
};
extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative()

79
native/Avalonia.Native/src/OSX/menu.h

@ -0,0 +1,79 @@
//
// menu.h
// Avalonia.Native.OSX
//
// Created by Dan Walmsley on 01/08/2019.
// Copyright © 2019 Avalonia. All rights reserved.
//
#ifndef menu_h
#define menu_h
#include "common.h"
class AvnAppMenuItem;
class AvnAppMenu;
@interface AvnMenu : NSMenu // for some reason it doesnt detect nsmenu here but compiler doesnt complain
@end
@interface AvnMenuItem : NSMenuItem
- (id) initWithAvnAppMenuItem: (AvnAppMenuItem*)menuItem;
- (void)didSelectItem:(id)sender;
@end
class AvnAppMenuItem : public ComSingleObject<IAvnAppMenuItem, &IID_IAvnAppMenuItem>
{
private:
AvnMenuItem* _native; // here we hold a pointer to an AvnMenuItem
IAvnActionCallback* _callback;
IAvnPredicateCallback* _predicate;
public:
FORWARD_IUNKNOWN()
AvnAppMenuItem();
AvnMenuItem* GetNative();
virtual HRESULT SetSubMenu (IAvnAppMenu* menu) override;
virtual HRESULT SetTitle (void* utf8String) override;
virtual HRESULT SetGesture (void* key, AvnInputModifiers modifiers) override;
virtual HRESULT SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback) override;
bool EvaluateItemEnabled();
void RaiseOnClicked();
};
class AvnAppMenu : public ComSingleObject<IAvnAppMenu, &IID_IAvnAppMenu>
{
private:
AvnMenu* _native;
public:
FORWARD_IUNKNOWN()
AvnAppMenu();
AvnAppMenu(AvnMenu* native);
AvnMenu* GetNative();
virtual HRESULT AddItem (IAvnAppMenuItem* item) override;
virtual HRESULT RemoveItem (IAvnAppMenuItem* item) override;
virtual HRESULT SetTitle (void* utf8String) override;
virtual HRESULT Clear () override;
};
#endif

211
native/Avalonia.Native/src/OSX/menu.mm

@ -0,0 +1,211 @@
#include "common.h"
#include "IGetNative.h"
#include "menu.h"
@implementation AvnMenu
@end
@implementation AvnMenuItem
{
AvnAppMenuItem* _item;
}
- (id) initWithAvnAppMenuItem: (AvnAppMenuItem*)menuItem
{
if(self != nil)
{
_item = menuItem;
self = [super initWithTitle:@""
action:@selector(didSelectItem:)
keyEquivalent:@""];
[self setEnabled:YES];
[self setTarget:self];
}
return self;
}
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
if([self submenu] != nil)
{
return YES;
}
return _item->EvaluateItemEnabled();
}
- (void)didSelectItem:(nullable id)sender
{
_item->RaiseOnClicked();
}
@end
AvnAppMenuItem::AvnAppMenuItem()
{
_native = [[AvnMenuItem alloc] initWithAvnAppMenuItem: this];
_callback = nullptr;
}
AvnMenuItem* AvnAppMenuItem::GetNative()
{
return _native;
}
HRESULT AvnAppMenuItem::SetSubMenu (IAvnAppMenu* menu)
{
auto nsMenu = dynamic_cast<AvnAppMenu*>(menu)->GetNative();
[_native setSubmenu: nsMenu];
return S_OK;
}
HRESULT AvnAppMenuItem::SetTitle (void* utf8String)
{
[_native setTitle:[NSString stringWithUTF8String:(const char*)utf8String]];
return S_OK;
}
HRESULT AvnAppMenuItem::SetGesture (void* key, AvnInputModifiers modifiers)
{
NSEventModifierFlags flags = 0;
if (modifiers & Control)
flags |= NSEventModifierFlagControl;
if (modifiers & Shift)
flags |= NSEventModifierFlagShift;
if (modifiers & Alt)
flags |= NSEventModifierFlagOption;
if (modifiers & Windows)
flags |= NSEventModifierFlagCommand;
[_native setKeyEquivalent:[NSString stringWithUTF8String:(const char*)key]];
[_native setKeyEquivalentModifierMask:flags];
return S_OK;
}
HRESULT AvnAppMenuItem::SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback)
{
_predicate = predicate;
_callback = callback;
return S_OK;
}
bool AvnAppMenuItem::EvaluateItemEnabled()
{
if(_predicate != nullptr)
{
auto result = _predicate->Evaluate ();
return result;
}
return false;
}
void AvnAppMenuItem::RaiseOnClicked()
{
if(_callback != nullptr)
{
_callback->Run();
}
}
AvnAppMenu::AvnAppMenu()
{
_native = [AvnMenu new];
}
AvnAppMenu::AvnAppMenu(AvnMenu* native)
{
_native = native;
}
AvnMenu* AvnAppMenu::GetNative()
{
return _native;
}
HRESULT AvnAppMenu::AddItem (IAvnAppMenuItem* item)
{
auto avnMenuItem = dynamic_cast<AvnAppMenuItem*>(item);
if(avnMenuItem != nullptr)
{
[_native addItem: avnMenuItem->GetNative()];
}
return S_OK;
}
HRESULT AvnAppMenu::RemoveItem (IAvnAppMenuItem* item)
{
auto avnMenuItem = dynamic_cast<AvnAppMenuItem*>(item);
if(avnMenuItem != nullptr)
{
[_native removeItem:avnMenuItem->GetNative()];
}
return S_OK;
}
HRESULT AvnAppMenu::SetTitle (void* utf8String)
{
[_native setTitle:[NSString stringWithUTF8String:(const char*)utf8String]];
return S_OK;
}
HRESULT AvnAppMenu::Clear()
{
[_native removeAllItems];
return S_OK;
}
static IAvnAppMenu* s_AppMenu = nullptr;
extern IAvnAppMenu* GetAppMenu()
{
@autoreleasepool
{
if(s_AppMenu == nullptr)
{
id menubar = [NSMenu new];
[menubar setTitle:@"Test"];
[NSApp setMainMenu:menubar];
id appMenuItem = [AvnMenuItem new];
[[NSApp mainMenu] addItem:appMenuItem];
[appMenuItem setSubmenu:[AvnMenu new]];
s_AppMenu = new AvnAppMenu([[NSApplication sharedApplication] mainMenu]);
}
return s_AppMenu;
}
}
extern IAvnAppMenu* CreateAppMenu()
{
@autoreleasepool
{
return new AvnAppMenu();
}
}
extern IAvnAppMenuItem* CreateAppMenuItem()
{
@autoreleasepool
{
return new AvnAppMenuItem();
}
}

6
native/Avalonia.Native/src/OSX/platformthreading.mm

@ -10,12 +10,6 @@ class PlatformThreadingInterface;
-(Signaler*) init;
@end
@interface ActionCallback : NSObject
- (ActionCallback*) initWithCallback: (IAvnActionCallback*) callback;
- (void) action;
@end
@implementation ActionCallback
{
ComPtr<IAvnActionCallback> _callback;

Loading…
Cancel
Save