stevee

My wayland statusbar
git clone git://gtms.dev/stevee
Log | Files | Refs | Submodules | README | LICENSE

commit 67af604eb290497b057f72820a8ac3db11702217
parent dadf6bdd225f5efe54f4364db9fa89e496a05c2b
Author: Tomas Nemec <owl@gtms.dev>
Date:   Fri, 31 May 2024 18:36:43 +0200

update zig 0.12

Diffstat:
M.gitmodules | 3---
Mbuild.zig | 23++++++++---------------
Ddeps/zig-udev | 1-
Msrc/Buffer.zig | 13+++++++------
Msrc/Config.zig | 2+-
Msrc/Loop.zig | 55+++++++++++++++++++------------------------------------
Msrc/Modules.zig | 16++--------------
Msrc/Wayland.zig | 4++--
Msrc/main.zig | 2--
Dsrc/modules/Backlight.zig | 119-------------------------------------------------------------------------------
Dsrc/modules/Battery.zig | 176-------------------------------------------------------------------------------
Msrc/modules/Pulse.zig | 15++++++++-------
Msrc/render.zig | 16++++++----------
13 files changed, 53 insertions(+), 392 deletions(-)

diff --git a/.gitmodules b/.gitmodules @@ -4,9 +4,6 @@ [submodule "deps/zig-pixman"] path = deps/zig-pixman url = https://github.com/ifreund/zig-pixman -[submodule "deps/zig-udev"] - path = deps/zig-udev - url = https://git.sr.ht/~andreafeletto/zig-udev [submodule "deps/zig-fcft"] path = deps/zig-fcft url = https://git.sr.ht/~novakane/zig-fcft diff --git a/build.zig b/build.zig @@ -1,14 +1,13 @@ const std = @import("std"); -const Builder = std.build.Builder; const Scanner = @import("deps/zig-wayland/build.zig").Scanner; -pub fn build(b: *Builder) void { +pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); const scanner = Scanner.create(b, .{}); - const wayland = b.createModule(.{ .source_file = scanner.result }); + const wayland = b.createModule(.{ .root_source_file = scanner.result }); scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml"); scanner.addSystemProtocol("stable/viewporter/viewporter.xml"); @@ -29,17 +28,12 @@ pub fn build(b: *Builder) void { scanner.generate("zriver_control_v1", 1); const pixman = b.createModule(.{ - .source_file = .{ .path = "deps/zig-pixman/pixman.zig" }, + .root_source_file = .{ .path = "deps/zig-pixman/pixman.zig" }, }); const fcft = b.createModule(.{ - .source_file = .{ .path = "deps/zig-fcft/fcft.zig" }, - .dependencies = &.{ - .{ .name = "pixman", .module = pixman }, - }, - }); - const udev = b.createModule(.{ - .source_file = .{ .path = "deps/zig-udev/udev.zig" }, + .root_source_file = .{ .path = "deps/zig-fcft/fcft.zig" }, }); + fcft.addImport("pixman", pixman); const exe = b.addExecutable(.{ .name = "levee", @@ -48,10 +42,9 @@ pub fn build(b: *Builder) void { .optimize = optimize, }); - exe.addModule("fcft", fcft); - exe.addModule("pixman", pixman); - exe.addModule("udev", udev); - exe.addModule("wayland", wayland); + exe.root_module.addImport("fcft", fcft); + exe.root_module.addImport("pixman", pixman); + exe.root_module.addImport("wayland", wayland); exe.linkLibC(); exe.linkSystemLibrary("fcft"); diff --git a/deps/zig-udev b/deps/zig-udev @@ -1 +0,0 @@ -Subproject commit 199ed84cc9e797e315be030326908fd25d88cc19 diff --git a/src/Buffer.zig b/src/Buffer.zig @@ -1,6 +1,7 @@ const std = @import("std"); const mem = std.mem; -const os = std.os; +const posix = std.posix; +const linux = std.os.linux; const pixman = @import("pixman"); const wl = @import("wayland").client.wl; @@ -24,14 +25,14 @@ pub fn resize(self: *Buffer, shm: *wl.Shm, width: u31, height: u31) !void { self.width = width; self.height = height; - const fd = try os.memfd_create("levee-shm", os.linux.MFD.CLOEXEC); - defer os.close(fd); + const fd = try posix.memfd_create("levee-shm", linux.MFD.CLOEXEC); + defer posix.close(fd); const stride = width * 4; self.size = stride * height; - try os.ftruncate(fd, self.size); + try posix.ftruncate(fd, self.size); - self.mmap = try os.mmap(null, self.size, os.PROT.READ | os.PROT.WRITE, os.MAP.SHARED, fd, 0); + self.mmap = try posix.mmap(null, self.size, posix.PROT.READ | posix.PROT.WRITE, .{ .TYPE = .SHARED }, fd, 0); self.data = mem.bytesAsSlice(u32, self.mmap.?); const pool = try shm.createPool(fd, self.size); @@ -47,7 +48,7 @@ pub fn resize(self: *Buffer, shm: *wl.Shm, width: u31, height: u31) !void { pub fn deinit(self: *Buffer) void { if (self.pix) |pix| _ = pix.unref(); if (self.buffer) |buf| buf.destroy(); - if (self.mmap) |mmap| os.munmap(mmap); + if (self.mmap) |mmap| posix.munmap(mmap); } fn listener(_: *wl.Buffer, event: wl.Buffer.Event, buffer: *Buffer) void { diff --git a/src/Config.zig b/src/Config.zig @@ -29,6 +29,6 @@ pub fn init() !Config { }, .border = 2, .font = try fcft.Font.fromName(&font_names, null), - .clockFormat = "%d %b %Y - %R", + .clockFormat = "%d/%m %R:%S", }; } diff --git a/src/Loop.zig b/src/Loop.zig @@ -1,23 +1,24 @@ const std = @import("std"); const log = std.log; const mem = std.mem; -const os = std.os; +const posix = std.posix; +const linux = std.os.linux; const utils = @import("utils.zig"); const Loop = @This(); const state = &@import("root").state; -sfd: os.fd_t, +sfd: posix.fd_t, pub fn init() !Loop { - var mask = os.empty_sigset; - os.linux.sigaddset(&mask, os.linux.SIG.INT); - os.linux.sigaddset(&mask, os.linux.SIG.TERM); - os.linux.sigaddset(&mask, os.linux.SIG.QUIT); + var mask = posix.empty_sigset; + linux.sigaddset(&mask, linux.SIG.INT); + linux.sigaddset(&mask, linux.SIG.TERM); + linux.sigaddset(&mask, linux.SIG.QUIT); - _ = os.linux.sigprocmask(os.linux.SIG.BLOCK, &mask, null); - const sfd = os.linux.signalfd(-1, &mask, os.linux.SFD.NONBLOCK); + _ = linux.sigprocmask(linux.SIG.BLOCK, &mask, null); + const sfd = linux.signalfd(-1, &mask, linux.SFD.NONBLOCK); return Loop{ .sfd = @intCast(sfd) }; } @@ -26,30 +27,20 @@ pub fn run(self: *Loop) !void { const wayland = &state.wayland; const modules = &state.modules; - var fds = [_]os.pollfd{ + var fds = [_]posix.pollfd{ .{ .fd = self.sfd, - .events = os.POLL.IN, + .events = posix.POLL.IN, .revents = undefined, }, .{ .fd = wayland.fd, - .events = os.POLL.IN, - .revents = undefined, - }, - .{ - .fd = if (modules.backlight) |mod| mod.fd else -1, - .events = os.POLL.IN, - .revents = undefined, - }, - .{ - .fd = if (modules.battery) |mod| mod.fd else -1, - .events = os.POLL.IN, + .events = posix.POLL.IN, .revents = undefined, }, .{ .fd = if (modules.pulse) |mod| mod.fd else -1, - .events = os.POLL.IN, + .events = posix.POLL.IN, .revents = undefined, }, }; @@ -61,42 +52,34 @@ pub fn run(self: *Loop) !void { if (ret == .SUCCESS) break; } - _ = os.poll(&fds, -1) catch |err| { + _ = posix.poll(&fds, -1) catch |err| { log.err("poll failed: {s}", .{@errorName(err)}); return; }; for (fds) |fd| { - if (fd.revents & os.POLL.HUP != 0 or fd.revents & os.POLL.ERR != 0) { + if (fd.revents & posix.POLL.HUP != 0 or fd.revents & posix.POLL.ERR != 0) { return; } } // signals - if (fds[0].revents & os.POLL.IN != 0) { + if (fds[0].revents & posix.POLL.IN != 0) { return; } // wayland - if (fds[1].revents & os.POLL.IN != 0) { + if (fds[1].revents & posix.POLL.IN != 0) { const errno = wayland.display.dispatch(); if (errno != .SUCCESS) return; } - if (fds[1].revents & os.POLL.OUT != 0) { + if (fds[1].revents & posix.POLL.OUT != 0) { const errno = wayland.display.flush(); if (errno != .SUCCESS) return; } // modules - if (modules.backlight) |*mod| if (fds[2].revents & os.POLL.IN != 0) { - log.info("backlight", .{}); - mod.refresh() catch return; - }; - if (modules.battery) |*mod| if (fds[3].revents & os.POLL.IN != 0) { - log.info("battery", .{}); - mod.refresh() catch return; - }; - if (modules.pulse) |*mod| if (fds[4].revents & os.POLL.IN != 0) { + if (modules.pulse) |*mod| if (fds[2].revents & posix.POLL.IN != 0) { log.info("pulse", .{}); mod.refresh() catch return; }; diff --git a/src/Modules.zig b/src/Modules.zig @@ -4,16 +4,12 @@ const ArrayList = std.ArrayList; const Modules = @This(); -const Backlight = @import("modules/Backlight.zig"); -const Battery = @import("modules/Battery.zig"); const Pulse = @import("modules/Pulse.zig"); var state = &@import("root").state; -const Tag = enum { backlight, battery, pulse }; +const Tag = enum { pulse }; -backlight: ?Backlight = null, -battery: ?Battery = null, pulse: ?Pulse = null, order: ArrayList(Tag), @@ -22,20 +18,12 @@ pub fn init() Modules { } pub fn deinit(self: *Modules) void { - if (self.backlight) |*mod| mod.deinit(); - if (self.battery) |*mod| mod.deinit(); if (self.pulse) |*mod| mod.deinit(); self.order.deinit(); } pub fn register(self: *Modules, name: []const u8) !void { - if (mem.eql(u8, name, "backlight")) { - self.backlight = try Backlight.init(); - try self.order.append(.backlight); - } else if (mem.eql(u8, name, "battery")) { - self.battery = try Battery.init(); - try self.order.append(.battery); - } else if (mem.eql(u8, name, "pulse")) { + if (mem.eql(u8, name, "pulse")) { self.pulse = try Pulse.init(); try self.pulse.?.start(); try self.order.append(.pulse); diff --git a/src/Wayland.zig b/src/Wayland.zig @@ -2,7 +2,7 @@ const std = @import("std"); const log = std.log; const mem = std.mem; const meta = std.meta; -const os = std.os; +const posix = std.posix; const wl = @import("wayland").client.wl; const wp = @import("wayland").client.wp; @@ -19,7 +19,7 @@ const Wayland = @This(); const state = &@import("root").state; display: *wl.Display, -fd: os.fd_t, +fd: posix.fd_t, compositor: ?*wl.Compositor = null, subcompositor: ?*wl.Subcompositor = null, diff --git a/src/main.zig b/src/main.zig @@ -76,8 +76,6 @@ fn help(program_name: []const u8) void { \\Usage: {s} [module]... \\ \\Available modules: - \\ backlight screen brightness - \\ battery battery capacity \\ pulse speaker volume with pulseaudio \\ ; diff --git a/src/modules/Backlight.zig b/src/modules/Backlight.zig @@ -1,119 +0,0 @@ -const std = @import("std"); -const fmt = std.fmt; -const log = std.log; -const mem = std.mem; -const os = std.os; - -const udev = @import("udev"); - -const render = @import("../render.zig"); -const utils = @import("../utils.zig"); -const Backlight = @This(); - -const state = &@import("root").state; - -context: *udev.Udev, -monitor: *udev.Monitor, -fd: os.fd_t, -devices: DeviceList, - -const Device = struct { - name: []const u8, - value: u64, - max: u64, -}; - -const DeviceList = std.ArrayList(Device); - -pub fn init() !Backlight { - const context = try udev.Udev.new(); - - const monitor = try udev.Monitor.newFromNetlink(context, "udev"); - try monitor.filterAddMatchSubsystemDevType("backlight", null); - try monitor.filterAddMatchSubsystemDevType("power_supply", null); - try monitor.enableReceiving(); - - var devices = DeviceList.init(state.gpa); - try updateDevices(state.gpa, context, &devices); - - return Backlight{ - .context = context, - .monitor = monitor, - .fd = try monitor.getFd(), - .devices = devices, - }; -} - -pub fn deinit(self: *Backlight) void { - _ = self.context.unref(); - for (self.devices.items) |*device| { - state.gpa.free(device.name); - } - self.devices.deinit(); -} - -pub fn refresh(self: *Backlight) !void { - _ = try self.monitor.receiveDevice(); - - for (state.wayland.monitors.items) |monitor| { - if (monitor.bar) |bar| { - if (bar.configured) { - render.renderModules(bar) catch continue; - bar.modules.surface.commit(); - bar.background.surface.commit(); - } - } - } -} - -pub fn print(self: *Backlight, writer: anytype) !void { - try updateDevices(state.gpa, self.context, &self.devices); - const device = self.devices.items[0]; - var percent = @as(f64, @floatFromInt(device.value)) * 100.0; - percent /= @as(f64, @floatFromInt(device.max)); - const value = @as(u8, @intFromFloat(@round(percent))); - - try writer.print("💡 {d}%", .{value}); -} - -fn updateDevices( - gpa: mem.Allocator, - context: *udev.Udev, - devices: *DeviceList, -) !void { - const enumerate = try udev.Enumerate.new(context); - try enumerate.addMatchSubsystem("backlight"); - try enumerate.scanDevices(); - const entries = enumerate.getListEntry(); - - var maybe_entry = entries; - while (maybe_entry) |entry| : (maybe_entry = entry.getNext()) { - const path = entry.getName(); - const device = try udev.Device.newFromSyspath(context, path); - try updateOrAppend(gpa, devices, device); - } -} - -fn updateOrAppend( - gpa: mem.Allocator, - devices: *DeviceList, - dev: *udev.Device, -) !void { - const value = try dev.getSysattrValue("actual_brightness"); - const max = try dev.getSysattrValue("max_brightness"); - const name = try dev.getSysname(); - - const device = blk: { - for (devices.items) |*device| { - if (mem.eql(u8, device.name, name)) { - break :blk device; - } - } else { - const device = try devices.addOne(); - device.name = try gpa.dupe(u8, name); - break :blk device; - } - }; - device.value = try fmt.parseInt(u64, value, 10); - device.max = try fmt.parseInt(u64, max, 10); -} diff --git a/src/modules/Battery.zig b/src/modules/Battery.zig @@ -1,176 +0,0 @@ -const std = @import("std"); -const fmt = std.fmt; -const log = std.log; -const mem = std.mem; -const os = std.os; - -const udev = @import("udev"); - -const Module = @import("../Modules.zig").Module; -const Event = @import("../Loop.zig").Event; -const render = @import("../render.zig"); -const utils = @import("../utils.zig"); -const Battery = @This(); - -const state = &@import("root").state; - -context: *udev.Udev, -fd: os.fd_t, -devices: DeviceList, - -const Device = struct { - name: []const u8, - status: []const u8, - capacity: u8, -}; - -const DeviceList = std.ArrayList(Device); - -pub fn init() !Battery { - const tfd = tfd: { - const fd = os.linux.timerfd_create( - os.CLOCK.MONOTONIC, - os.linux.TFD.CLOEXEC, - ); - const interval: os.linux.itimerspec = .{ - .it_interval = .{ .tv_sec = 10, .tv_nsec = 0 }, - .it_value = .{ .tv_sec = 10, .tv_nsec = 0 }, - }; - _ = os.linux.timerfd_settime(@as(i32, @intCast(fd)), 0, &interval, null); - break :tfd @as(os.fd_t, @intCast(fd)); - }; - - const context = try udev.Udev.new(); - - var devices = DeviceList.init(state.gpa); - try updateDevices(state.gpa, context, &devices); - - return Battery{ - .context = context, - .fd = tfd, - .devices = devices, - }; -} - -pub fn deinit(self: *Battery) void { - _ = self.context.unref(); - for (self.devices.items) |*device| { - state.gpa.free(device.name); - state.gpa.free(device.status); - } - self.devices.deinit(); -} - -pub fn print(self: *Battery, writer: anytype) !void { - try updateDevices(state.gpa, self.context, &self.devices); - const device = self.devices.items[0]; - - var icon: []const u8 = "❓"; - if (mem.eql(u8, device.status, "Discharging")) { - icon = "🔋"; - } else if (mem.eql(u8, device.status, "Charging")) { - icon = "🔌"; - } else if (mem.eql(u8, device.status, "Full")) { - icon = "⚡"; - } - - try fmt.format(writer, "{s} {d}%", .{ icon, device.capacity }); -} - -pub fn refresh(self: *Battery) !void { - var expirations = mem.zeroes([8]u8); - _ = try os.read(self.fd, &expirations); - - for (state.wayland.monitors.items) |monitor| { - if (monitor.bar) |bar| { - if (bar.configured) { - render.renderClock(bar) catch continue; - render.renderModules(bar) catch continue; - bar.clock.surface.commit(); - bar.modules.surface.commit(); - bar.background.surface.commit(); - } - } - } -} - -fn updateDevices( - gpa: mem.Allocator, - context: *udev.Udev, - devices: *DeviceList, -) !void { - const enumerate = try udev.Enumerate.new(context); - defer _ = enumerate.unref(); - - try enumerate.addMatchSubsystem("power_supply"); - try enumerate.addMatchSysattr("type", "Battery"); - try enumerate.scanDevices(); - - const entries = enumerate.getListEntry(); - - var maybe_entry = entries; - while (maybe_entry) |entry| : (maybe_entry = entry.getNext()) { - const path = entry.getName(); - const device = try udev.Device.newFromSyspath(context, path); - try updateOrAppend(gpa, devices, device); - } -} - -fn updateOrAppend( - gpa: mem.Allocator, - devices: *DeviceList, - dev: *udev.Device, -) !void { - const name = dev.getSysname() catch return; - const status = dev.getSysattrValue("status") catch return; - const capacity = getCapacity(dev) catch return; - - const device = blk: { - for (devices.items) |*device| { - if (mem.eql(u8, device.name, name)) { - gpa.free(device.status); - break :blk device; - } - } else { - const device = try devices.addOne(); - device.name = try gpa.dupe(u8, name); - break :blk device; - } - }; - - device.status = try gpa.dupe(u8, status); - device.capacity = capacity; -} - -fn getCapacity(dev: *udev.Device) !u8 { - const capacity_str = dev.getSysattrValue("capacity") catch { - return computeCapacityFromCharge(dev) catch { - return computeCapacityFromEnergy(dev); - }; - }; - - const capacity = try fmt.parseInt(u8, capacity_str, 10); - return capacity; -} - -fn computeCapacityFromEnergy(dev: *udev.Device) !u8 { - const energy_str = try dev.getSysattrValue("energy_now"); - const energy_full_str = try dev.getSysattrValue("energy_full"); - - const energy = try fmt.parseFloat(f64, energy_str); - const energy_full = try fmt.parseFloat(f64, energy_full_str); - - const capacity = energy * 100.0 / energy_full; - return @as(u8, @intFromFloat(@round(capacity))); -} - -fn computeCapacityFromCharge(dev: *udev.Device) !u8 { - const charge_str = try dev.getSysattrValue("charge_now"); - const charge_full_str = try dev.getSysattrValue("charge_full"); - - const charge = try fmt.parseFloat(f64, charge_str); - const charge_full = try fmt.parseFloat(f64, charge_full_str); - - const capacity = charge * 100.0 / charge_full; - return @as(u8, @intFromFloat(@round(capacity))); -} diff --git a/src/modules/Pulse.zig b/src/modules/Pulse.zig @@ -1,7 +1,8 @@ const std = @import("std"); const log = std.log; const mem = std.mem; -const os = std.os; +const posix = std.posix; +const linux = std.os.linux; const pulse = @cImport(@cInclude("pulse/pulseaudio.h")); @@ -13,7 +14,7 @@ const Pulse = @This(); const state = &@import("root").state; -fd: os.fd_t, +fd: posix.fd_t, mainloop: *pulse.pa_threaded_mainloop, api: *pulse.pa_mainloop_api, context: *pulse.pa_context, @@ -26,8 +27,8 @@ muted: bool, pub fn init() !Pulse { // create descriptor for poll in Loop const efd = efd: { - const fd = try os.eventfd(0, os.linux.EFD.NONBLOCK); - break :efd @as(os.fd_t, @intCast(fd)); + const fd = try posix.eventfd(0, linux.EFD.NONBLOCK); + break :efd @as(posix.fd_t, @intCast(fd)); }; // setup pulseaudio api @@ -71,7 +72,7 @@ pub fn start(self: *Pulse) !void { pub fn refresh(self: *Pulse) !void { var data = mem.zeroes([8]u8); - _ = try os.read(self.fd, &data); + _ = try posix.read(self.fd, &data); for (state.wayland.monitors.items) |monitor| { if (monitor.bar) |bar| { @@ -90,7 +91,7 @@ pub fn print(self: *Pulse, writer: anytype) !void { if (self.muted) { try writer.print(" 🔇 ", .{}); } else { - try writer.print("🔊 {d}%", .{self.volume}); + try writer.print("🔊 {d}%", .{self.volume}); } } @@ -194,5 +195,5 @@ export fn sinkInfoCallback( self.muted = info.mute != 0; const increment = mem.asBytes(&@as(u64, 1)); - _ = os.write(self.fd, increment) catch return; + _ = posix.write(self.fd, increment) catch return; } diff --git a/src/render.zig b/src/render.zig @@ -10,8 +10,6 @@ const Bar = @import("Bar.zig"); const Tag = @import("Tags.zig").Tag; const utils = @import("utils.zig"); -const Backlight = @import("modules/Backlight.zig"); -const Battery = @import("modules/Battery.zig"); const Pulse = @import("modules/Pulse.zig"); const state = &@import("root").state; @@ -81,7 +79,7 @@ pub fn renderClock(bar: *Bar) !void { var x: i32 = 0; i = 0; - var color = pixman.Image.createSolidFill(&state.config.foregroundColor).?; + const color = pixman.Image.createSolidFill(&state.config.foregroundColor).?; while (i < run.count) : (i += 1) { const glyph = run.glyphs[i]; x += @as(i32, @intCast(glyph.x)); @@ -107,8 +105,6 @@ pub fn renderModules(bar: *Bar) !void { for (state.modules.order.items) |tag| { try writer.print(" | ", .{}); switch (tag) { - .backlight => try state.modules.backlight.?.print(writer), - .battery => try state.modules.battery.?.print(writer), .pulse => try state.modules.pulse.?.print(writer), } } @@ -131,8 +127,8 @@ pub fn renderModules(bar: *Bar) !void { // set subsurface offset const font_height = @as(u32, @intCast(state.config.font.height)); - var x_offset = @as(i32, @intCast(bar.width - width)); - var y_offset = @as(i32, @intCast(@divFloor(bar.height - font_height, 2))); + const x_offset = @as(i32, @intCast(bar.width - width)); + const y_offset = @as(i32, @intCast(@divFloor(bar.height - font_height, 2))); bar.modules.subsurface.setPosition(x_offset, y_offset); const buffers = &bar.modules.buffers; @@ -148,7 +144,7 @@ pub fn renderModules(bar: *Bar) !void { var x: i32 = 0; i = 0; - var color = pixman.Image.createSolidFill(&state.config.foregroundColor).?; + const color = pixman.Image.createSolidFill(&state.config.foregroundColor).?; while (i < run.count) : (i += 1) { const glyph = run.glyphs[i]; x += @as(i32, @intCast(glyph.x)); @@ -200,7 +196,7 @@ fn renderTag( break :blk &state.config.foregroundColor; }; const font = state.config.font; - var char = pixman.Image.createSolidFill(glyph_color).?; + const char = pixman.Image.createSolidFill(glyph_color).?; const glyph = try font.rasterizeCharUtf32(tag.label, .default); const x = offset + @divFloor(size - glyph.width, 2); const y = @divFloor(size - glyph.height, 2); @@ -208,7 +204,7 @@ fn renderTag( } fn formatDatetime() ![]const u8 { - var buf = try state.gpa.alloc(u8, 256); + const buf = try state.gpa.alloc(u8, 256); const now = time.time(null); const local = time.localtime(&now); const len = time.strftime(