App in Autostart hinzufügen/entfernen bei aktivierter Sandbox

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • App in Autostart hinzufügen/entfernen bei aktivierter Sandbox

    In meiner App soll es möglich sein, diese zum Autostart hinzuzufügen oder entfernen zu können - damit die App startet, wenn das OS hochfährt (bzw. auch nicht). Folgender Code funktioniert problemlos - genau so lange, bis ich die Sandbox aktiviere. Auf meiner Suche nach einer Lösung bin ich häufig auf die Notwendigkeit einer "Helper App" gestoßen - kann mir aber einfach nicht vorstellen, dass es nicht auch einfacher geht?

    Quellcode

    1. func applicationIsInStartUpItems() -> Bool {
    2. return (itemReferencesInLoginItems().existingReference != nil)
    3. }
    4. func itemReferencesInLoginItems() -> (existingReference: LSSharedFileListItemRef?, lastReference: LSSharedFileListItemRef?) {
    5. var itemUrl : UnsafeMutablePointer<Unmanaged<CFURL>?> = UnsafeMutablePointer<Unmanaged<CFURL>?>.alloc(1)
    6. if let appUrl : NSURL = NSURL.fileURLWithPath(NSBundle.mainBundle().bundlePath) {
    7. let loginItemsRef = LSSharedFileListCreate(
    8. nil,
    9. kLSSharedFileListSessionLoginItems.takeRetainedValue(),
    10. nil
    11. ).takeRetainedValue() as LSSharedFileListRef?
    12. if loginItemsRef != nil {
    13. let loginItems: NSArray = LSSharedFileListCopySnapshot(loginItemsRef, nil).takeRetainedValue() as NSArray
    14. println("There are \(loginItems.count) login items")
    15. if(loginItems.count > 0){
    16. let lastItemRef: LSSharedFileListItemRef = loginItems.lastObject as LSSharedFileListItemRef
    17. for var i = 0; i < loginItems.count; ++i {
    18. let currentItemRef: LSSharedFileListItemRef = loginItems.objectAtIndex(i) as LSSharedFileListItemRef
    19. if LSSharedFileListItemResolve(currentItemRef, 0, itemUrl, nil) == noErr {
    20. if let urlRef: NSURL = itemUrl.memory?.takeRetainedValue() {
    21. println("URL Ref: \(urlRef.lastPathComponent)")
    22. if urlRef.isEqual(appUrl) {
    23. return (currentItemRef, lastItemRef)
    24. }
    25. }
    26. } else {
    27. println("Unknown login App")
    28. }
    29. }
    30. println("App was not found in startup items")
    31. return (nil, lastItemRef)
    32. } else {
    33. let addatstart: LSSharedFileListItemRef = kLSSharedFileListItemBeforeFirst.takeRetainedValue()
    34. return(nil,addatstart)
    35. }
    36. }
    37. }
    38. return (nil, nil)
    39. }
    40. func toggleLaunchAtStartup() -> Bool {
    41. var autoStartState : Bool = false
    42. let itemReferences = itemReferencesInLoginItems()
    43. let shouldBeToggled = (itemReferences.existingReference == nil)
    44. let loginItemsRef = LSSharedFileListCreate(
    45. nil,
    46. kLSSharedFileListSessionLoginItems.takeRetainedValue(),
    47. nil
    48. ).takeRetainedValue() as LSSharedFileListRef?
    49. if loginItemsRef != nil {
    50. if shouldBeToggled {
    51. if let appUrl : CFURLRef = NSURL.fileURLWithPath(NSBundle.mainBundle().bundlePath) {
    52. LSSharedFileListInsertItemURL(
    53. loginItemsRef,
    54. itemReferences.lastReference,
    55. nil,
    56. nil,
    57. appUrl,
    58. nil,
    59. nil
    60. )
    61. println("App was added to login items")
    62. autoStartState = true
    63. }
    64. } else {
    65. if let itemRef = itemReferences.existingReference {
    66. LSSharedFileListItemRemove(loginItemsRef,itemRef);
    67. println("App was removed from login items")
    68. autoStartState = false
    69. }
    70. }
    71. }
    72. return autoStartState
    73. }
    Alles anzeigen
  • Bin das Tutorial jetzt soweit durchgegangen. Habe die Helper.app selbst einfach als Obj-C Projekt angelegt, so konnte ich den Code direkt übernehmen.
    Den Teil der Main.app versuche ich nun nach Swift zu übersetzen. Eigentlich passiert doch da nicht viel, oder?

    Obj-C (aus dem genannten Tutorial):
    ​@synthesize launchAtLoginButton;
    -(IBAction)toggleLaunchAtLogin:(id)sender
    {
    int clickedSegment = [sender selectedSegment];
    if (clickedSegment == 0) { // ON
    // Turn on launch at login
    if (!SMLoginItemSetEnabled ((__bridge CFStringRef)@"com.timschroeder.LaunchAtLoginHelperApp", YES)) {
    NSAlert *alert = [NSAlert alertWithMessageText:@"An error ocurred"
    defaultButton:@"OK"
    alternateButton:nil
    otherButton:nil
    informativeTextWithFormat:@"Couldn't add Helper App to launch at login item list."];
    [alert runModal];
    }
    }
    if (clickedSegment == 1) { // OFF
    // Turn off launch at login
    if (!SMLoginItemSetEnabled ((__bridge CFStringRef)@"com.timschroeder.LaunchAtLoginHelperApp", NO)) {
    NSAlert *alert = [NSAlert alertWithMessageText:@"An error ocurred"
    defaultButton:@"OK"
    alternateButton:nil
    otherButton:nil
    informativeTextWithFormat:@"Couldn't remove Helper App from launch at login item list."];
    [alert runModal];
    }
    }
    }


    Ohne die Alerts, mein aktueller Stand in Swift:

    ​let launchDaemon: CFStringRef = "de.meinName.HelperApp"
    func testLogin(){
    SMLoginItemSetEnabled(launchDaemon, Boolean(1))
    if SMLoginItemSetEnabled(launchDaemon, Boolean(1)) != 0
    {
    println("login active")
    } else {
    println("login inactive")
    }
    }

    ... als Feedback erhalte ich jedoch stets "login active", egal wie ich den Wert mit ​SMLoginItemSetEnabled(launchDaemon, Boolean()) setze. Warum ist das so? Außerdem ist mir nicht ganz klar, warum ich die Helper.app in die LaunchItems packen muss und das nicht direkt mit der Main.app machen kann?
  • Der letzte Teil war natürlich Quatsch, da die if-Abfrage Unsinn ist bzw. ich darüber nur den aktuellen Stand abfrage, aber nichts ändere.

    Mir ist halt leider überhaupt nicht klar, wie ich jetzt den Wechsel (Autologin ein oder ausschalten) hinbekomme. Ich bin gerade soweit, dass die Helper.app mitstartet und auch im richtigen Verzeichnis liegt. Habe mir sowohl das genannte Tutorial angesehen, die Referenz und auch dieses Tutorial - hat mich nicht weitergebracht.

    Verstehe ich denn richtig, dass SMLoginItemSetEnabled nur die HelperApp aktiviert? Andererseits gebe ich ja auch den SOLL-Zustand mit, daher müsste sie doch eigentlich den Zustand ändern? Ein SMLoginItemSetEnabled(launchDaemon, Boolean(1)) bewirkt leider gar nichts, wie mir scheint.

    ?( ;(
  • Osxer schrieb:

    In diesem Fall, ja. Die HelperApp wird beim Start aktiviert, und diese wiederum öffnet deine MainApp.


    Okay, dann müsste also der mitgelieferte Boolean-Wert angeben, ob der Autostart aktiviert oder deaktiviert werden soll, oder? Davon bin ich bisher ausgegangen. Den Code der Helper.app poste ich gleich.
  • Nochmal einen Schritt zurück - wenn ich mir dieses Tutorial anschaue, scheint das Hinzufügen und Entfernen des LoginItems nicht sehr schwierig:

    Quellcode

    1. ​- (void)addLoginItem {
    2. NSString *ref = @"com.madebynotion.myLoginHelper";
    3. if (!SMLoginItemSetEnabled((CFStringRef)ref, true)) {
    4. NSLog(@"SMLoginItemSetEnabled failed.");
    5. }
    6. }


    übersetze ich in Swift mit:

    Quellcode

    1. ​let launchDaemon: CFStringRef = "de.mrtnlxo.meineMainApp"
    2. func addLoginItem(){
    3. if SMLoginItemSetEnabled(launchDaemon, true) {
    4. println("SMLoginItemSetEnabled failed")
    5. }
    6. }


    ... das Problem ist leider: Es kompiliert nicht. Fehlermeldung "Type 'Boolean' does not conform to protocol 'BooleanType'" ?(
  • Merci Michael! Mit den ​extensions funktioniert SMLoginItemSetEnabled soweit - ich erhalte stets true als Rückgabewert, was laut Dokumentation bedeutet, dass die Registrierung des LoginItems funktioniert hat:

    Quellcode

    1. ​let launchDaemon: CFStringRef = "de.mrtnlxo.HelperAppObjC"
    2. extension Boolean : BooleanLiteralConvertible {
    3. public init(booleanLiteral value: Bool) {
    4. self = value ? 1 : 0
    5. }
    6. }
    7. extension Boolean : BooleanType {
    8. public var boolValue : Bool {
    9. return self != 0
    10. }
    11. }
    12. func addLoginItem(){
    13. if SMLoginItemSetEnabled(launchDaemon, true) == 1 {
    14. // Returns true if the requested change has taken effect
    15. println("success")
    16. }
    17. }
    18. func removeLoginItem(){
    19. if SMLoginItemSetEnabled(launchDaemon, false) == 1 {
    20. // Returns true if the requested change has taken effect
    21. println("success")
    22. }
    23. }
    Alles anzeigen


    Der Autostart an sich funktioniert leider noch nicht ;( Ich bin ratlos:

    Grundsätzlich habe ich es so verstanden, dass die HelperApp lediglich die MainApp öffnen muss um sich selbst dann wieder zu schließen. Korrekt?

    Die Bundle-ID in der Variablen launchDaemon ("de.mrtnlxo.HelperAppObjC") entspricht exakt der der HelperApp.

    Sowohl in der MainApp als auch HelperApp ist App Sandbox aktiviert. Beide Apps haben unter "Identity" die gleiche Developer Signatur hinterlegt.

    In den Projekteinstellungen der MainApp habe ich in den Build Phases zudem angegeben, die HelperApp nach Contents/Library/LoginItems zu kopieren. Das funktioniert auch, denn wenn ich mir den Paketinhalt der App anzeigen lasse, existiert der Ordner LoginItems samt HelperApp darin.

    Meine HelperApp selbst ist auf Basis Objective-C angelegt:

    Quellcode

    1. #import "AppDelegate.h"
    2. #import <ServiceManagement/ServiceManagement.h> // Ist das in der HelperApp notwendig?
    3. @interface AppDelegate ()
    4. @property (weak) IBOutlet NSWindow *window;
    5. @end
    6. @implementation AppDelegate
    7. - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
    8. {
    9. [[NSWorkspace sharedWorkspace] launchApplication:
    10. @"Contents/Library"]; // Hier gehört der Pfad zum MainBundle hin. Bin unsicher, ob meine Angabe richtig ist?
    11. // Check if main app is already running; if yes, do nothing and terminate helper app
    12. BOOL alreadyRunning = NO;
    13. NSArray *running = [[NSWorkspace sharedWorkspace] runningApplications];
    14. for (NSRunningApplication *app in running) {
    15. if ([[app bundleIdentifier] isEqualToString:@"de.mrtnlxo.MainApp"]) {
    16. alreadyRunning = YES;
    17. }
    18. }
    19. if (!alreadyRunning) {
    20. NSString *path = [[NSBundle mainBundle] bundlePath];
    21. NSArray *p = [path pathComponents];
    22. NSMutableArray *pathComponents = [NSMutableArray arrayWithArray:p];
    23. [pathComponents removeLastObject];
    24. [pathComponents removeLastObject];
    25. [pathComponents removeLastObject];
    26. [pathComponents addObject:@"MacOS"];
    27. [pathComponents addObject:@"MainApp"];
    28. NSString *newPath = [NSString pathWithComponents:pathComponents];
    29. [[NSWorkspace sharedWorkspace] launchApplication:newPath];
    30. }
    31. [NSApp terminate:nil];
    32. }
    Alles anzeigen


    Was mich ratlos zurücklässt ist die vorallem die Tatsache, dass die HelperApp, sofern ich sie manuell aufrufe (wenn ich in den Paketinhalt der MainApp reingehe...), offensichtlich funktioniert: Sie öffnet die MainApp und schließt sich dann selbst.

    Getestet habe ich immer aus dem Applications-Ordner heraus, da ich gelesen hatte, dass LoginItems nur von dort funktioniert.

    Würde mich freuen, wenn noch jemand einen Tipp für mich hat.

    Viele Grüße
    Martin
  • Hey!
    Mein Code ist eigentlich der gleiche wie deiner. Nur in der if-Abfrage hab ich was anderes:

    Quellcode

    1. if (!alreadyRunning)
    2. {
    3. NSString *path = [[NSBundle mainBundle] bundlePath];
    4. NSArray *p = [path pathComponents];
    5. NSMutableArray *pathComponents = [NSMutableArray arrayWithArray:p];
    6. [pathComponents removeLastObject];
    7. [pathComponents removeLastObject];
    8. [pathComponents removeLastObject];
    9. [pathComponents addObject:@"MacOS"];
    10. [pathComponents addObject:@"AppName"];
    11. NSString *newPath = [NSString pathWithComponents:pathComponents];
    12. NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSArray arrayWithObject:@"launchAtLogin"], NSWorkspaceLaunchConfigurationArguments, nil];
    13. [[NSWorkspace sharedWorkspace] launchApplicationAtURL:[NSURL fileURLWithPath:newPath]
    14. options:NSWorkspaceLaunchWithoutActivation
    15. configuration:dict
    16. error:nil];
    17. }
    Alles anzeigen


    ServiceManagement brauchst du nicht. Was hast du mit Zeile 14/15 vor? Hast du die HelperApp der MainApp korrekt hinzugefügt, wie es dem timschröder-tut beschrieben ist?
  • Habe meine if (!alreadyRunning) Abfrage gegen deine ausgetauscht - die App verhält sich leider wie bisher.

    Bin gerade nochmal jeden Schritt im Tim Schröder-Tutorial durchgegangen: Ich habe alles nach Anleitung gemacht. Wie gesagt, sieht im Bundle auch alles okay aus. Der Helper direkt aufgerufen startet meine MainApp. Meine MainApp gibt positives Feedback beim SMLoginItemSetEnabled.

    Maximale Verzweiflung ||
  • Ich konnte das Problem nun lösen: Unter einem anderen Benutzeraccount funktioniert die App inklusive Helper. Ich vermute, mein tagelanges Testen hat irgendetwas Blockierendes in den LoginItems meines Benutzeraccounts hinterlassen... vielen Dank für eure Unterstützung!