macOS Swift:如何正确添加应用程序作为登录项

Mic*_*bro 9 macos cocoa objective-c swift macos-sierra

我花了大约一天(可能多一点)试图按照它在macOS启动(用户登录)启动的顺序将我的应用程序添加到Login Item.

  1. 第一种方法是最新的方法; 我在youtube上查看了这个教程:https://www.youtube.com/watch?v = 2mmWEHUgEBo & t = 660s

所以按照这个步骤,我做了:

  1. 在我的主项目中添加我命名为Launcher的新项目
  2. 我使用自动签名(作为我的Xcode的版本)是不同的 在此输入图像描述

  3. 在项目设置>功能中,我将App Sandbox切换为ON.

  4. 在构建阶段我添加了这个: 在此输入图像描述

  5. 我的启动器已跳过安装=是 在此输入图像描述

  6. 我的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];
    } 
    
    Run Code Online (Sandbox Code Playgroud)
  7. 最后,我在主应用程序中有魔术代码,以启用应用程序作为登录项

  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();
   }
Run Code Online (Sandbox Code Playgroud)
  1. 我已经创建了Archive,然后有不同的Export选项:

在此输入图像描述

我无法决定选择哪一个,所以我尝试了所有这些."保存为Mac App Store部署" - 已安装在/ Applications /目录中的安装包,但应用程序永远不会运行."Developer-Id签名","开发签名","macOS App"都将文件放在我导出到Applications目录的目录中,但没人工作.

  1. 当我单击复选框按钮时,我可以看到一些窗口在屏幕上闪烁一段时间(Launcher程序).当我退出并登录同一窗口时,会出现闪烁效果,但Launcher未启动主应用程序.当我再次单击复选框按钮(并关闭登录项)时,这种对用户登录(系统启动)的闪烁效果不会再次发生.因此,似乎将Launcher程序添加为Login Item可以正常工作,但是这个Launcher无法启动Main应用程序.此外,当我转到/Applications/Main.app/Contents/Library/LoginItems/Launcher.app并手动点击它然后Launcher应用程序正确启动主应用程序(所以路径是正确的).

  2. 出了什么问题?

然后我考虑使用不推荐的方法的实现 kLSSharedFileListSessionLoginItems

我认为它必须工作它只是在下面的系统首选项中添加一些东西.

在此输入图像描述

但它也可能出错!

  1. 我在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;
    }
    
     }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 我通过单击复选框使用它来打开/关闭登录项

      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.");
            }
        }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 我已经在调试模式(Xcode播放按钮)中运行它并尝试将其存档并导出到/ Applications文件夹(如果重要),但这种方法也不起作用.

  4. 控制台打印的消息.错误表示插入登录项的函数返回nil.

在此输入图像描述

所以在那之后我甚至尝试使用Objective-C实现这个(来自一些stackoverflow示例)(因为Swift中有很多Unmanaged <>)所以我添加了新的.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);
    }
}
Run Code Online (Sandbox Code Playgroud)

简单(只是插入)没有任何铃声和口哨声.它也有同样的问题,LSSharedFileListInsertItemURL返回nil并且Login Item没有添加到系统首选项>用户和组>登录项.

所以我知道为什么我不能做这个工作?

更新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 在执行时返回nilLSSharedFileListInsertItemURL.所以我不知道为什么会这样.

在这里观看它是如何工作的:https: //youtu.be/S_7ctQLkIuA

更新2

从El Capitan 10.11.5升级到macOS Sierra 10.12.5并使用Xcode 8.3.2而不是Xcode 8.0.0后,第二种方法也正常工作并将登录项添加到系统偏好设置>用户和组>登录项 重要!要使用此方法LSSharedFileListInsertItemURL需要禁用App沙盒!如下面的视频:https: //youtu.be/UvDkby0t_WI

Sin*_*hus 8

几年前我也曾为此苦苦挣扎,最终为其制作了一个,使为沙盒应用程序添加“登录时启动”功能变得更加容易。

无需执行大量手动步骤,您只需要:

import LaunchAtLogin

LaunchAtLogin.isEnabled = true
Run Code Online (Sandbox Code Playgroud)


Pav*_*ský 6

从 macOS 13 Ventura 开始,我们终于可以使用新的SMAppService.

import ServiceManagement

try SMAppService.mainApp.register()
Run Code Online (Sandbox Code Playgroud)

有关更多详细信息,请参阅SMAppService 文档。


Mic*_*bro 1

上述登录项编程问题的解决方案可以使用 ServiceManagement.framework 的现代方法和将登录项插入系统首选项 > 用户和组 > 登录项的旧(已弃用)方法正常工作。请参阅我的更新 1 和更新 2 评论。