Closed bobbyl140 closed 10 months ago
Have you considered setting the update_freq
of the item to 1
, such that the command is executed every second?
sketchybar --add item time right \
--set time update_freq=1 script='sketchybar --set $NAME label="$(date "+%H:%M:%S")"'
Or do you want to have actual sub-second precision with the display of the seconds, without invoking a command at all? If this is the case and you don't mind getting your hands dirty with some C, you can create a helper program, which you set up with a high precision timer and periodically send the time update directly via mach messages to sketchybar (see for example my C helper program for a hint how to do that: https://github.com/FelixKratz/dotfiles/tree/master/.config/sketchybar/helper). Doing it this way will drastically reduce the overhead of spawning the shell process and interpreting the script every second, with a total latency between helper and sketchybar lower than 100 microseconds.
I did set the update_freq
to 1, but the problem is this isn't always going to execute on the second, for example, if the config finishes reloading at 10:09:30.9, then the clock will be .9 seconds late, and yes I realize this is nitpicking, but I do frequently use the seconds on the stock Menu Bar. I certainly don't mind writing some code but I'm afraid I don't know C. I will take a look at the helper program and see what I can do.
Actually, it is really simple to create such C program:
#include "sketchybar.h"
#include <CoreFoundation/CoreFoundation.h>
#include <time.h>
void callback(CFRunLoopTimerRef timer, void* info) {
time_t current_time;
time(¤t_time);
const char* format = "%H:%M:%S";
char buffer[64];
strftime(buffer, sizeof(buffer), format, localtime(¤t_time));
uint32_t message_size = sizeof(buffer) + 64;
char message[message_size];
snprintf(message, message_size, "--set time label=\"%s\"", buffer);
sketchybar(message);
}
int main() {
CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, (int64_t)CFAbsoluteTimeGetCurrent() + 1.0, 1.0, 0, 0, callback, NULL);
CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopDefaultMode);
sketchybar("--add item time right");
CFRunLoopRun();
return 0;
}
Note that the "magic" happens in the sketchybar.h
file, which is this one: https://github.com/FelixKratz/dotfiles/blob/master/.config/sketchybar/helper/sketchybar.h
but you don't need to understand what is happening in there, you only need it to compile the program with:
clang -std=c99 clock.c -framework CoreFoundation -o clock
and then execute it with:
./clock
You can of course automate this to autmatically start with sketchybar (see my dotfiles for reference). With this running you will need no scripts at all and it will be perfectly in sync with the actual time.
Wow, thank you! I follow the logic of what you wrote, not that I'd be able to write C myself, but I see what you're saying now. For some reason though it still doesn't seem to be realtime. The timer still seems to rely on when clock
starts running, because depending on when through the second I run it, the delay varies. I tried adding one second to the current_time
in the code, but then it (of course) gets ahead of the normal clock by a little bit. I also tried setting the timer in the C program to execute every 0.5 seconds (possibly improperly) to no avail. I'm not really sure what I may be doing wrong here so I'm including the relevant config in case it helps. I did modify the names and paths of a few things, but that shouldn't matter (right?).
sketchybarrc:
sketchybar --add item clock left
sketchybar --set clock padding_right=5 background.drawing=1 icon.background.drawing=1 icon="" icon.color=0xff000000 icon.padding_left=4 icon.padding_right=5 background.border_width=1 label.padding_left=5 label.padding_right=5 background.color=0x77000000 label.font.size=12.5
# The below is the compiled clock.c
~/.config/sketchybar/scripts/clock &
Then in clock.c
, I removed the sketchybar --add
call so it could be handled in the sketchybarrc
instead, renamed the item
to clock
, and changed the format of the time string.
Ah, I forgot about that. You need to round down the CFAbsoluteTimeGetCurrent()
result from a double precision number to an integer, such that you fire the timer exactly at the begin of a new second:
CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, (int64_t)CFAbsoluteTimeGetCurrent() + 1.0, 1.0, 0, 0, callback, NULL);
I have updated this in my original response as well.
That line did the trick! Thank you again, I really appreciate you guiding me through the solution.
I just installed SketchyBar yesterday, oh boy it's amazing and I can already say I'm never going back. My single gripe is that a clock with seconds can't be updated in realtime (because of the need to call
date
). Is there a way to tell SketchyBar to run thedate
command exactly on the second? Or, instead, is there a more dynamic way of getting the time (in seconds) that doesn't require calling something? Sort of like an event subscription, but more of a system-level thing?