FelixKratz / SketchyBar

A highly customizable macOS status bar replacement
https://felixkratz.github.io/SketchyBar/
GNU General Public License v3.0
5.77k stars 89 forks source link

Any way to schedule updates of items (specifically clock)? #453

Closed bobbyl140 closed 10 months ago

bobbyl140 commented 10 months ago

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 the date 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?

FelixKratz commented 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.

bobbyl140 commented 10 months ago

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.

FelixKratz commented 10 months ago

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(&current_time);
  const char* format = "%H:%M:%S";
  char buffer[64];
  strftime(buffer, sizeof(buffer), format, localtime(&current_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.

bobbyl140 commented 10 months ago

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.

FelixKratz commented 10 months ago

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.

bobbyl140 commented 10 months ago

That line did the trick! Thank you again, I really appreciate you guiding me through the solution.