草庐IT

objective-c - macOS Swift : How to properly add application as Login Item

coder 2023-07-17 原文

我花了大约一天(也许多一点)尝试将我的应用程序添加到登录项,按照它在 macOS 启动(用户登录)时启动的顺序。

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

  • 所以按照这个步骤,我已经完成了:
  • 在我命名为 Launcher 的主项目中添加新项目
  • 我正在使用自动签名(作为我的 Xcode 版本)是不同的
  • 在 Project Settings > Capabilities 中,我将 App Sandbox 切换为 ON。
  • 在构建阶段我添加了这个:
  • 我的启动器有跳过安装 = YES
  • 我的 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 signed”、“Development-signed”、“macOS App”都在我导出到 Applications 目录的目录中创建文件,但没有人工作。
  • 当我单击复选框按钮时,我可以看到屏幕上的某个窗口闪烁了一会儿(启动程序)。当我注销并登录同一窗口时,会出现闪烁效果,但 Launcher 未启动主应用程序。当我再次单击复选框按钮(并关闭登录项)时,这种对用户登录(系统启动)的闪烁效果不会再次发生。因此,将 Launcher 程序添加为登录项似乎有效,但此 Launcher 无法启动 Main 应用程序。此外,当我转到/Applications/Main.app/Contents/Library/LoginItems/Launcher.app 并手动单击它时,启动器应用程序正确启动主应用程序(因此路径正确)。
  • 那么出了什么问题呢?

  • 然后我考虑使用
    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.");
            }
        }
    
  • 我已经在 Debug模式(Xcode 播放按钮)下运行它并尝试将它存档并导出到/Applications 文件夹(如果有关系),但这种方法也不起作用。
  • 控制台打印消息。错误意味着插入登录项的函数返回 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);
        }
    }
    

    简单(只是插入),没有任何花里胡哨的东西。
    它也有同样的问题 LSSharedFileListInsertItemURL 返回 nil 并且登录项未添加到 系统偏好设置 > 用户和组 > 登录项。

    那么知道为什么我不能让这个工作吗?

    更新 1

    我尝试在另一台计算机 iMac(MacOS Sierra 和最新的 XCode 8.3)上使用第一种方法(主应用程序中的辅助启动器应用程序)实现应用程序,它似乎在那里正常工作,所以我的操作系统或 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 .所以我不知道为什么会这样。

    在这里观看它是如何工作的:
    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

    最佳答案

    几年前我也为此苦苦挣扎,最终制作了一个 package因为它可以更轻松地为沙盒应用程序添加“登录时启动”功能。

    而不是 lots of manual steps ,你只需要:

    import LaunchAtLogin
    
    LaunchAtLogin.isEnabled = true
    

    关于objective-c - macOS Swift : How to properly add application as Login Item,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44030354/

    有关objective-c - macOS Swift : How to properly add application as Login Item的更多相关文章

    1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

      类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

    2. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

      我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

    3. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

      如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

    4. objective-c - 在设置 Cocoa Pods 和安装 Ruby 更新时出错 - 2

      我正在尝试为我的iOS应用程序设置cocoapods但是当我执行命令时:sudogemupdate--system我收到错误消息:当前已安装最新版本。中止。当我进入cocoapods的下一步时:sudogeminstallcocoapods我在MacOS10.8.5上遇到错误:ERROR:Errorinstallingcocoapods:cocoapods-trunkrequiresRubyversion>=2.0.0.我在MacOS10.9.4上尝试了同样的操作,但出现错误:ERROR:Couldnotfindavalidgem'cocoapods'(>=0),hereiswhy:U

    5. ruby - 你会如何在 Ruby 中表达成语 "with this object, if it exists, do this"? - 2

      在Ruby(尤其是Rails)中,您经常需要检查某物是否存在,然后对其执行操作,例如:if@objects.any?puts"Wehavetheseobjects:"@objects.each{|o|puts"hello:#{o}"end这是最短的,一切都很好,但是如果你有@objects.some_association.something.hit_database.process而不是@objects呢?我将不得不在if表达式中重复两次,如果我不知道实现细节并且方法调用很昂贵怎么办?显而易见的选择是创建一个变量,然后测试它,然后处理它,但是你必须想出一个变量名(呃),它也会在内存中

    6. ruby - 在 Ruby 中,为什么 Array.new(size, object) 创建一个由对同一对象的多个引用组成的数组? - 2

      如thisanswer中所述,Array.new(size,object)创建一个数组,其中size引用相同的object。hash=Hash.newa=Array.new(2,hash)a[0]['cat']='feline'a#=>[{"cat"=>"feline"},{"cat"=>"feline"}]a[1]['cat']='Felix'a#=>[{"cat"=>"Felix"},{"cat"=>"Felix"}]为什么Ruby会这样做,而不是对object进行dup或clone? 最佳答案 因为那是thedocumenta

    7. ruby object.hash - 2

      一个对象的散列值是什么意思?在什么情况下两个对象具有相同的哈希值??还有说Array|Hash不能是Hashkeys,这个跟对象的hash值有关系,为什么? 最佳答案 对于要存储在HashMap或哈希集中的对象,必须满足以下条件:如果认为两个对象相等,则它们的哈希值也必须相等。如果两个对象不被认为是相等的,那么它们的哈希值应该很可能不同(两个不同的对象具有相同哈希值的次数越多,对HashMap/集合的操作性能就越差)。因此,如果两个对象具有相同的哈希值,则很有可能(但不能保证)它们相等。上面“相等”的确切含义取决于散列方法的实现者。

    8. ruby - 为什么 Object 在 Ruby 中既包含内核又继承它? - 2

      在Ruby(1.8.X)中为什么Object既继承了内核又包含了内核?仅仅继承还不够吗?irb(main):006:0>Object.ancestors=>[Object,Kernel]irb(main):005:0>Object.included_modules=>[Kernel]irb(main):011:0>Object.superclass=>nil请注意,在Ruby1.9中情况类似(但更简洁):irb(main):001:0>Object.ancestors=>[Object,Kernel,BasicObject]irb(main):002:0>Object.included

    9. ruby-on-rails - Rails 3 : Looping through array of objects, 忽略数组中的第一个对象? - 2

      在我看来,我正在尝试显示一个对象表,这是我的代码:CategoriesCBB's">然而这是抛出一个错误说:can'tconvertCapabilityBuildingBlockintoArray关系是正确的,错误来self尝试在此处减去数组的第一个对象的行:有什么方法可以忽略数组中的第一个对象来遍历数组吗?谢谢 最佳答案 尝试使用Array.drop-http://www.ruby-doc.org/core/classes/Array.html#M000294 关于ruby-on-ra

    10. ruby-on-rails - my_object.save(false) 并没有真正跳过我的 Active Record 验证 - 2

      所以我一直在努力解决我一直遇到的这个错误,我终于找到了导致它的原因。我一直觉得,当我调用@my_model.save(false)我会跳过我的ActiveRecord验证。事实证明这是部分正确的。我的对象正在保存到数据库中DESPITE我的ActiveRecord验证。我的问题存在是因为我的一个验证在验证过程中修改了一个子模型(这是一个24小时位置的调度应用程序,因此当午餐被保存时,我对照他们保存的那天和第二天检查它们以及确保用户不是指“凌晨2点”表示要上夜类。我的问题是:有没有办法真正跳过我的验证并直接移动到数据库?这是正常的ActiveRecord行为还是我应该更深入地研究我的验证

    随机推荐