macOS Swift:如何正确添加应用程序作为登录项 [英] macOS Swift: How to properly add application as Login Item

查看:102
本文介绍了macOS Swift:如何正确添加应用程序作为登录项的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我花了大约一天(也许更多)的时间尝试将我的应用程序按其在macOS启动(用户登录)时启动的顺序添加到Login Item中.

I have spent about one day (maybe a little more) on trying to add my application to Login Item in the order it starts up at macOS launch (user login).

  1. 第一种方法是最新的方法;我在youtube上查看了本教程: https://www.youtube.com/watch?v=2mmWEHUgEBo&t=660s
  1. The first approach was the newest one; I check this tutorial on youtube: https://www.youtube.com/watch?v=2mmWEHUgEBo&t=660s

因此,按照以下步骤操作,我已经完成:

So following this steps, I have done:

  1. 在我已命名为Launcher的主项目中添加新项目
  2. 我使用的是自动签名(因为我的Xcode版本不同)

  1. Add new project inside my main project that I have named Launcher
  2. I am using Automatic Signing (as version of my Xcode) is different

在项目设置">功能"中,我将应用程序沙箱"切换为打开".

In Project Settings > Capabilities I toggled App Sandbox to ON.

在构建阶段中,我添加了以下内容:

In Build Phases I have added this:

我的启动器具有跳过安装=是

My Launcher has Skip Install = YES

我的启动器应用程序中的代码如下所示(我以前甚至使用过Swift来做同样的事情)

Code in my Launcher app looks like this (I have even previously use Swift to do the same)

  - (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];
} 

  • 最后,我在主应用中拥有神奇的代码,可以将应用用作登录项

  • Finally, I have magic code in Main App to enable app as Login Item

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

    1. 我创建了存档,然后有不同的导出选项:

    我无法决定选择哪一个,所以我尝试了所有这些. 保存为Mac App Store部署"-制作已安装在/Applications/目录中的安装程序包,但该应用程序从未运行. "Developer-Id签名","Development-signed","macOS App"都在我导出到Applications目录的目录中创建文件,但是没有人可以使用.

    I couldn't decide which one to choose, so I tried all of them. "Save for Mac App Store Deployment" - made Installation package that has installed in /Applications/ directory but the app never runs. "Developer-Id signed," "Development-signed" , "macOS App" all makes file in a directory that I exported to Applications directory, but no one works.

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

    1. When I click the checkbox button, I could see some window blinking for a while on the screen (Launcher program). When I log out and log in the same window blinking effect appears but Launcher didn't start the Main application. When I click checkbox button again (and turn off Login Item) this blinking effect on user login (system startup) doesn't happen again. So it seems that this addition of Launcher program as Login Item works, but this Launcher couldn't start the Main app. Moreover when I go to /Applications/Main.app/Contents/Library/LoginItems/Launcher.app and click it manually then Launcher app launch Main application correctly (so the path was correct).

    那怎么了?

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

    Then I consider implementation of deprecated approach using kLSSharedFileListSessionLoginItems

    我认为它必须工作,只需在系统偏好设置"中添加一些内容即可 下面的窗口.

    I have thought it must work it just add something in System Preferences this window below.

    但是它也可能出错!

    1. 我选择了Swift中的实现(我发现的所有示例/教程都在Objective-C中),所以我写了这样的东西:

    1. I have chosen implementation in Swift (all examples/tutorials I have found was in Objective-C) So I have written something like this:

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

  • 我已使用它通过单击复选框

  • I have used this to turn on/off Login Item by clicking in the checkbox

      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文件夹(如果有问题的话),但是这种方法也不起作用.

  • I have run it in Debug mode (Xcode Play button) and try to archive it and export to /Applications folder if it matters, but this approach also doesn't work.

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

    Console-printed messaged. Error means that the function Inserting Login Item returns nil.

    因此,在那之后,我什至尝试使用Objective-C(由于Swift中有许多Unmanaged<>)来实现这一点(来自一些stackoverflow示例) 因此,我添加了新的.m和.h文件以及Bridging-Header.h,然后添加了如下代码:

    So after that I even try to implement this (from some stackoverflow example) using Objective-C (as there is many Unmanaged<> in Swift) So I added new .m and .h file and Bridging-Header.h and then a code like this:

    - (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,并且Login Item没有添加到 System Preferences> Users&网上论坛>登录项目.

    Simple (just insertion) without any bells and whistles. It also has the same issue that LSSharedFileListInsertItemURL returns nil and Login Item is not added to System Preferences > Users & Groups > Login Items.

    所以我为什么不能完成这项工作?

    So any idea why I cannot make this work?

    更新1

    我尝试在另一台计算机iMac(MacOS Sierra和最新的XCode 8.3)上使用第一种方法(主应用程序内的辅助启动器应用程序)实施应用程序,并且该应用程序似乎可以正常运行,因此我的操作系统或Xcode(配置文件,应用程序签名或其他)在无法使用该方法的MacBook Air上,我使用OS X El Capitan 10.11.5和Xcode 8.0.

    I have tried to implement application using first approach (helper Launcher application inside Main application) on another computer iMac (MacOS Sierra and the newest XCode 8.3) and it seems to work there correctly so maybe there is something wrong with my OS or Xcode (provisioning profiles, signing of app or whatever) On MacBook Air where this approach doesn't work I am using OS X El Capitan 10.11.5 and Xcode 8.0.

    在此处观看其工作方式: https://youtu.be/6fnLzkh5Rbs 和测试 https://www.youtube.com/watch?v=sUE7Estju0U

    Watch how it works here: https://youtu.be/6fnLzkh5Rbs and testing https://www.youtube.com/watch?v=sUE7Estju0U

    第二种方法也不适用于我的iMac,同时执行LSSharedFileListInsertItemURL时返回 nil .所以我不知道为什么会这样.

    The second approach doesn't work also on my iMac returning the nil while doing LSSharedFileListInsertItemURL. So I don't know why that is happening.

    在此处观看其工作方式: https://youtu.be/S_7ctQLkIuA

    Watch how it works here: 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一起使用,需要禁用应用沙箱!就像下面的视频: https://youtu.be/UvDkby0t_WI

    After upgrade to macOS Sierra 10.12.5 from El Capitan 10.11.5 and using Xcode 8.3.2 instead of Xcode 8.0.0 the second approach also happens to work correctly and is adding Login Items to System Preferences > Users & Groups > Login Items IMPORTANT! To work this approach with LSSharedFileListInsertItemURL needs to disable App Sandboxing! Like in the video below: https://youtu.be/UvDkby0t_WI

    推荐答案

    对于ServiceManagement方法,有时它在您的开发计算机中不起作用,因为Xcode的DerivedData中有该应用程序的另一个副本.系统不知道要启动哪个应用程序.因此,转到〜/Library/Developer/Xcode/DerivedData并删除您的开发副本可能会有所帮助.

    For the ServiceManagement approach, sometimes it doesn't work in your development machine because there is another copy of the app in your Xcode's DerivedData. The system don't know which app to launch. So go to ~/Library/Developer/Xcode/DerivedData and delete your development copy could help.

    这篇关于macOS Swift:如何正确添加应用程序作为登录项的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