Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support schedule #13

Merged
merged 3 commits into from
Jul 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions 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-07-04T22:26:07+0800
#+LASTMOD: 2023-07-05T21:12:47+0800
#+AUTHOR: Jiacai Liu
#+EMAIL: dev@liujiacai.net
#+OPTIONS: toc:nil num:nil
Expand Down Expand Up @@ -76,13 +76,20 @@ Total 17 1260 1072 42 146 34.46K

Available commands by category:
Manual on/off control:
on Turn Night Shift on
off Turn Night Shift off
toggle Toggle Night Shift
status View current Night Shift status
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
temp View temperature preference
temp <0-100> Set temperature preference

Schedule:
schedule View current schedule
schedule sun Start schedule from sunset to sunrise
schedule off Stop the current schedule
schedule <from> <to> Start a custom schedule(HH:mm, 24-hour format)

OPTIONS:
-v, --version Print version
Expand Down
1 change: 1 addition & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ pub fn build(b: *Build) void {
}

const test_all_step = b.step("test", "Run all tests");
test_all_step.dependOn(buildTestStep(b, "util", target));
for (all_tests.items) |step| {
test_all_step.dependOn(step);
}
Expand Down
172 changes: 133 additions & 39 deletions src/night-shift.zig
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,75 @@ const c = @cImport({
const Time = extern struct {
hour: c_int,
minute: c_int,

fn fromString(hhmm: []const u8) !@This() {
var iter = std.mem.splitSequence(u8, hhmm, ":");
const hour = iter.next() orelse return error.MissingHour;
const minute = iter.next() orelse return error.MissingMinute;

return .{
.hour = std.fmt.parseInt(c_int, hour, 10) catch return error.InvalidHour,
.minute = std.fmt.parseInt(c_int, minute, 10) catch return error.InvalidMinute,
};
}
};
const Schedule = extern struct {

const CustomSchedule = extern struct {
from_time: Time,
to_time: Time,
};

const Schedule = union(enum) {
// false means schedule is off
SunSetToSunRise: bool,
Custom: CustomSchedule,

fn toMode(self: @This()) c_int {
return switch (self) {
.SunSetToSunRise => |v| if (v) 1 else 0,
.Custom => 2,
};
}
};

// 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,
custom_schedule: CustomSchedule,
disable_flags: c_ulonglong,
available: bool,

const Self = @This();

fn formatSchedule(self: Self, buf: []u8) ![]const u8 {
return switch (self.mode) {
0 => "Off",
1 => "SunsetToSunrise",
2 => try std.fmt.bufPrint(buf, "Custom({d}:{d}-{d}:{d})", .{
self.custom_schedule.from_time.hour,
self.custom_schedule.from_time.minute,
self.custom_schedule.to_time.hour,
self.custom_schedule.to_time.minute,
}),
else => "Unknown",
};
}

fn display(self: Self, wtr: anytype) !void {
if (!self.enabled) {
try wtr.writeAll("Enabled: off");
return;
}

var buf = std.mem.zeroes([32]u8);
try wtr.print(
\\Enabled: on
\\Schedule: {s}
, .{try self.formatSchedule(&buf)});
}
};

const Client = struct {
Expand Down Expand Up @@ -60,6 +114,29 @@ const Client = struct {
return status;
}

fn setSchedule(self: Self, schedule: Schedule) !void {
{
const call: *fn (c.id, c.SEL, c_int) callconv(.C) bool = @constCast(@ptrCast(&c.objc_msgSend));
const ret = call(self.inner, c.sel_registerName("setMode:"), schedule.toMode());
if (!ret) {
return error.setMode;
}
}

switch (schedule) {
.SunSetToSunRise => {},
.Custom => |custom| {
var ptr = try self.allocator.create(CustomSchedule);
ptr.* = custom;
const call: *fn (c.id, c.SEL, [*c]CustomSchedule) callconv(.C) bool = @constCast(@ptrCast(&c.objc_msgSend));
const ret = call(self.inner, c.sel_registerName("setSchedule:"), ptr);
if (!ret) {
return error.setSchedule;
}
},
}
}

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);
Expand Down Expand Up @@ -100,19 +177,21 @@ const Client = struct {
}
};

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

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

Expand All @@ -139,60 +218,52 @@ pub fn main() !void {
\\
\\ Available commands by category:
\\ Manual on/off control:
\\ on Turn Night Shift on
\\ off Turn Night Shift off
\\ toggle Toggle Night Shift
\\ status View current Night Shift status
\\ 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
\\ temp View temperature preference
\\ temp <0-100> Set temperature preference
\\
\\ Schedule:
\\ schedule View current schedule
\\ schedule sun Start schedule from sunset to sunrise
\\ schedule off Stop the current schedule
\\ schedule <from> <to> Start a custom schedule(HH:mm, 24-hour format)
, util.get_build_info());
defer opt.deinit();

const action: Action = if (opt.positional_args.items.len == 0)
.Status
var args_iter = util.SliceIter([]const u8).init(opt.positional_args.items);
const cmd: Command = if (args_iter.next()) |v|
Command.FromString.get(v) orelse return error.UnknownCommand
else
Action.FromString.get(opt.positional_args.items[0]) orelse .Status;
.Status;

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

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

if (!status.enabled) {
try wtr.writeAll("enabled: off");
return;
try status.display(wtr);
if (status.enabled) {
try wtr.print(
\\
\\Temperature: {d:.0}
, .{try client.getStrength() * 100});
}

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]);
if (args_iter.next()) |v| {
const strength = try std.fmt.parseFloat(f32, v);
try client.setStrength(strength / 100.0);
return;
} else {
const strength = try client.getStrength();
try wtr.print("{d:.0}\n", .{strength * 100});
}

