Check Mac battery percentage in swift - macos

I have been trying to check the mac battery level programmatically.it can be done on ios but i want to do it in mac.i found some resources on stackoverflow but those links were deprecated. Any Ideas?

First create a "Umbrella-Bridging-Header.h"
with the content:
#import <IOKit/ps/IOPowerSources.h>
then in main.swift
import Foundation
println("Hello, World!")
let timeRemaining = IOPSGetTimeRemainingEstimate ()
println("timeRemaining: \(timeRemaining)")

If you don't want to add Objective-C bridging and you need just to know a couple of values. Then you could use this function.
func getBatteryState() -> [String?]
{
let task = Process()
let pipe = Pipe()
task.launchPath = "/usr/bin/pmset"
task.arguments = ["-g", "batt"]
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = NSString(data: data, encoding: String.Encoding.utf8.rawValue) as! String
let batteryArray = output.components(separatedBy: ";")
let source = output.components(separatedBy: "'")[1]
let state = batteryArray[1].trimmingCharacters(in: NSCharacterSet.whitespaces).capitalized
let percent = String.init(batteryArray[0].components(separatedBy: ")")[1].trimmingCharacters(in: NSCharacterSet.whitespaces).characters.dropLast())
var remaining = String.init(batteryArray[2].characters.dropFirst().split(separator: " ")[0])
if(remaining == "(no"){
remaining = "Calculating"
}
return [source, state, percent, remaining]
}
print(getBatteryState().flatMap{$0}) -> "AC Power", "Discharging", "94", "3:15"
pmset is a very old command line function which is very unlikely to change in the future. Of course this does not give extended properties of power options like mAh and so on, but it was enough for me, because I just needed to know is it charging or not and how much percent battery has currently.
Just my 2 cents. I understand if people will find this discouraging to use.
N.B. If charging - remaining will show how long until it's fully charged.
If discharging - it will show how long until it's discharged.

First, you can see the answer here on how to include Objective-C code in your swift project (very good post btw).
Then, check out the IOMPowerSource class. It should include everything you need to report the status of the computer's power information.

Swift 2 Version of the answer of #Just A Minnion
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var label: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
label.stringValue = String(getBatteryState().flatMap{$0})
}
func getBatteryState() -> [String?] {
let task = NSTask()
let pipe = NSPipe()
task.launchPath = "/usr/bin/pmset"
task.arguments = ["-g", "batt"]
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = NSString(data: data, encoding: NSUTF8StringEncoding) as! String
let batteryArray = output.componentsSeparatedByString(";")
let source = output.componentsSeparatedByString("'")[1]
let state = batteryArray[0].stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet()).capitalizedString
let percent = String.init(batteryArray[0].componentsSeparatedByString(")")[0].stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet()))
var remaining = String.init(batteryArray[0].characters.dropFirst().split(" ")[1])
if (remaining == "(no") {
remaining = "Calculating"
}
return [source, state, percent, remaining]
}
}

Related

Trying to read MacOS clipboard contents

