Skip to content

Commit

Permalink
add night shift (#12)
Browse files Browse the repository at this point in the history
* add night shift

* tidy

* add args for night-shift

* update CI

* fix macos build

* bump to macos-13

* debug

* fix header

* disable night-shift in CI
  • Loading branch information
jiacai2050 committed Jul 4, 2023
1 parent eccf284 commit 5771935
Show file tree
Hide file tree
Showing 5 changed files with 313 additions and 11 deletions.
41 changes: 36 additions & 5 deletions .github/workflows/binary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,15 @@ on:
workflow_dispatch:
pull_request:
paths:
- '.github/workflows/binary.yml'
- '**.zig'
- '.github/workflows/CI.yml'
push:
branches:
- main
paths:
- '**.zig'
- '.github/workflows/binary.yml'

env:
BUILD_DIR: "build"

jobs:
build:
timeout-minutes: 10
Expand All @@ -31,6 +29,37 @@ jobs:
- "x86_64-linux"
- "arm-linux"
- "aarch64-linux"
steps:
- uses: actions/checkout@v3
with:
submodules: true
- uses: goto-bus-stop/setup-zig@v2
with:
version: ${{ matrix.zig-version }}
- name: Set Environment Variables
run: |
echo "BUILD_DATE=$(date +'%Y-%m-%dT%H:%M:%S%z')" >> $GITHUB_ENV
- name: Build
run: |
zig build -Dtarget=${{ matrix.targets }} -Doptimize=ReleaseSafe \
-Dgit_commit=${{ github.head_ref }}-${{ github.sha }} \
-Dbuild_date=${{ env.BUILD_DATE }}
# https://github.com/actions/upload-artifact#maintaining-file-permissions-and-case-sensitive-files
tar -cvf zigcli.tar zig-out/bin/
- name: Upload
uses: actions/upload-artifact@v3
with:
name: zigcli-${{ matrix.targets }}
path: zigcli.tar

build-macos:
timeout-minutes: 10
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
zig-version: [master]
targets:
- "x86_64-macos"
- "aarch64-macos"
steps:
Expand All @@ -43,11 +72,13 @@ jobs:
- name: Set Environment Variables
run: |
echo "BUILD_DATE=$(date +'%Y-%m-%dT%H:%M:%S%z')" >> $GITHUB_ENV
# xcrun --show-sdk-path
# find $(xcrun --show-sdk-path) -name objc.h
- name: Build
run: |
zig build -Dtarget=${{ matrix.targets }} -Doptimize=ReleaseSafe \
-Dgit_commit=${{ github.head_ref }}-${{ github.sha }} \
-Dbuild_date=${{ env.BUILD_DATE }}
-Dbuild_date=${{ env.BUILD_DATE }} -Dis_ci=true
# https://github.com/actions/upload-artifact#maintaining-file-permissions-and-case-sensitive-files
tar -cvf zigcli.tar zig-out/bin/
- name: Upload
Expand Down
27 changes: 26 additions & 1 deletion README.org
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#+TITLE: Zigcli
#+DATE: 2022-09-20T22:55:17+0800
#+LASTMOD: 2023-06-29T20:22:28+0800
#+LASTMOD: 2023-07-04T22:26:07+0800
#+AUTHOR: Jiacai Liu
#+EMAIL: dev@liujiacai.net
#+OPTIONS: toc:nil num:nil
Expand All @@ -14,6 +14,7 @@ Command line programs written in Zig. Currently there are:
- =tree=, list contents of directories in a tree-like format.
- =yes=, output a string repeatedly until killed.
- =pidof=, like [[https://man7.org/linux/man-pages/man1/pidof.1.html][pidof]], but for macOS.
- =night-shift=, control [[https://support.apple.com/guide/mac-help/use-night-shift-mchl97bc676d/mac][Night Shift]] in cli, build for macOS.

Prebuilt binaries can be found in [[https://github.com/jiacai2050/loc/actions/workflows/binary.yml][CI's artifacts]], or you can build from source:
#+begin_src bash
Expand Down Expand Up @@ -63,6 +64,30 @@ Ruby 1 8 5 2 1 201.00B
-------- ---- ---- ---- ------- ----- -------
Total 17 1260 1072 42 146 34.46K
#+end_src
* Night shift
#+begin_src bash :results verbatim code :exports both
./zig-out/bin/night-shift -h
#+end_src

#+RESULTS:
#+begin_src bash
USAGE:
./zig-out/bin/night-shift [OPTIONS] [--] <command>

Available commands by category:
Manual on/off control:
on Turn Night Shift on
off Turn Night Shift off
toggle Toggle Night Shift

Color temperature:
temp View temperature preference
temp <0-100> Set temperature preference

OPTIONS:
-v, --version Print version
-h, --help Print help information
#+end_src

* Roadmap
** Loc
Expand Down
34 changes: 30 additions & 4 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,17 @@ pub fn build(b: *Build) void {
b.modules.put("build_info", opt.createModule()) catch @panic("OOM");
b.modules.put("simargs", simargs_dep.module("simargs")) catch @panic("OOM");
b.modules.put("table-helper", table_dep.module("table-helper")) catch @panic("OOM");
const is_ci = b.option(bool, "is_ci", "Build in CI") orelse false;

var all_tests = std.ArrayList(*Build.Step).init(b.allocator);
inline for (.{
"tree",
"loc",
"pidof",
"yes",
"night-shift",
}) |prog_name| {
if (buildCli(b, prog_name, optimize, target)) |exe| {
if (buildCli(b, prog_name, optimize, target, is_ci)) |exe| {
var deps = b.modules.iterator();
while (deps.next()) |dep| {
exe.addModule(dep.key_ptr.*, dep.value_ptr.*);
Expand Down Expand Up @@ -67,17 +69,41 @@ fn buildTestStep(b: *std.Build, comptime name: []const u8, target: std.zig.Cross
return test_step;
}

fn buildCli(b: *std.Build, comptime name: []const u8, optimize: std.builtin.Mode, target: std.zig.CrossTarget) ?*Build.CompileStep {
if (std.mem.eql(u8, name, "pidof")) {
fn buildCli(
b: *std.Build,
comptime name: []const u8,
optimize: std.builtin.Mode,
target: std.zig.CrossTarget,
is_ci: bool,
) ?*Build.CompileStep {
if (std.mem.eql(u8, name, "night-shift") or std.mem.eql(u8, name, "pidof")) {
if (target.getOsTag() != .macos) {
return null;
}
}

return b.addExecutable(.{
if (is_ci and std.mem.eql(u8, name, "night-shift")) {
// zig build -Dtarget=aarch64-macos will throw error
// error: warning(link): library not found for '-lobjc'
// warning(link): Library search paths:
// warning(link): framework not found for '-framework CoreBrightness'
// warning(link): Framework search paths:
// warning(link): /System/Library/PrivateFrameworks
// so disable this in CI environment.
return null;
}

const exe = b.addExecutable(.{
.name = name,
.root_source_file = FileSource.relative("src/" ++ name ++ ".zig"),
.target = target,
.optimize = optimize,
});

if (std.mem.eql(u8, name, "night-shift")) {
exe.linkSystemLibrary("objc");
exe.addFrameworkPath("/System/Library/PrivateFrameworks");
exe.linkFramework("CoreBrightness");
}
return exe;
}
212 changes: 212 additions & 0 deletions src/night-shift.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
//! Control Night shift in cli, build for macOS.
//!
const std = @import("std");
const simargs = @import("simargs");
const util = @import("util.zig");
const c = @cImport({
@cInclude("objc/objc.h");
@cInclude("objc/message.h");
});

const Time = extern struct {
hour: c_int,
minute: c_int,
};
const Schedule = extern struct {
from_time: Time,
to_time: Time,
};

// Refer https://github.com/smudge/nightlight/blob/03595a642f0876388db11b9f5a3bd8261ab178d5/src/macos/status.rs#L21
const Status = extern struct {
active: bool,
enabled: bool,
sun_schedule_permitted: bool,
mode: c_int,
schedule: Schedule,
disable_flags: c_ulonglong,
available: bool,
};

const Client = struct {
inner: c.id,
allocator: std.mem.Allocator,

const Self = @This();

fn init(allocator: std.mem.Allocator) Self {
// https://developer.limneos.net/?ios=14.4&framework=CoreBrightness.framework&header=CBBlueLightClient.h
const clazz = c.objc_getClass("CBBlueLightClient");
const call: *fn (c.id, c.SEL) callconv(.C) c.id = @constCast(@ptrCast(&c.objc_msgSend));

return Self{
.inner = call(
call(@alignCast(@ptrCast(clazz.?)), c.sel_registerName("alloc")),
c.sel_registerName("init"),
),
.allocator = allocator,
};
}

fn getStatus(self: Self) !*Status {
var status = try self.allocator.create(Status);
const call: *fn (c.id, c.SEL, *Status) callconv(.C) bool =
@constCast(@ptrCast(&c.objc_msgSend));
const ret = call(self.inner, c.sel_registerName("getBlueLightStatus:"), status);
if (!ret) {
return error.getBlueLightStatus;
}

return status;
}

fn setEnabled(self: Self, enabled: bool) !void {
const call: *fn (c.id, c.SEL, bool) callconv(.C) bool = @constCast(@ptrCast(&c.objc_msgSend));
const ret = call(self.inner, c.sel_registerName("setEnabled:"), enabled);
if (!ret) {
return error.getStrength;
}
}

fn turnOn(self: Self) !void {
return self.setEnabled(true);
}

fn turnOff(self: Self) !void {
return self.setEnabled(false);
}

fn getStrength(self: Self) !f32 {
var strength: f32 = 0;
const call: *fn (c.id, c.SEL, *f32) callconv(.C) bool = @constCast(@ptrCast(&c.objc_msgSend));
const ret = call(self.inner, c.sel_registerName("getStrength:"), &strength);
if (!ret) {
return error.getStrength;
}

return strength;
}

fn setStrength(self: Self, strength: f32) !void {
const call: *fn (c.id, c.SEL, f32, bool) callconv(.C) bool = @constCast(@ptrCast(&c.objc_msgSend));
const ret = call(self.inner, c.sel_registerName("setStrength:commit:"), strength, true);
if (!ret) {
return error.setStrength;
}
}

fn destroyStatus(self: Self, status: *Status) void {
self.allocator.destroy(status);
}
};

const Action = enum {
Status,
On,
Off,
Toggle,
Temp,

const FromString = std.ComptimeStringMap(@This(), .{
.{ "status", .Status },
.{ "on", .On },
.{ "off", .Off },
.{ "toggle", .Toggle },
.{ "temp", .Temp },
});
};

pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();

const opt = try simargs.parse(allocator, struct {
version: bool = false,
help: bool = false,

pub const __shorts__ = .{
.version = .v,
.help = .h,
};

pub const __messages__ = .{
.help = "Print help information",
.version = "Print version",
};
},
\\<command>
\\
\\ Available commands by category:
\\ Manual on/off control:
\\ on Turn Night Shift on
\\ off Turn Night Shift off
\\ toggle Toggle Night Shift
\\
\\ Color temperature:
\\ temp View temperature preference
\\ temp <0-100> Set temperature preference
, util.get_build_info());
defer opt.deinit();

const action: Action = if (opt.positional_args.items.len == 0)
.Status
else
Action.FromString.get(opt.positional_args.items[0]) orelse .Status;

const client = Client.init(allocator);
var wtr = std.io.getStdOut().writer();

switch (action) {
.Status => {
var status = try client.getStatus();
defer client.destroyStatus(status);

if (!status.enabled) {
try wtr.writeAll("enabled: off");
return;
}

const schedule = switch (status.mode) {
0 => "Off",
1 => "SunsetToSunrise",
2 => try std.fmt.allocPrint(allocator, "Custom({d}:{d}-{d}:{d})", .{
status.schedule.from_time.hour,
status.schedule.from_time.minute,
status.schedule.to_time.hour,
status.schedule.to_time.minute,
}),
else => "Unknown",
};
try wtr.print(
\\Enabled: on
\\Schedule: {s}
\\Temperature: {d:.0}
, .{ schedule, try client.getStrength() * 100 });
},
.Temp => {
if (opt.positional_args.items.len == 2) {
const strength = try std.fmt.parseFloat(f32, opt.positional_args.items[1]);
try client.setStrength(strength / 100.0);
return;
}

const strength = try client.getStrength();
try wtr.print("{d:.0}\n", .{strength * 100});
},
.Toggle => {
var status = try client.getStatus();
if (status.enabled) {
try client.turnOff();
} else {
try client.turnOn();
}
},
.On => {
try client.turnOn();
},
.Off => {
try client.turnOff();
},
}
}
Loading

0 comments on commit 5771935

Please sign in to comment.