Skip to content

Commit

Permalink
Added login item using launchctl
Browse files Browse the repository at this point in the history
  • Loading branch information
NSGolova committed Jan 15, 2021
1 parent d9b2900 commit d498ffd
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 15 deletions.
12 changes: 8 additions & 4 deletions Clippr/Clippr.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
B90DD4442469F60700DB7FDC /* ItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B90DD4432469F60700DB7FDC /* ItemSource.swift */; };
B90DD446246A02B300DB7FDC /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B90DD445246A02B300DB7FDC /* Coding.swift */; };
B945571625AB2C9800897A04 /* permission.png in Resources */ = {isa = PBXBuildFile; fileRef = B945571525AB2C9800897A04 /* permission.png */; };
B955F22625B1B6A80096A1AB /* Loader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B955F22525B1B6A80096A1AB /* Loader.swift */; };
B9A49975246CC154002E02BB /* NSPasteboard+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9A49974246CC154002E02BB /* NSPasteboard+Extensions.swift */; };
B9D11DC8246CE25800420FA6 /* IntroViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9D11DC6246CE25800420FA6 /* IntroViewController.swift */; };
B9D11DC9246CE25800420FA6 /* IntroViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = B9D11DC7246CE25800420FA6 /* IntroViewController.xib */; };
Expand Down Expand Up @@ -76,6 +77,7 @@
B90DD445246A02B300DB7FDC /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = "<group>"; };
B945571025A6EDD900897A04 /* LottieUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LottieUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B945571525AB2C9800897A04 /* permission.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = permission.png; sourceTree = "<group>"; };
B955F22525B1B6A80096A1AB /* Loader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Loader.swift; sourceTree = "<group>"; };
B9A49974246CC154002E02BB /* NSPasteboard+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSPasteboard+Extensions.swift"; sourceTree = "<group>"; };
B9D11DC6246CE25800420FA6 /* IntroViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntroViewController.swift; sourceTree = "<group>"; };
B9D11DC7246CE25800420FA6 /* IntroViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = IntroViewController.xib; sourceTree = "<group>"; };
Expand Down Expand Up @@ -156,6 +158,7 @@
B9F85A162468341600A3636A /* KeyWindow.swift */,
B90DD4412469EF7300DB7FDC /* Defaults.swift */,
B90DD445246A02B300DB7FDC /* Coding.swift */,
B955F22525B1B6A80096A1AB /* Loader.swift */,
);
path = Utils;
sourceTree = "<group>";
Expand Down Expand Up @@ -413,6 +416,7 @@
B9F85A172468341600A3636A /* KeyWindow.swift in Sources */,
B9F85A242468865900A3636A /* WindowController.swift in Sources */,
B9D11DCC246CE27B00420FA6 /* MainViewController.swift in Sources */,
B955F22625B1B6A80096A1AB /* Loader.swift in Sources */,
B9F85A1C2468369B00A3636A /* Clipboard.swift in Sources */,
B90DD43E2469C65700DB7FDC /* MenuItem.swift in Sources */,
B90DD4422469EF7300DB7FDC /* Defaults.swift in Sources */,
Expand Down Expand Up @@ -597,8 +601,8 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.12;
MARKETING_VERSION = 0.1;
PRODUCT_BUNDLE_IDENTIFIER = dev.nsgolova.Clippr;
MARKETING_VERSION = 0.1.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.golova.Clippr;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
Expand All @@ -620,8 +624,8 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.12;
MARKETING_VERSION = 0.1;
PRODUCT_BUNDLE_IDENTIFIER = dev.nsgolova.Clippr;
MARKETING_VERSION = 0.1.0;
PRODUCT_BUNDLE_IDENTIFIER = dev.golova.Clippr;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
Expand Down
10 changes: 10 additions & 0 deletions Clippr/Clippr/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
registerShortcut()
}

func applicationWillFinishLaunching(_ notification: Notification) {
guard let bundleIdentifier = Bundle.main.bundleIdentifier else { return }
NSRunningApplication.runningApplications(withBundleIdentifier: bundleIdentifier).forEach {
if NSRunningApplication.current != $0 {
$0.activate(options: .activateIgnoringOtherApps)
NSRunningApplication.current.forceTerminate()
}
}
}