On my adventure to learn Rust I decided to try and print to the cli contents of the clipboard. I've done this before in Swift so thought I would have much issues in Rust.
However I'm having a hard time printing the contents of the returned NSArray. I've spent a few hours playing around with different functions but haven't made much progress.
The Swift code I have that works:
import Foundation
import AppKit
let pasteboard = NSPasteboard.general
func reload() -> [String]{
var clipboardItems: [String] = []
for element in pasteboard.pasteboardItems! {
if let str = element.string(forType: NSPasteboard.PasteboardType(rawValue: "public.utf8-plain-text")) {
clipboardItems.append(str)
}
}
return clipboardItems;
}
// Access the item in the clipboard
while true {
let firstClipboardItem = reload()
print(firstClipboardItem);
sleep(1);
}
Here is the Rust code:
use cocoa::appkit::{NSApp, NSPasteboard, NSPasteboardReading, NSPasteboardTypeString};
use cocoa::foundation::NSArray;
fn main() {
unsafe {
let app = NSApp();
let pid = NSPasteboard::generalPasteboard(app);
let changec = pid.changeCount();
let pid_item = pid.pasteboardItems();
if pid_item.count() != 0 {
let items = &*pid_item.objectAtIndex(0);
println!("{:?}", items);
}
println!("{:?}", *pid.stringForType(NSPasteboardTypeString));
}
}
The code above produces: *<NSPasteboardItem: 0x6000021a3de0>*
EDIT:
I've made a little progress but stuck on one last bit. I've managed to get the first UTF8 char out of the clipboard.
The issue I have is if I copy the text: World the system will loop the correct amount of times for the word length but will only print the first letter, in this case W. Output below:
TEXT 'W'
TEXT 'W'
TEXT 'W'
TEXT 'W'
TEXT 'W'
The bit I'm trying to get my head around is how to move to the next i8. I can't seem to find a way to point to the next i8.
The NSString function UTF8String() returns *const i8. I'm scratching my head with how one would walk the text.
use cocoa::appkit::{NSApp, NSPasteboard, NSPasteboardTypeString};
use cocoa::foundation::{NSArray, NSString};
fn main() {
unsafe {
let app = NSApp();
let pid = NSPasteboard::generalPasteboard(app);
let changec = pid.changeCount();
let nsarray_ptr = pid.pasteboardItems();
if nsarray_ptr.count() != 0 {
for i in 0..NSArray::count(nsarray_ptr) {
let raw_item_ptr = NSArray::objectAtIndex(nsarray_ptr, i);
let itm = raw_item_ptr.stringForType(NSPasteboardTypeString);
for u in 0..itm.len() {
let stri = itm.UTF8String();
println!("TEXT {:?}", *stri as u8 as char);
}
}
}
}
}
To everyone who's looked/commented on this so far thank you.
After reading some tests provided by cocoa I figured out what I needed to do.
The code below prints the contents of the clipboard. Thanks to those who pointed me in the right direction.
use cocoa::appkit::{NSApp, NSPasteboard, NSPasteboardTypeString};
use cocoa::foundation::{NSArray, NSString};
use std::{str, slice};
fn main() {
unsafe {
let app = NSApp();
let pid = NSPasteboard::generalPasteboard(app);
let nsarray_ptr = pid.pasteboardItems();
if nsarray_ptr.count() != 0 {
for i in 0..NSArray::count(nsarray_ptr) {
let raw_item_ptr = NSArray::objectAtIndex(nsarray_ptr, i);
let itm = raw_item_ptr.stringForType(NSPasteboardTypeString);
let stri = itm.UTF8String() as *const u8;
let clipboard = str::from_utf8(slice::from_raw_parts(stri, itm.len()))
.unwrap();
println!("{}", clipboard);
}
}
}
}

How to redirect stdout to NSScrollView in Swift 4.0 Cocoa Application?

I have a simple NSButton function executing a 'ls -la' command:
#IBAction func Convert(_ sender: NSButton) {
let path = "/bin/ls"
let arguments = ["-la"]
sender.isEnabled = false
let task = Process.launchedProcess(launchPath: path, arguments: arguments)
task.waitUntilExit()
sender.isEnabled = true
}
I need the stdout in my NSScrollView:
#IBOutlet weak var Output: NSScrollView!
You need a Pipe and a FileHandle
let path = "/bin/ls"
let arguments = ["-la"]
// sender.isEnabled = false
let task = Process()
task.launchPath = path
task.arguments = arguments
let outputPipe = Pipe()
task.standardOutput = outputPipe
task.launch()
task.waitUntilExit()
let data = (task.standardOutput as! Pipe).fileHandleForReading.readDataToEndOfFile()
let output = String(data:data, encoding: .utf8)!
print(output)
// sender.isEnabled = true
And please conform to the naming convention that variable and function / method names start with a lowercase letter.

NSTask : Couldn't posix_spawn: error 13 when launching app

