Skip to content

Commit

Permalink
Merge pull request #4 from smudge/set-schedule
Browse files Browse the repository at this point in the history
Add ability to set the schedule!
  • Loading branch information
smudge authored Apr 27, 2020
2 parents f132638 + f22131c commit 2798cfa
Show file tree
Hide file tree
Showing 8 changed files with 544 additions and 57 deletions.
333 changes: 332 additions & 1 deletion Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "nightshift"
version = "0.0.3"
version = "0.0.4"
authors = ["smudge <[email protected]>"]
edition = "2018"
categories = ["command-line-utilities", "os::macos-apis"]
Expand All @@ -11,3 +11,4 @@ license = "MIT"

[dependencies]
objc = "0.2"
time = "0.2.10"
59 changes: 48 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ This crate also doubles as a Rust library. 🦀

### Why?

The "Night Shift" feature on macOS is a convenient, first party alternative
to the more feature-rich [f.lux®](https://justgetflux.com/). However, as
The "Night Shift" feature on macOS is a convenient, built-in feature
that can theoretically accomplish most of what third-party alternatives
(like [f.lux®](https://justgetflux.com/)) are capable of. However, as
of now, there is no way to programmatically configure Night Shift (without
entering the system preferences GUI).
entering the system preferences GUI), making its current usage more limited.

This `nightshift` CLI aims to enable such access via a few simple commands.
(Or, alternatively, via library access for other Rust tools.)
Expand Down Expand Up @@ -53,37 +54,73 @@ Set color temperature (a number from 0 to 100):
nightshift temp 70
```

Schedule from sunset to sunrise:

```
nightshift schedule
```

Set a custom schedule (in 12 or 24-hour time format):

```
nightshift schedule 19:45 6:00
nightshift schedule 7:45pm 6am
```

Disable the current schedule:

```
nightshift unschedule
```

View current schedule, on/off state, and color temperature preference:

```
nightshift status
```

### Rust API

In addition to a CLI, `nightshift` can be pulled-in as a dependency for other Rust crates:

```
nightshift = "0.0.3"
nightshift = "0.0.4"
```

Here's an example `fn` that toggles Night Shift off,
changes the color temperature preference, and then
toggles the feature back on:
changes the schedule and color temperature preference,
and then toggles the feature back on:

```rust
extern crate nightshift;

use nightshift::NightShift;
use nightshift::{NightShift, Schedule};

fn main() {
let night_shift = NightShift::new();
night_shift.off().unwrap();

if night_shift.status().unwrap().currently_active {
println!("Turning Night Shift off...");
night_shift.off().unwrap();
}

println!("Setting schedule and temperature...");
night_shift.set_schedule(Schedule::SunsetToSunrise).unwrap();
night_shift.set_temp(70).unwrap();

println!("Turning Night Shift on...");
night_shift.on().unwrap();
}
```

## Todo:

- [ ] Ability to see current status of Night Shift
- [ ] Ability to enable/disable sunrise/sundown schedule
- [ ] Ability to enable/disable custom schedules
- [X] Ability to see current status of Night Shift
- [X] Ability to enable/disable sunrise/sundown schedule
- [X] Ability to enable/disable custom schedules
- [ ] Ensure that changing schedule doesn't affect on/off state.
- [ ] API improvements and full documentation
- [ ] Test coverage of schedule/time parsing.
- [ ] Tests that use fake/stub ObjC library.
- [ ] Cross-platform support (e.g. Windows' "Night Light")

Expand Down
22 changes: 22 additions & 0 deletions src/ffi/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::ffi::BlueLightStatus;
use objc::rc::StrongPtr;
use objc::runtime::{Object, BOOL, YES};
use objc::{class, msg_send, sel, sel_impl};
use std::os::raw::c_int;

pub struct CBBlueLightClient {
inner: StrongPtr,
Expand All @@ -27,6 +28,27 @@ impl CBBlueLightClient {
}
}

pub fn set_mode(&self, mode: u8) -> Result<(), String> {
let result: BOOL = unsafe { msg_send![*self.inner, setMode: mode as c_int] };

if result == YES {
Ok(())
} else {
Err("Failed to set schedule".to_string())
}
}

pub fn set_schedule(&self, from: (u8, u8), to: (u8, u8)) -> Result<(), String> {
let ptr = BlueLightStatus::sched_ptr(from, to);
let result: BOOL = unsafe { msg_send![*self.inner, setSchedule: &ptr] };

if result == YES {
Ok(())
} else {
Err("Failed to set schedule".to_string())
}
}

pub fn set_strength(&self, strength: f32) -> Result<(), String> {
let result: BOOL = unsafe { msg_send![*self.inner, setStrength:strength commit:YES] };

Expand Down
33 changes: 23 additions & 10 deletions src/ffi/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ mod padding;

#[derive(Default)]
#[repr(C)]
struct Time {
pub struct Time {
hour: c_int,
minute: c_int,
}

#[derive(Default)]
#[repr(C)]
struct Schedule {
pub struct Schedule {
from_time: Time,
to_time: Time,
}
Expand Down Expand Up @@ -40,6 +40,19 @@ impl BlueLightStatus {
InnerStatus::default()
}

pub fn sched_ptr(from: (u8, u8), to: (u8, u8)) -> Schedule {
Schedule {
from_time: Time {
hour: from.0 as i32,
minute: from.1 as i32,
},
to_time: Time {
hour: to.0 as i32,
minute: to.1 as i32,
},
}
}

pub fn new(inner: InnerStatus) -> BlueLightStatus {
if !inner.padding.is_empty() {
eprintln!(
Expand All @@ -59,17 +72,17 @@ impl BlueLightStatus {
self.inner.mode as i32
}

pub fn from_time(&self) -> String {
format!(
"{}:{}",
self.inner.schedule.from_time.hour, self.inner.schedule.from_time.minute
pub fn from_time(&self) -> (u8, u8) {
(
self.inner.schedule.from_time.hour as u8,
self.inner.schedule.from_time.minute as u8,
)
}

pub fn to_time(&self) -> String {
format!(
"{}:{}",
self.inner.schedule.to_time.hour, self.inner.schedule.to_time.minute
pub fn to_time(&self) -> (u8, u8) {
(
self.inner.schedule.to_time.hour as u8,
self.inner.schedule.to_time.minute as u8,
)
}
}
46 changes: 21 additions & 25 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,18 @@
extern crate objc;

mod ffi;
mod schedule;

use std::fmt;
pub use schedule::{Schedule, Time};

pub struct NightShift {
client: ffi::CBBlueLightClient,
}

pub struct Status {
pub currently_active: bool,
pub schedule_type: Schedule,
pub schedule: Schedule,
pub color_temperature: i32,
pub from_time: String,
pub to_time: String,
}

pub enum Schedule {
Off = 0,
Custom = 2,
SunsetToSunrise = 1,
}

impl fmt::Display for Schedule {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Schedule::Off => write!(f, "off"),
Schedule::Custom => write!(f, "custom"),
Schedule::SunsetToSunrise => write!(f, "sunset to sunrise"),
}
}
}

impl NightShift {
Expand All @@ -51,6 +34,17 @@ impl NightShift {
self.client.set_enabled(on)
}

pub fn set_schedule(&self, schedule: Schedule) -> Result<(), String> {
match schedule {
Schedule::Off => self.client.set_mode(0),
Schedule::SunsetToSunrise => self.client.set_mode(1),
Schedule::Custom(from, to) => {
self.client.set_mode(2)?;
self.client.set_schedule(from.tuple(), to.tuple())
}
}
}

pub fn set_temp(&self, temp: i32) -> Result<(), String> {
if temp < 0 || temp > 100 {
return Err("Color temperature must be a number from 0 to 100.".to_string());
Expand All @@ -61,19 +55,21 @@ impl NightShift {

pub fn status(&self) -> Result<Status, String> {
let status = self.client.status()?;
let schedule = NightShift::schedule(status.mode(), status.from_time(), status.to_time())?;
Ok(Status {
currently_active: status.enabled(),
schedule_type: NightShift::schedule_type(status.mode())?,
schedule,
color_temperature: self.client.get_strength()?,
from_time: status.from_time(),
to_time: status.to_time(),
})
}

pub fn schedule_type(mode: i32) -> Result<Schedule, String> {
fn schedule(mode: i32, from: (u8, u8), to: (u8, u8)) -> Result<Schedule, String> {
let from = Time::from_tuple(from);
let to = Time::from_tuple(to);

match mode {
0 => Ok(Schedule::Off),
2 => Ok(Schedule::Custom),
2 => Ok(Schedule::Custom(from, to)),
1 => Ok(Schedule::SunsetToSunrise),
_ => Err("Unrecognized schedule type".to_string()),
}
Expand Down
42 changes: 33 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use nightshift::{NightShift, Schedule, Status};
use nightshift::{NightShift, Schedule, Status, Time};
use std::env::args;
use std::process::exit;

Expand All @@ -11,19 +11,26 @@ fn print_usage(program: &String) {
println!("{}\n", env!("CARGO_PKG_DESCRIPTION"));
println!("Usage:\n {} [command]\n", program);
println!("Available Commands:");
println!(" on Turn Night Shift on (until tomorrow/sunrise)");
println!(" off Turn Night Shift off");
println!(" temp [0-100] Set color temperature preference (does not affect on/off)");
println!(" status View current status and configuration");
println!(" on Turn Night Shift on (until sunrise/scheduled stop)");
println!(" off Turn Night Shift off (until sunset/scheduled start)");
println!(" schedule Start schedule from sunset to sunrise");
println!(" schedule [from] [to] Start a custom schedule (12 or 24-hour time format)");
println!(" unschedule Stop schedule");
println!(" temp [0-100] Set color temperature preference (does not affect on/off)");
println!(" status View current status and configuration");
}

fn print_status(status: Status) {
println!("Schedule:\n=> {}", status.schedule_type);
let off_at = match status.schedule_type {
println!("Schedule:\n=> {}", status.schedule);
let off_at = match status.schedule {
Schedule::SunsetToSunrise => "Sunrise",
Schedule::Off => "Tomorrow",
Schedule::Custom => {
println!("From:\n=> {} to {}", status.from_time, status.to_time);
Schedule::Custom(from_time, to_time) => {
println!(
"From:\n=> {} to {}",
from_time.to_string(),
to_time.to_string()
);
"Tomorrow"
}
};
Expand All @@ -44,6 +51,16 @@ fn main() {
night_shift.on().unwrap_or_else(|e| error(e));
} else if args.len() == 2 && args[1] == "off" {
night_shift.off().unwrap_or_else(|e| error(e));
} else if args.len() == 2 && args[1] == "schedule" {
night_shift
.set_schedule(Schedule::SunsetToSunrise)
.unwrap_or_else(|e| error(e));
} else if args.len() == 4 && args[1] == "schedule" {
schedule(night_shift, &args[2], &args[3]).unwrap_or_else(|e| error(e));
} else if args.len() == 2 && args[1] == "unschedule" {
night_shift
.set_schedule(Schedule::Off)
.unwrap_or_else(|e| error(e));
} else if args.len() == 2 && args[1] == "status" {
match night_shift.status() {
Ok(status) => print_status(status),
Expand All @@ -57,6 +74,13 @@ fn main() {
}
}

fn schedule(night_shift: NightShift, from: &String, to: &String) -> Result<(), String> {
let from = Time::parse(from)?;
let to = Time::parse(to)?;

night_shift.set_schedule(Schedule::Custom(from, to))
}

fn error(text: String) {
eprintln!("{}", text);
exit(1)
Expand Down
Loading

0 comments on commit 2798cfa

Please sign in to comment.