我花了大约一天(可能多一点)试图按照它在macOS启动(用户登录)启动的顺序将我的应用程序添加到Login Item。
所以按照这个步骤,我做了:
在项目设置>功能我将App Sandbox切换为ON。
我的Launcher应用程序中的代码看起来像这样(我以前甚至使用Swift做同样的事情)
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
NSArray *pathComponents = [[[NSBundle mainBundle] bundlePath] pathComponents];
pathComponents = [pathComponents subarrayWithRange:NSMakeRange(0, [pathComponents count] - 4)];
NSString *path = [NSString pathWithComponents:pathComponents];
[[NSWorkspace sharedWorkspace] launchApplication:path];
[NSApp terminate:nil];
}
最后,我在主应用程序中使用魔术代码启用应用程序作为登录项
if(!SMLoginItemSetEnabled("click.remotely.Remotely-Click-Server-Launcher" as CFString, Bool(checkboxButton.state as NSNumber) ) ) { let alert: NSAlert = NSAlert() alert.messageText = "Remotely.Click Server - Error"; alert.informativeText = "Application couldn't be added as Login Item to macOS System Preferences > Users & Groups."; alert.alertStyle = NSAlertStyle.warning; alert.addButton(withTitle:"OK"); alert.runModal(); }
我无法决定选择哪一个,所以我尝试了所有这些。 "保存Mac App Store部署" - 已安装在/ Applications /目录中的安装包,但应用程序从不运行。 " Developer-Id签名," "发展签名" ," macOS App" all将文件放在我导出到Applications目录的目录中,但没有人工作。
当我点击复选框按钮时,我可以在屏幕上看到一些窗口闪烁一段时间(Launcher程序)。当我退出并登录同一窗口时,会出现闪烁效果,但Launcher没有启动主应用程序。当我再次单击复选框按钮(并关闭登录项)时,这种对用户登录(系统启动)的闪烁效果不会再次发生。因此,似乎将Launcher程序添加为Login Item可以正常工作,但是这个Launcher无法启动Main应用程序。此外,当我转到/Applications/Main.app/Contents/Library/LoginItems/Launcher.app并手动点击它然后Launcher应用程序正确启动主应用程序(所以路径是正确的)。
那么出了什么问题?
然后我考虑使用不推荐的方法的实现
的 kLSSharedFileListSessionLoginItems
我认为它必须工作只需在系统偏好设置中添加一些内容 窗口如下。
但它也可能出错!
我在Swift中选择了实现(我发现的所有示例/教程都在Objective-C中)所以我写了这样的东西:
class LoginItemsList : NSObject {
let loginItemsList : LSSharedFileList = LSSharedFileListCreate(nil, kLSSharedFileListSessionLoginItems.takeRetainedValue(), nil).takeRetainedValue();
func addLoginItem(_ path: CFURL) -> Bool {
if(getLoginItem(path) != nil) {
print("Login Item has already been added to the list.");
return true;
}
var path : CFURL = CFURLCreateWithString(nil, "file:///Applications/Safari.app" as CFString, nil);
print("Path adding to Login Item list is: ", path);
// add new Login Item at the end of Login Items list
if let loginItem = LSSharedFileListInsertItemURL(loginItemsList,
getLastLoginItemInList(),
nil, nil,
path,
nil, nil) {
print("Added login item is: ", loginItem);
return true;
}
return false;
}
func removeLoginItem(_ path: CFURL) -> Bool {
// remove Login Item from the Login Items list
if let oldLoginItem = getLoginItem(path) {
print("Old login item is: ", oldLoginItem);
if(LSSharedFileListItemRemove(loginItemsList, oldLoginItem) == noErr) {
return true;
}
return false;
}
print("Login Item for given path not found in the list.");
return true;
}
func getLoginItem(_ path : CFURL) -> LSSharedFileListItem! {
var path : CFURL = CFURLCreateWithString(nil, "file:///Applications/Safari.app" as CFString, nil);
// Copy all login items in the list
let loginItems : NSArray = LSSharedFileListCopySnapshot(loginItemsList, nil).takeRetainedValue();
var foundLoginItem : LSSharedFileListItem?;
var nextItemUrl : Unmanaged<CFURL>?;
// Iterate through login items to find one for given path
print("App URL: ", path);
for var i in (0..<loginItems.count) // CFArrayGetCount(loginItems)
{
var nextLoginItem : LSSharedFileListItem = loginItems.object(at: i) as! LSSharedFileListItem; // CFArrayGetValueAtIndex(loginItems, i).;
if(LSSharedFileListItemResolve(nextLoginItem, 0, &nextItemUrl, nil) == noErr) {
print("Next login item URL: ", nextItemUrl!.takeUnretainedValue());
// compare searched item URL passed in argument with next item URL
if(nextItemUrl!.takeRetainedValue() == path) {
foundLoginItem = nextLoginItem;
}
}
}
return foundLoginItem;
}
func getLastLoginItemInList() -> LSSharedFileListItem! {
// Copy all login items in the list
let loginItems : NSArray = LSSharedFileListCopySnapshot(loginItemsList, nil).takeRetainedValue() as NSArray;
if(loginItems.count > 0) {
let lastLoginItem = loginItems.lastObject as! LSSharedFileListItem;
print("Last login item is: ", lastLoginItem);
return lastLoginItem
}
return kLSSharedFileListItemBeforeFirst.takeRetainedValue();
}
func isLoginItemInList(_ path : CFURL) -> Bool {
if(getLoginItem(path) != nil) {
return true;
}
return false;
}
static func appPath() -> CFURL {
return NSURL.fileURL(withPath: Bundle.main.bundlePath) as CFURL;
}
}
我已经用它来点击复选框
打开/关闭登录项目 let loginItemsList = LoginItemsList();
if( checkboxButton.state == 0) {
if(!loginItemsList.removeLoginItem(LoginItemsList.appPath())) {
print("Error while removing Login Item from the list.");
}
} else {
if(!loginItemsList.addLoginItem(LoginItemsList.appPath())) {
print("Error while adding Login Item to the list.");
}
}
我已经在调试模式(Xcode播放按钮)中运行它并尝试将其存档并导出到/ Applications文件夹(如果重要),但这种方法也不起作用。
控制台打印的消息。错误表示插入登录项的功能返回nil。
所以在那之后我甚至尝试使用Objective-C实现这个(来自一些stackoverflow示例)(因为Swift中有许多Unmanaged&lt;&gt;) 所以我添加了新的.m和.h文件以及Bridging-Header.h,然后添加了这样的代码:
- (void)enableLoginItemWithURL:(NSURL *)itemURL
{
LSSharedFileListRef loginListRef = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if (loginListRef) {
// Insert the item at the bottom of Login Items list.
LSSharedFileListItemRef loginItemRef = LSSharedFileListInsertItemURL(loginListRef,
kLSSharedFileListItemLast,
NULL,
NULL,
(__bridge CFURLRef) itemURL,
NULL,
NULL);
if (loginItemRef) {
CFRelease(loginItemRef);
}
CFRelease(loginListRef);
}
}
简单(只是插入)没有任何铃声和口哨声。
它也有同样的问题, LSSharedFileListInsertItemURL
返回nil,而登录项未添加到系统偏好设置&gt;用户&amp;群组&gt;登录项目。
所以我知道为什么我不能做这个工作?
更新1
我试图在另一台计算机iMac(MacOS Sierra和最新的XCode 8.3)上使用第一种方法(主应用程序中的辅助Launcher应用程序)实现应用程序,它似乎正常工作,所以可能我的操作系统有问题或者Xcode(供应配置文件,应用程序的签名或其他)在MacBook Air上,这种方法不起作用我正在使用OS X El Capitan 10.11.5和Xcode 8.0。
观看它的工作原理: https://youtu.be/6fnLzkh5Rbs 和测试 https://www.youtube.com/watch?v=sUE7Estju0U
第二种方法在我的iMac上也不起作用,在执行LSSharedFileListInsertItemURL
时返回 nil 。所以我不知道为什么会这样。
观看它的工作原理: https://youtu.be/S_7ctQLkIuA
更新2
从El Capitan 10.11.5升级到macOS Sierra 10.12.5并使用Xcode 8.3.2而不是Xcode 8.0.0后,第二种方法也正常工作,并且正在向系统偏好设置添加登录项&gt;用户&amp;群组&gt;登录项目
重要!要使用LSSharedFileListInsertItemURL
这种方法,需要禁用应用沙盒!如下面的视频:
https://youtu.be/UvDkby0t_WI
答案 0 :(得分:4)
几年前,我也为此而苦苦挣扎,最终为其制作了package,这使得为沙盒应用程序添加“登录时启动”功能更加容易。
您只需:
来代替lots of manual steps。import LaunchAtLogin
LaunchAtLogin.isEnabled = true
答案 1 :(得分:1)
上述登录项目编程问题的解决方案既可以使用现代方法与ServiceManagement.framework正确使用,也可以使用旧的(已弃用的)方法将登录项插入到系统偏好设置&gt;用户&amp;群组&gt;登录项目。请参阅我的更新1和更新2备注。
答案 2 :(得分:1)
对于ServiceManagement方法,有时它在您的开发机器中不起作用,因为Xcode的DerivedData中有另一个应用程序副本。系统不知道要启动哪个应用程序。所以转到〜/ Library / Developer / Xcode / DerivedData并删除你的开发副本可能有所帮助。
答案 3 :(得分:0)
亲爱的Michal我对登录项目遇到了同样的问题。登录项目可以通过两种方式添加;来自LSSharedFileListItemRef
的一个将显示在首选项的登录项中,但此方法仅适用于非沙盒应用程序,如果您正在制作沙盒应用程序,那么您应该采用另一种方式使用ServiceManagement
框架。
您可以查看以下指向所有内容的链接 - : Launching your app on system start
我正在添加另一个在登录时添加应用程序的参考 - : Approach for sandbox app with helper application
您可能无法在登录项目中实施应用程序,但请按照相应步骤进行操作,否则您将获得成功。