I have a sub-app in my main Swift app. I made it so it's copied automatically in the Resources folder of the main app when building it. That way, I want to be able to launch an instance of the sub-app from the main app.
The thing is, I'm having an error that is hard to debug/find answers about.
Here is my code :
let args = ["--args", "-admin_url", site.url, "-login", site.login, "-pass", site.password]
let helperPath = (NSBundle.mainBundle().pathForResource("App Helper", ofType: "app"))!
let task = NSTask.init()
task.launchPath = helperPath
task.arguments = args
task.launch()
And the error :
[56490:7218926] Couldn't posix_spawn: error 13
I have no idea where to look, what to search for. I don't know what I'm doing wrong.
I'm wondering if the issue is related to the sub-app itself. That sub-app is empty for now. I set Application is Agent to YES. And in MainMenu.xib, I set the Visible at launch option to no.
That sub-app needs to do some work in the background and doesn't need any UI at all.
Thanks !
Don't use NSTask for this, use NSWorkspace:
let helperAppURL = NSBundle.mainBundle().URLForResource("App Helper",
withExtension:"app")!
_ = try? NSWorkspace.sharedWorkspace().openURL(helperAppURL,
options:[.Default],
configuration:[NSWorkspaceLaunchConfigurationArguments :
["--args", "-admin_url", site.url, "-login",
site.login, "-pass", site.password]])
In the above code, for brevity, I ignored the result of the openURL() command, but in reality it can return an instance of NSRunningApplication which represents the task.
To keep track of the instances of your helper app you launch, you could keep references to this NSRunningApplication in an appropriate kind of collection class, and when the time comes, call its terminate() method.
the launch() function is deprecated, using run()
func shell(_ command: String) -> String {
let task = Process()
task.launchPath = "/usr/bin/"
task.arguments = ["-c", command]
let pipe = Pipe()
task.standardOutput = pipe
if #available(macOS 10.13, *) {
try? task.run()
} else {
task.launch()
}
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue)! as String
return output
}
or using swift-commands
import Commands
Commands.Bash.run("say hello")

Swift 2.1 OSx shell commands using NSTask work when run from xcode, but not when exported

I wrote a simple OSx (10.11) application to execute shell commands when a button is pressed. It works when I run it from xcode, but when I export the application via "archive", one of the buttons no longer works. I don't get an error and I don't get any output either. I am using absolute paths so I don't understand why it works in xcode but not as an exported application, Nor do I understand why one button works and the other doesn't.
Here is the main function that I am using the make the shell commands
func runCommand(path : String, args : [String]) -> (output: [String], error: [String], exitCode: Int32) {
var output : [String] = []
var error : [String] = []
let task = NSTask()
task.launchPath = path
task.arguments = args
let outpipe = NSPipe()
task.standardOutput = outpipe
let errpipe = NSPipe()
task.standardError = errpipe
task.launch()
let outdata = outpipe.fileHandleForReading.readDataToEndOfFile()
if var string = String.fromCString(UnsafePointer(outdata.bytes)) {
string = string.stringByTrimmingCharactersInSet(NSCharacterSet.newlineCharacterSet())
output = string.componentsSeparatedByString("\n")
}
let errdata = errpipe.fileHandleForReading.readDataToEndOfFile()
if var string = String.fromCString(UnsafePointer(errdata.bytes)) {
string = string.stringByTrimmingCharactersInSet(NSCharacterSet.newlineCharacterSet())
error = string.componentsSeparatedByString("\n")
}
//task.waitUntilExit()
let status = task.terminationStatus
return (output, error, status)
}
and here is the button that works:
// Check for configurator 2 app installation
let (output, error, status) = self.runCommand("/bin/bash", args: ["-c", "/bin/ls", "/Applications/Apple Configurator 2.app"])
and here is the button that doesn't:
// Check if the phone is plugged in and paired
let (output, error, status) = self.runCommand("/bin/bash", args: ["-c", "/usr/local/bin/cfgutil", "get", "isPaired"])
What is even more strange, I discovered (through sheer frustration) that if I repeatedly click the button that doesn't work, it will sometimes eventually work.
Your issue is the result of two things happening together:
you return default values
you don't specify alternative branches for the control flow
What happens is that it hides potential failures, and leads to code that is very hard to debug, as you experienced.
A possible solution with your existing code is to cover all possible ways, meaning providing else branches to your if var string = String.fromCString(UnsafePointer(errdata.bytes)) conditions, where you will handle errors.
Thanks to Eric D. I simplified my code and now everything is working.
func runCommand(path : String, args : [String]) -> (output: NSString, error: NSString, exitCode: Int32) {
let task = NSTask()
task.launchPath = path
task.arguments = args
let outpipe = NSPipe()
task.standardOutput = outpipe
let errpipe = NSPipe()
task.standardError = errpipe
task.launch()
let outdata = outpipe.fileHandleForReading.readDataToEndOfFile()
let output = NSString(data: outdata, encoding: NSUTF8StringEncoding)
let errdata = errpipe.fileHandleForReading.readDataToEndOfFile()
let error_output = NSString(data: errdata, encoding: NSUTF8StringEncoding)
task.waitUntilExit()
let status = task.terminationStatus
return (output!, error_output!, status)
}