func applicationDidFinishLaunching(_ aNotification: Notification) {
NSApp.setActivationPolicy(.accessory)

Expand Down
9 changes: 1 addition & 8 deletions Clippr/Clippr/Clippr.entitlements
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.icloud-container-identifiers</key>
<array/>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
</dict>
<dict/>
</plist>
2 changes: 2 additions & 0 deletions Clippr/Clippr/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.productivity</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>LSUIElement</key>
Expand Down
Binary file modified Clippr/Clippr/MainUI/Intro/permission.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions Clippr/Clippr/MainUI/MainViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ class MainViewController: NSViewController {
@objc
func keyPathsForValuesAffectingSaveToPreferences() -> Set<String> { ["Preferences.savingMethod"] }

@objc dynamic var launchOnLogin: Bool {
get {
Preferences.launchOnLogin
}
set {
Preferences.launchOnLogin = newValue
}
}

@objc
func keyPathsForValuesAffectingLaunchOnLogin() -> Set<String> { ["Preferences.launchOnLogin"] }

var keylessWindow: KeylessWindow? { view.window as? KeylessWindow }

override func viewDidAppear() {
Expand Down
3 changes: 3 additions & 0 deletions Clippr/Clippr/MainUI/MainViewController.xib
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="-2" name="value" keyPath="launchOnLogin" id="5qD-KD-7a9"/>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="3XM-ia-baV">
<rect key="frame" x="216" y="249" width="48" height="19"/>
Expand Down
8 changes: 5 additions & 3 deletions Clippr/Clippr/Utils/Defaults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ class Preferences: NSObject {
@DefaultsStored("launchOnLogin")
@objc dynamic static var launchOnLogin = false {
didSet {
guard let agentBundleIdentifier = Bundle.main.bundleIdentifier as NSString? else { return }

SMLoginItemSetEnabled(agentBundleIdentifier, launchOnLogin)
if launchOnLogin {
Loader.loadAgent()
} else {
Loader.unloadAgent()
}
}
}
}
Expand Down
107 changes: 107 additions & 0 deletions Clippr/Clippr/Utils/Loader.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//
// Loader.swift
// Clippr
//
// Created by Viktor Radulov on 1/15/21.
// Copyright © 2021 Viktor Radulov. All rights reserved.
//

import Foundation

struct LaunchctlRegistry: Codable {

enum ProcessType: String, Codable {
case background = "Background"
case standard = "Standard"
case adaptive = "Adaptive"
case interactive = "Interactive"
}

enum SessionType: String, Codable {
case background = "Background"
case aqua = "Aqua"
case loginWindow = "LoginWindow"
}

static let agentsPath = "/Library/LaunchAgents/"

var disabled: Bool?
var launchOnlyOnce: Bool?
var keepAlive = true
var sessionType: SessionType?
let label: String
let program: String
var processType: ProcessType?
var machServices: [String: Bool]?
var runAtLoad: Bool?
var arguments: [String]?

var defaultPlistName: String {
return label + ".plist"
}

private enum CodingKeys: String, CodingKey {
var rawValue: String {
switch self {
case .disabled: return LAUNCH_JOBKEY_DISABLED
case .keepAlive: return LAUNCH_JOBKEY_KEEPALIVE
case .sessionType: return LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE
case .label: return LAUNCH_JOBKEY_LABEL
case .program: return LAUNCH_JOBKEY_PROGRAM
case .processType: return LAUNCH_JOBKEY_PROCESSTYPE
case .machServices: return LAUNCH_JOBKEY_MACHSERVICES
case .runAtLoad: return LAUNCH_JOBKEY_RUNATLOAD
case .arguments: return LAUNCH_JOBKEY_PROGRAMARGUMENTS
case .launchOnlyOnce: return LAUNCH_JOBKEY_LAUNCHONLYONCE
}
}

case disabled
case keepAlive
case sessionType
case label
case program
case processType
case machServices
case runAtLoad
case arguments
case launchOnlyOnce
}

init?(bundle: Bundle) {
guard let bundleIdentifier = bundle.bundleIdentifier,
let executablePath = bundle.executablePath else { return nil }
self.init(label: bundleIdentifier, program: executablePath)
}

init(label: String, program: String) {
self.label = label
self.program = program
}
}

class Loader {
static let appBundle = Bundle.main
static let bundleIdentifier = appBundle.bundleIdentifier!
static let pathToLaunchPlist = NSHomeDirectory() + LaunchctlRegistry.agentsPath + bundleIdentifier + ".plist"

static func unloadAgent() {
let fileManager = FileManager.default
if fileManager.fileExists(atPath: pathToLaunchPlist) {
Process.launchedProcess(launchPath: "/bin/launchctl", arguments: ["unload", pathToLaunchPlist]).waitUntilExit()
try? fileManager.removeItem(atPath: pathToLaunchPlist)
}
}

static func loadAgent() {
var registry = LaunchctlRegistry(bundle: appBundle)!
registry.processType = .interactive
registry.sessionType = .aqua
registry.disabled = false
registry.launchOnlyOnce = true
registry.arguments = [appBundle.executablePath!, "fromLaunchd"]
try? FileManager.default.createDirectory(atPath: NSHomeDirectory() + LaunchctlRegistry.agentsPath, withIntermediateDirectories: false, attributes: [:])
try! (try! PropertyListEncoder().encode(registry)).write(to: URL(fileURLWithPath: pathToLaunchPlist))
Process.launchedProcess(launchPath: "/bin/launchctl", arguments: ["load", pathToLaunchPlist])
}
}

0 comments on commit d498ffd

Please sign in to comment.