Commit 115b4572 authored by Joel Rennich's avatar Joel Rennich

Merge branch 'eapolclient' into 'Experimental'

Eapolclient->Experimental

See merge request !76
parents 414c13b5 039e2b99
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0800"
LastUpgradeVersion = "0910"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
......@@ -115,6 +115,14 @@
argument = "-v"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "-shares"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "-rawLDAP"
isEnabled = "YES">
</CommandLineArgument>
</CommandLineArguments>
<AdditionalOptions>
</AdditionalOptions>
......
......@@ -58,13 +58,13 @@
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "NoMAD/NoMADMenuController.swift"
timestampString = "530133121.618043"
timestampString = "538679095.913172"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "466"
endingLineNumber = "466"
landmarkName = "NoMADMenuController"
landmarkType = "3">
startingLineNumber = "467"
endingLineNumber = "467"
landmarkName = "NoMADMenuClickLogOut(_:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
......
# NoMAD Actions Readme
This file lays out the design goals of the Actions Menu that you can add into NoMAD and how to use it.
## Philosophy
The Actions Menu is composed of "actions" which are defined by preferences in the "menu.nomad.actions" domain. In this domain Actions are listed as an array of dictionaries with each dictionary comprising one action. NoMAD Actions are simultaneously attempting to be highly configurable without being overly complicated, as such most things are optional.
The Actions Menu is aware of updates to the preference file and does not require NoMAD to be re-launched in order to load the new actions. When the preferences are changed, NoMAD rebuilds the entire menu from scratch and runs all tests and other housekeeping items.
Actions are run lazilly in the background, so don't expect immediate satisfaction when testing things. Since the commands are checked every time NoMAD does an update cycle (on launch, every 15 minutes, on network change, or when the menu is clicked), but run in the background, the first time the UI is shown it may have the old status/text/data. The actions will update in the background and the UI will update accordingly.
## Global Options
Within the `menu.nomad.actions` there are a few global pref keys that can determine the behavior of the menu.
| Attribute |Definition |Type | Required
|---|---|---|---|---|
| Version | The numeric version of the pref file. Currently only "1" is suppored| Int | yes
| MenuIcon | Determines if the Actions menu itself will have a red/yellow/green light next to it. | Bool | no
|MenuText| Determines if the text of the main Action menu be the result of a command | Bool | no
* MenuIcon will put a red/yellow/green icon next to the main Action menu based upon "worst" of the visible items in the submenu. In other words if you have any visible submenu actions that have a red icon next to them, the main menu will have a red icon. If any visible submenu actions are yellow, and none are red, the main menu item will have a yellow icon.
* MenuText requires a command to return a result of `<<menu>>` followed by the text you'd like to make the menu. The last command to return a result containing `<<menu>>` will determine what the menu title is.
## Anatomy of an action
An action is comprised of some meta data and then four phases. Each phase has a collection of Commands in them. These Commands have the Command itself and then a CommandOptions that can modify the command. Commands can execute external scripts or use the built in functions included with Actions. The only required part of the Action is the name of the action, all the other parts are optional. To break out a sample Action
| Attribute |Definition |Type | Required
|---|---|---|---|---|
|Name| Plaintext name of the Action. Will be used for the menu name if a Title isn't given | String | yes
|Title| Command Set that determines the name of the menu item | Dictionary | no
| Show | Command Set that determine if the item should be shown in the menu | Array | no
| Action | Command Set that make up the actual Action itself | Array | no
| Post | Command Set that will happen after the Action commands are run | Array | no
| GUID | Unique ID for the Action | String | no
|Connected | If the action set should only be run when connected to the AD domain | Bool | no
|Timer| Length in minutes between firing the Action | Int | no
|ToolTip| The text to be shown when hovering over the menu item | String | no
* Note that the Title Command set can only have one command
* If the Title command returns "false" or "true" the text of the title won't be updated. Instead a red, in the case of "false", or green dot will be next to the menu item and the title will be the Name of the action set. If the Title command returns "yellow" a yellow dot will be shown next to the item.
* An Action with the Name of "Separator" will become a separator bar in the menu.
* An Action with no Action command set will be greyed out in the menu bar. If you'd like to have a menu item enabeld but with no action, add the "true" command to the Action command set.
## Commands
NoMAD has a number of built-in commands to make things easy, however, since one command is to execute a script, you'll quickly be able to make any unique commands that you want.
Each command has a CommandOptions value that determines what the command does. All options are strings. All commands can return results. A result of "false" is used by the Show action to prevent the menu item from being shown.
| Command | Function | Options
|---|---|---|
| path | Excute a binary at a specific file path | The path to execute
| app | Launch an app at a specific file path | The path to the application
| url | Launch a URL in the user's default browser | The URL to launch
| ping | Ping a host, will return false if the host is unpingable | The host to ping
| adgroup | Determine if the current user is a member of an AD group | The group to test with
| alert | Display a modal dialog to the user | Text of the dialog
|notify| Display a notification in the notification center | Text of the notification
|false| A command that always returns false | Anything
|true| A command that always returns true | Anything
* The result of any command can be passed on to the next one. Using `<<result>>` in your command options will cause it to be replaced with the result of the previous command. Note that "true" or "false" results will not be conveyed to the next command.
* When using the "alert" and "notify" commands, if the command options are blank or are either "true" or "false", no alert or notification will be displayed. You can use this to only show errors.
* Adding "True" or "False" at the end of the command will only trigger that command if the previously run command returns "true" or "false". For example, using "alertTrue" as the command name will only run that command if the previously run command returned "true". Keep in mind that the result state is persistant, so you can have one "path" command, for example, return "true" and then multiple commands following it with "False" in the command name. None of the "False" commands would run. Also note that capitalization is important here.
* The "true" and "false" commands, note the capitalization, can be used to clear any previous results in the command set.
* Results do not persist between command sets.
* Command options support the standard NoMAD variable swaps such as `<<domain>>`, `<<user>>`, and `<<email>>`.
## Workflow
* On launch NoMAD looks at the `menu.nomad.actions` preference domain and reads in any Actions.
* For each Action, NoMAD will run the Show command set to determine if the menu item should be shown. Note that all commands in the Show command set have to return positive for the menu item to be shown.
* For items that pass the Show test, NoMAD will then run the Title command set to get the text of the menu item. If no command set has been configured, the Action name will be used instead.
* An item that is clicked on will cause the item's Action command set to be run.
* Following the Action set running, the Post set will then be run acting on the result of the Action set.
* Every time NoMAD updates (every 15 minutes, network change or NoMAD menu interaction) the Actions items will be updated with the same process.
## URIs
Actions will respond to `nomad://action/ActionName` URIs so you can run them from the command line via `open nomad://action/DoSomething`. For actions with a space in the name, or other upper ascii characters, use standard percent encoding when listing them in the URI. Note: when running actions in this way, the Title or Show command sets will not be executed.
Using `nomad://actionsilent/ActionName` will run the specified Action command set, but not the corresponding Post command set.
......@@ -18,8 +18,8 @@ let updateNotification = Notification(name: Notification.Name(rawValue: "menu.no
class AppDelegate: NSObject, NSApplicationDelegate {
var refreshTimer: Timer?
var refreshActivity: NSBackgroundActivityScheduler?
@objc var refreshTimer: Timer?
@objc var refreshActivity: NSBackgroundActivityScheduler?
func applicationDidFinishLaunching(_ aNotification: Notification) {
......@@ -51,7 +51,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
if (defaults.string(forKey: Preferences.stateChangeAction) != "" ) {
myLogger.logit(.base, message: "Firing State Change Action")
cliTask(defaults.string(forKey: Preferences.stateChangeAction)! + " &")
let _ = cliTask(defaults.string(forKey: Preferences.stateChangeAction)! + " &")
}
}
......@@ -76,13 +76,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
refreshTimer?.invalidate()
}
func sendUpdateMessage() {
@objc func sendUpdateMessage() {
myLogger.logit(.base, message: "It's been a while, checking things.")
NotificationQueue.default.enqueue(updateNotification, postingStyle: .now)
}
/// Schedule our update notification to fire every 15 minutes or so.
func scheduleTimer() {
@objc func scheduleTimer() {
if #available(OSX 10.12, *) {
refreshActivity = NSBackgroundActivityScheduler(identifier: "com.trusourcelabs.updatecheck")
refreshActivity?.repeats = true
......
This diff is collapsed.
......@@ -118,11 +118,11 @@ public class CertificateSigningRequest:NSObject {
self.init(commonName: nil, organizationName:nil, organizationUnitName:nil, countryName:nil, cryptoAlgorithm: cryptoAlgorithm)
}
public func build(_ publicKeyBits:Data, privateKey: SecKey) -> Data?{
@objc public func build(_ publicKeyBits:Data, privateKey: SecKey) -> Data?{
var certificationRequestInfo = buldCertificationRequestInfo(publicKeyBits)
var shaBytes:[UInt8]
var padding:SecPadding
// let padding = SecPadding.PKCS1SHA1 // Not needed on macOS as there is only one choice.
var certificationRequestInfoBytes = [UInt8](repeating: 0, count: certificationRequestInfo.count)
certificationRequestInfo.copyBytes(to: &certificationRequestInfoBytes, count: certificationRequestInfo.count)
var digest:[UInt8]
......@@ -137,7 +137,6 @@ public class CertificateSigningRequest:NSObject {
digest = [UInt8](repeating: 0, count: cryptoAlgorithm.digestLength)
CC_SHA1_Final(&digest, &SHA1)
shaBytes = SEQUENCE_OBJECT_sha1WithRSAEncryption
padding = SecPadding.PKCS1SHA1
case .sha256:
......@@ -148,7 +147,6 @@ public class CertificateSigningRequest:NSObject {
digest = [UInt8](repeating: 0, count: cryptoAlgorithm.digestLength)
CC_SHA256_Final(&digest, &SHA256)
shaBytes = SEQUENCE_OBJECT_sha256WithRSAEncryption
padding = SecPadding.PKCS1SHA1
case .sha512:
......@@ -159,7 +157,6 @@ public class CertificateSigningRequest:NSObject {
digest = [UInt8](repeating: 0, count: cryptoAlgorithm.digestLength)
CC_SHA512_Final(&digest, &SHA512)
shaBytes = SEQUENCE_OBJECT_sha512WithRSAEncryption
padding = SecPadding.PKCS1SHA1
default:
......@@ -169,7 +166,7 @@ public class CertificateSigningRequest:NSObject {
// Build signature - step 2: Sign hash
var signature = [UInt8](repeating: 0, count: 256)
var signatureLen = signature.count
let signatureLen = signature.count
// this is iOS-only - converted to using SecTransform - Joel Rennich
......@@ -204,7 +201,7 @@ public class CertificateSigningRequest:NSObject {
return certificationRequest
}
func buldCertificationRequestInfo(_ publicKeyBits:Data) -> Data{
@objc func buldCertificationRequestInfo(_ publicKeyBits:Data) -> Data{
var certificationRequestInfo = Data(capacity: 256)
//Add version
......@@ -249,7 +246,7 @@ public class CertificateSigningRequest:NSObject {
}
/// Utility class methods ...
func buildPublicKeyInfo(_ publicKeyBits:Data)-> Data{
@objc func buildPublicKeyInfo(_ publicKeyBits:Data)-> Data{
var publicKeyInfo = Data(capacity: 390)
......@@ -358,7 +355,7 @@ public class CertificateSigningRequest:NSObject {
// From http://stackoverflow.com/questions/3840005/how-to-find-out-the-modulus-and-exponent-of-rsa-public-key-on-iphone-objective-c
func getPublicKeyExp(_ publicKeyBits:Data)->Data{
@objc func getPublicKeyExp(_ publicKeyBits:Data)->Data{
var iterator = 0
......@@ -377,7 +374,7 @@ public class CertificateSigningRequest:NSObject {
return publicKeyBits.subdata(in: range)
}
func getPublicKeyMod(_ publicKeyBits: Data)->Data{
@objc func getPublicKeyMod(_ publicKeyBits: Data)->Data{
var iterator = 0
......
//
// DFSResolver.swift
// NoMAD
//
// Created by Benedikt Wiesnet, on 08.12.17.
//
// build issues with this class
// will clean up
//import Foundation
//import NetFS
//
//extension String {
// //: ### Base64 encoding a string
// func base64Encoded() -> String? {
// if let data = self.data(using: .utf16) {
// return data.base64EncodedString()
// }
// return nil
// }
//
// //: ### Base64 decoding a string
// func base64Decoded() -> String? {
// if let data = Data(base64Encoded: self) {
// return String(data: data, encoding: .utf16)
// }
// return nil
// }
//}
//
//class DFSResolver: NSObject {
//
// static func checkAndReplace(url: URL)->String{
// let test = DFSResolver()
// do{
// myLogger.logit(.debug, message: "Try if \(url.absoluteString) is a dfs share!")
// let (result,str) = try test.resolve(dfspath: url.absoluteString)
// myLogger.logit(.debug, message: "Response for \(url.absoluteString) is: \(result) with \(str ?? "")")
// if(result){
// myLogger.logit(.debug, message: "Replaced Mount: \( test.replace(dfspath: url.absoluteString, sharepath: str!))")
// return test.replace(dfspath: url.absoluteString, sharepath: str!)
// }else{
// return url.absoluteString
// }
//
// }catch {
// myLogger.logit(.base, message: "\(error.localizedDescription)")
// return url.absoluteString
// }
// }
//
// func replace(dfspath: String, sharepath: String)->String{
// var result = sharepath
// var rest: [String] = []
// if(dfspath.contains("://")){
// rest = dfspath.components(separatedBy: "://")[1].components(separatedBy: "/")
// }else{
// rest = dfspath.components(separatedBy: "/")
// }
//
// if(rest.count >= 2){
// rest.remove(at: 1)
// rest.remove(at: 0)
// result+="/"+rest.joined(separator: "/")
// }
// return result
// }
//
// func resolve(dfspath: String) throws ->(Bool,String?){
// let (server,namespace) = splitDFSPath(dfspath: dfspath)
// let base = getBaseDN(server: server)
// let uncRaw = try queryLDAP(server: server,namespace: namespace,base: base)
// let unc = decode(base64: uncRaw)
// if(unc.count > 0){
// return (true,unc[0])
// }
// return (false,nil)
// }
//
// // Splits DFS Addresses, returns server and namespace
// // which is localhost and SorryThereIsNoDFS if input (dfspath) has an invalid format
// func splitDFSPath(dfspath: String)->(String,String){
// var pathWithoutProtocol = ""
//
// if(dfspath.contains("://")){
// pathWithoutProtocol = dfspath.components(separatedBy: "://")[1]
// }else{
// pathWithoutProtocol = dfspath
// }
//
// if(pathWithoutProtocol.contains("/")){
// return (pathWithoutProtocol.components(separatedBy: "/")[0],pathWithoutProtocol.components(separatedBy: "/")[1])
// }
//
// return ("localhost","SorryThereIsNoDFS")
// }
//
// // Transforms Base Adress example.org to DC=example,DC=org,
// // returns empty string when format is not fqdn of server
// func getBaseDN(server: String)->String{
// var result = ""
// let split = server.components(separatedBy: ".")
// if(split.count <= 0){
// return result
// }
// for dc in split {
// result = result+"DC="+dc+","
// }
// result = result.substring(to: result.index(before: result.endIndex))
// return result;
// }
//
// func queryLDAP(server: String, namespace: String, base: String) throws -> String{
// let ldaputils = LDAPServers()
// //ldaputils.staticServer = server
// ldaputils.setDomain(server)
// ldaputils.defaultNamingContext = base
// let tempResult : [[String:String]] = try ldaputils.getLDAPInformation(["(&(objectClass=msDFS-Namespacev2)(cn="+namespace+"))"])
// // Look for "msDFS-TargetListv2"
// if(tempResult.count <= 0){
// return ""
// }else{
// if let result = tempResult[0]["msDFS-TargetListv2"]{
// return result
// }else{
// return ""
// }
// }
// }
//
// func decode(base64: String)->[String] {
// let result = base64.base64Decoded()
// if result == "" {
// return []
// }
// let data = result!.data(using: .utf16)
// let parser2 = XML(data: data!)
// if(parser2.children.count >= 0 && parser2.children[0].children.count >= 0){
// var response: [String] = []
// for child in parser2.children[0].children {
// if(child.attributes["state"] == "online"){
// let trim: String = child.text.replacingOccurrences(of: "\\\\", with: "")
// response.append("smb://"+trim.replacingOccurrences(of: "\\", with: "/"))
// }
// }
// return response
// }else{
// return []
// }
// }
//}
//
////Use XML class for XML document
////Parse using Foundation's XMLParser
//class XML:XMLNode {
// var parser:XMLParser
// init(data: Data) {
// self.parser = XMLParser(data: data)
// super.init()
// parser.delegate = self
// parser.parse()
// }
// init?(contentsOf url: URL) {
// guard let parser = XMLParser(contentsOf: url) else { return nil}
// self.parser = parser
// super.init()
// parser.delegate = self
// parser.parse()
// }
//}
////Each element of the XML hierarchy is represented by an XMLNode
////<name attribute="attribute_data">text<child></child></name>
//class XMLNode:NSObject {
// var name:String?
// var attributes:[String:String] = [:]
// var text = ""
// var children:[XMLNode] = []
// var parent:XMLNode?
//
// override init() {
//
// }
// init(name:String) {
// self.name = name
// }
// init(name:String,value:String) {
// self.name = name
// self.text = value
// }
// //MARK: Update data
// func indexIsValid(index: Int) -> Bool {
// return (index >= 0 && index < children.count)
// }
// subscript(index: Int) -> XMLNode {
// get {
// assert(indexIsValid(index: index), "Index out of range")
// return children[index]
// }
// set {
// assert(indexIsValid(index: index), "Index out of range")
// children[index] = newValue
// newValue.parent = self
// }
// }
// subscript(index: String) -> XMLNode? {
// //if more than one exists, assume the first
// get {
// return children.filter({ $0.name == index }).first
// }
// set {
// guard let newNode = newValue,
// let filteredChild = children.filter({ $0.name == index }).first
// else {return}
// filteredChild.attributes = newNode.attributes
// filteredChild.text = newNode.text
// filteredChild.children = newNode.children
// }
// }
// func addChild(_ node:XMLNode) {
// children.append(node)
// node.parent = self
// }
// func addChild(name:String,value:String) {
// addChild(XMLNode(name: name, value: value))
// }
// func removeChild(at index:Int) {
// children.remove(at: index)
// }
// //MARK: Description properties
// override var description:String {
// if let name = name {
// return "<\(name)\(attributesDescription)>\(text)\(childrenDescription)</\(name)>"
// } else if let first = children.first {
// return "<?xml version=\"1.0\" encoding=\"utf-8\"?>\(first.description)"
// } else {
// return ""
// }
// }
// var attributesDescription:String {
// return attributes.map({" \($0)=\"\($1)\" "}).joined()
// }
// var childrenDescription:String {
// return children.map({ $0.description }).joined()
// }
//}
//extension XMLNode:XMLParserDelegate {
// public func parser(_ parser: XMLParser, foundCharacters string: String) {
// text += string.trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines)
// }
// func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
// let childNode = XMLNode()
// childNode.name = elementName
// childNode.parent = self
// childNode.attributes = attributeDict
// parser.delegate = childNode
//
// children.append(childNode)
// }
// func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
// if let parent = parent {
// parser.delegate = parent
// }
// }
//}
......@@ -9,7 +9,7 @@
import Foundation
extension NSWindow {
func forceToFrontAndFocus(_ sender: AnyObject?) {
@objc func forceToFrontAndFocus(_ sender: AnyObject?) {
NSApp.activate(ignoringOtherApps: true)
self.makeKeyAndOrderFront(sender);
}
......@@ -66,7 +66,7 @@ extension String {
return addingPercentEncoding(withAllowedCharacters: allowedCharacters)
}
func variableSwap() -> String {
func variableSwap(_ encoding: Bool=true) -> String {
var cleanString = self
......@@ -77,7 +77,9 @@ extension String {
let upn = defaults.string(forKey: Preferences.userUPN) ?? ""
let email = defaults.string(forKey: Preferences.userEmail) ?? ""
cleanString = cleanString.replacingOccurrences(of: " ", with: "%20") //cleanString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlHostAllowed) ?? cleanString
if encoding {
cleanString = cleanString.replacingOccurrences(of: " ", with: "%20") //cleanString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlHostAllowed) ?? cleanString
}
cleanString = cleanString.replacingOccurrences(of: "<<domain>>", with: domain)
cleanString = cleanString.replacingOccurrences(of: "<<fullname>>", with: fullName)
......@@ -89,5 +91,44 @@ extension String {
return cleanString //.addingPercentEncoding(withAllowedCharacters: .alphanumerics)
}
}
extension Data {
init?(fromHexEncodedString string: String) {
// Convert 0 ... 9, a ... f, A ...F to their decimal value,
// return nil for all other input characters
func decodeNibble(u: UInt16) -> UInt8? {
switch(u) {
case 0x30 ... 0x39:
return UInt8(u - 0x30)
case 0x41 ... 0x46:
return UInt8(u - 0x41 + 10)
case 0x61 ... 0x66:
return UInt8(u - 0x61 + 10)
default:
return nil
}
}
self.init(capacity: string.utf16.count/2)
var even = true
var byte: UInt8 = 0
for c in string.utf16 {
guard let val = decodeNibble(u: c) else { return nil }
if even {
byte = val << 4
} else {
byte += val
self.append(byte)
}
even = !even
}
guard even else { return nil }
}
func hexEncodedString() -> String {
return map { String(format: "%02hhx", $0) }.joined()
}
}
......@@ -19,14 +19,14 @@ class GetHelp {
if let getHelpType = defaults.string(forKey: Preferences.getHelpType),
let getHelpOptions = defaults.string(forKey: Preferences.getHelpOptions) {
if getHelpType.characters.count > 0 && getHelpOptions.characters.count > 0 {
if getHelpType.count > 0 && getHelpOptions.count > 0 {
switch getHelpType {
case "Bomgar":
if let myURL = subVariables(getHelpOptions) {
OperationQueue.main.addOperation() {
cliTask("curl -o /tmp/BomgarClient " + myURL )
cliTaskNoTerm("/usr/bin/unzip -o -d /tmp /tmp/BomgarClient")
cliTask("/usr/bin/open /tmp/Bomgar/Double-Click\\ To\\ Start\\ Support\\ Session.app")
let _ = cliTask("curl -o /tmp/BomgarClient " + myURL )
let _ = cliTaskNoTerm("/usr/bin/unzip -o -d /tmp /tmp/BomgarClient")
let _ = cliTask("/usr/bin/open /tmp/Bomgar/Double-Click\\ To\\ Start\\ Support\\ Session.app")
}
}
case "URL":
......@@ -34,11 +34,11 @@ class GetHelp {
myLogger.logit(.base, message: "Could not create help URL.")
break
}
NSWorkspace.shared().open(url)
NSWorkspace.shared.open(url)
case "Path":
cliTask(getHelpOptions.replacingOccurrences(of: " ", with: "\\ "))
let _ = cliTask(getHelpOptions.replacingOccurrences(of: " ", with: "\\ "))
case "App":
NSWorkspace.shared().launchApplication(getHelpOptions)
NSWorkspace.shared.launchApplication(getHelpOptions)
default:
myLogger.logit(.info, message: "Invalid getHelpType or getHelpOptions, defaulting to www.apple.com/support")
openDefaultHelpURL()
......@@ -55,7 +55,7 @@ class GetHelp {
myLogger.logit(.base, message: "Could not create default help URL.")
return
}
NSWorkspace.shared().open(url)
NSWorkspace.shared.open(url)
}
fileprivate func subVariables(_ url: String) -> String? {
......
......@@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.1.2</string>
<string>1.1.3</string>
<key>CFBundleShortVersionString-orginal</key>
<string>1.1.2</string>
<string>1.1.3.877</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
......@@ -38,7 +38,7 @@
<true/>
</dict>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2016 Orchard &amp; Grove. All rights reserved.</string>
<string>Copyright © 2018 Orchard &amp; Grove. All rights reserved.</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
......
......@@ -30,8 +30,8 @@ class KeychainMinder : NSWindowController, NSWindowDelegate {
// overrides
override var windowNibName: String? {
return "KeychainMinder"
@objc override var windowNibName: NSNib.Name {
return NSNib.Name(rawValue: "KeychainMinder")
}
override func windowDidLoad() {
......@@ -66,7 +66,7 @@ class KeychainMinder : NSWindowController, NSWindowDelegate {
// functions
func clearWindow() {
@objc func clearWindow() {
// clear out the fields
oldPassword.stringValue = ""
......@@ -75,7 +75,7 @@ class KeychainMinder : NSWindowController, NSWindowDelegate {
activitySpinner.isHidden = true
}
func startOperations() {
@objc func startOperations() {
activitySpinner.startAnimation(nil)
activitySpinner.isHidden = false
changeButton.isEnabled = false
......@@ -83,14 +83,14 @@ class KeychainMinder : NSWindowController, NSWindowDelegate {
oldPassword.isEnabled = false
}
func stopOperations() {
@objc func stopOperations() {
activitySpinner.stopAnimation(nil)
activitySpinner.isHidden = true
newPassword.isEnabled = true
oldPassword.isEnabled = true
}
func changePassword() -> String {
@objc func changePassword() -> String {
let new = newPassword.stringValue
let old = oldPassword.stringValue
......@@ -120,7 +120,7 @@ class KeychainMinder : NSWindowController, NSWindowDelegate {
return ""
}
func resetLocalKeychain() -> String {
@objc func resetLocalKeychain() -> String {
let new = newPassword.stringValue
......@@ -148,7 +148,7 @@ class KeychainMinder : NSWindowController, NSWindowDelegate {
return ""
}
func showAlert(message: String) {
@objc func showAlert(message: String) {
let myAlert = NSAlert()
myAlert.messageText = message
......
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12121" systemVersion="16F2104" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13529" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12121"/>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13529"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
......@@ -21,14 +22,14 @@
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="429" height="214"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1440"/>
<value key="minSize" type="size" width="429" height="214"/>
<value key="maxSize" type="size" width="429" height="214"/>