const strength = try client.getStrength();
try wtr.print("{d:.0}\n", .{strength * 100});
},
.Toggle => {
var status = try client.getStatus();
Expand All @@ -208,5 +279,28 @@ pub fn main() !void {
.Off => {
try client.turnOff();
},
.Schedule => {
const sub_cmd = args_iter.next() orelse {
var status = try client.getStatus();
defer client.destroyStatus(status);
var buf = std.mem.zeroes([32]u8);
try wtr.writeAll(try status.formatSchedule(&buf));
return;
};

if (std.mem.eql(u8, "off", sub_cmd)) {
try client.setSchedule(.{ .SunSetToSunRise = false });
} else if (std.mem.eql(u8, "sun", sub_cmd)) {
try client.setSchedule(.{ .SunSetToSunRise = true });
} else {
const from = sub_cmd;
const to = args_iter.next() orelse return error.MissingTo;
const schedule = .{ .Custom = .{
.from_time = try Time.fromString(from),
.to_time = try Time.fromString(to),
} };
try client.setSchedule(schedule);
}
},
}
}
33 changes: 33 additions & 0 deletions src/util.zig
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,36 @@ pub fn get_build_info() []const u8 {
builtin.zig_backend,
});
}

pub fn SliceIter(comptime T: type) type {
return struct {
slice: []const T,
idx: usize,

const Self = @This();

pub fn init(slice: []const T) Self {
return .{
.slice = slice,
.idx = 0,
};
}

pub fn next(self: *Self) ?T {
if (self.idx == self.slice.len) {
return null;
}
const value = self.slice[self.idx];
self.idx += 1;
return value;
}
};
}

test "slice iter" {
var iter = SliceIter(u8).init(&[_]u8{ 1, 2, 3 });
try std.testing.expectEqual(iter.next().?, 1);
try std.testing.expectEqual(iter.next().?, 2);
try std.testing.expectEqual(iter.next().?, 3);
try std.testing.expectEqual(iter.next(), null);
}