Format Numbers in Textfields using Swift

I am trying to format a number from a UITextfield, as its being typed, to a decimal with commas.
I have done so with the following code:
#IBAction func editingDidBegin(sender : AnyObject)
{
costField.addTarget(self, action: Selector("textFieldDidChange:"), forControlEvents: UIControlEvents.EditingChanged)
}
func textFieldDidChange(theTextField:UITextField) -> Void
{
var textFieldText = theTextField.text.stringByReplacingOccurrencesOfString(",", withString: " ", options: NSStringCompareOptions.RegularExpressionSearch, range: Range(start: theTextField.text.startIndex, end: theTextField.text.endIndex))
var formatter:NSNumberFormatter = NSNumberFormatter()
formatter.numberStyle = NSNumberFormatterStyle.DecimalStyle
var formattedOutput = formatter.stringFromNumber(textFieldText.bridgeToObjectiveC().integerValue)
costField.text = formattedOutput
}
The problem with this, is after four digits are entered, everything after the comma is deleted. For example if I enter 4000 it formats to 4,000, then if I type another number like 8 it reformats to 48.
Is there another way I can format this, maybe through IB or how can I fix the code?
Replace the line with:
var textFieldText = theTextField.text.stringByReplacingOccurrencesOfString(",", withString: "", options: NSStringCompareOptions.RegularExpressionSearch, range: Range(start: theTextField.text.startIndex, end: theTextField.text.endIndex))
(I only removed the space between the double quotes).
Fact is, NSNumberFormatter doesn't like the added spaces in the string.
Works fine afterwards.
I know I am late to the party but this worked well for me.
var phoneNumber = " 1 (888) 555-5551 "
var strippedPhoneNumber = "".join(phoneNumber.componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet))
It takes out the spaces and strips out the non decimal numeric characters.
The end result is "1888555551"
I've updated this answer to the newest version of swift. This borrows 90% from the two answers above however, also accounts for nil exception from the textfield when the textfield is cleared.
func textFieldDidChangeCommas(theTextField:UITextField) -> Void
{
if theTextField.text != nil {
var textFieldText = theTextField.text!.stringByReplacingOccurrencesOfString(",", withString: "", options: NSStringCompareOptions.RegularExpressionSearch, range: Range(start: theTextField.text!.startIndex, end: theTextField.text!.endIndex))
var formatter:NSNumberFormatter = NSNumberFormatter()
formatter.numberStyle = NSNumberFormatterStyle.DecimalStyle
if textFieldText != "" {
var formattedOutput = formatter.stringFromNumber(Int(textFieldText)!)
costField.text = formattedOutput
}
}
}

Resources