From 8d16baf6f165f37bc8397afddf231b8d5e57636e Mon Sep 17 00:00:00 2001 From: xenomxrph Date: Wed, 16 Jul 2025 15:00:51 +0200 Subject: [PATCH] initalize --- README.mb | 0 aerospace/.aerospace.toml.swp | Bin 0 -> 24576 bytes aerospace/aerospace.toml | 223 ++++++++++++ sketchybar/.DS_Store | Bin 0 -> 6148 bytes sketchybar/bar.lua | 15 + sketchybar/bridge/.gitignore | 1 + sketchybar/bridge/Makefile | 3 + sketchybar/bridge/menus/Makefile | 5 + sketchybar/bridge/menus/menus.c | 248 +++++++++++++ sketchybar/bridge/network_load/Makefile | 5 + sketchybar/bridge/network_load/network_load.c | 39 ++ sketchybar/bridge/network_load/network_load.h | 93 +++++ sketchybar/bridge/sketchybar.h | 124 +++++++ sketchybar/config/colors.lua | 38 ++ sketchybar/config/dimens.lua | 40 +++ sketchybar/config/fonts.lua | 14 + sketchybar/config/icons.lua | 332 ++++++++++++++++++ sketchybar/config/init.lua | 1 + sketchybar/config/settings.lua | 11 + sketchybar/constants.lua | 34 ++ sketchybar/default.lua | 67 ++++ sketchybar/init.lua | 15 + sketchybar/install/init.lua | 1 + sketchybar/install/sbar.lua | 25 ++ sketchybar/items/apple.lua | 7 + sketchybar/items/front_apps.lua | 71 ++++ sketchybar/items/init.lua | 11 + sketchybar/items/media.lua | 118 +++++++ sketchybar/items/menu_spaces_toggle.lua | 71 ++++ sketchybar/items/menus.lua | 74 ++++ sketchybar/items/menus/bin/menus | Bin 0 -> 36920 bytes sketchybar/items/message.lua | 55 +++ sketchybar/items/spaces.lua | 108 ++++++ sketchybar/items/widgets/battery.lua | 91 +++++ sketchybar/items/widgets/calendar.lua | 17 + sketchybar/items/widgets/init.lua | 4 + sketchybar/items/widgets/volume.lua | 128 +++++++ sketchybar/items/widgets/wifi.lua | 261 ++++++++++++++ sketchybar/sketchybarrc | 3 + sketchybar/util/.gitkeep | 0 40 files changed, 2353 insertions(+) create mode 100644 README.mb create mode 100644 aerospace/.aerospace.toml.swp create mode 100644 aerospace/aerospace.toml create mode 100644 sketchybar/.DS_Store create mode 100644 sketchybar/bar.lua create mode 100644 sketchybar/bridge/.gitignore create mode 100644 sketchybar/bridge/Makefile create mode 100644 sketchybar/bridge/menus/Makefile create mode 100644 sketchybar/bridge/menus/menus.c create mode 100644 sketchybar/bridge/network_load/Makefile create mode 100644 sketchybar/bridge/network_load/network_load.c create mode 100644 sketchybar/bridge/network_load/network_load.h create mode 100644 sketchybar/bridge/sketchybar.h create mode 100644 sketchybar/config/colors.lua create mode 100644 sketchybar/config/dimens.lua create mode 100644 sketchybar/config/fonts.lua create mode 100644 sketchybar/config/icons.lua create mode 100644 sketchybar/config/init.lua create mode 100644 sketchybar/config/settings.lua create mode 100644 sketchybar/constants.lua create mode 100644 sketchybar/default.lua create mode 100644 sketchybar/init.lua create mode 100644 sketchybar/install/init.lua create mode 100644 sketchybar/install/sbar.lua create mode 100644 sketchybar/items/apple.lua create mode 100644 sketchybar/items/front_apps.lua create mode 100644 sketchybar/items/init.lua create mode 100644 sketchybar/items/media.lua create mode 100644 sketchybar/items/menu_spaces_toggle.lua create mode 100644 sketchybar/items/menus.lua create mode 100755 sketchybar/items/menus/bin/menus create mode 100644 sketchybar/items/message.lua create mode 100644 sketchybar/items/spaces.lua create mode 100644 sketchybar/items/widgets/battery.lua create mode 100644 sketchybar/items/widgets/calendar.lua create mode 100644 sketchybar/items/widgets/init.lua create mode 100644 sketchybar/items/widgets/volume.lua create mode 100644 sketchybar/items/widgets/wifi.lua create mode 100755 sketchybar/sketchybarrc create mode 100644 sketchybar/util/.gitkeep diff --git a/README.mb b/README.mb new file mode 100644 index 0000000..e69de29 diff --git a/aerospace/.aerospace.toml.swp b/aerospace/.aerospace.toml.swp new file mode 100644 index 0000000000000000000000000000000000000000..eaa1ffe80b26da7243cab828610a0d3e4316c693 GIT binary patch literal 24576 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 literal 0 HcmV?d00001 diff --git a/sketchybar/bar.lua b/sketchybar/bar.lua new file mode 100644 index 0000000..1ade075 --- /dev/null +++ b/sketchybar/bar.lua @@ -0,0 +1,15 @@ +local settings = require("config.settings") + +sbar.bar({ + topmost = "window", + height = settings.dimens.graphics.bar.height, + color = 0xff1e1e2e, + padding_right = settings.dimens.padding.right, + padding = settings.dimens.padding.bar, + padding_left = settings.dimens.padding.left, + margin = settings.dimens.padding.bar, + corner_radius = settings.dimens.graphics.background.corner_radius, + y_offset = settings.dimens.graphics.bar.offset, + -- blur_radius = settings.dimens.graphics.blur_radius, + border_width = 0, +}) diff --git a/sketchybar/bridge/.gitignore b/sketchybar/bridge/.gitignore new file mode 100644 index 0000000..ba077a4 --- /dev/null +++ b/sketchybar/bridge/.gitignore @@ -0,0 +1 @@ +bin diff --git a/sketchybar/bridge/Makefile b/sketchybar/bridge/Makefile new file mode 100644 index 0000000..43a4e04 --- /dev/null +++ b/sketchybar/bridge/Makefile @@ -0,0 +1,3 @@ +all: + (cd network_load && $(MAKE)) >/dev/null + (cd menus && $(MAKE)) >/dev/null diff --git a/sketchybar/bridge/menus/Makefile b/sketchybar/bridge/menus/Makefile new file mode 100644 index 0000000..0cb454e --- /dev/null +++ b/sketchybar/bridge/menus/Makefile @@ -0,0 +1,5 @@ +bin/menus: menus.c | bin + clang -std=c99 -O3 -F/System/Library/PrivateFrameworks/ -framework Carbon -framework SkyLight $< -o $@ + +bin: + mkdir bin diff --git a/sketchybar/bridge/menus/menus.c b/sketchybar/bridge/menus/menus.c new file mode 100644 index 0000000..2e77822 --- /dev/null +++ b/sketchybar/bridge/menus/menus.c @@ -0,0 +1,248 @@ +#include + +void ax_init() { + const void *keys[] = { kAXTrustedCheckOptionPrompt }; + const void *values[] = { kCFBooleanTrue }; + + CFDictionaryRef options; + options = CFDictionaryCreate(kCFAllocatorDefault, + keys, + values, + sizeof(keys) / sizeof(*keys), + &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks ); + + bool trusted = AXIsProcessTrustedWithOptions(options); + CFRelease(options); + if (!trusted) exit(1); +} + +void ax_perform_click(AXUIElementRef element) { + if (!element) return; + AXUIElementPerformAction(element, kAXCancelAction); + usleep(150000); + AXUIElementPerformAction(element, kAXPressAction); +} + +CFStringRef ax_get_title(AXUIElementRef element) { + CFTypeRef title = NULL; + AXError error = AXUIElementCopyAttributeValue(element, + kAXTitleAttribute, + &title ); + + if (error != kAXErrorSuccess) return NULL; + return title; +} + +void ax_select_menu_option(AXUIElementRef app, int id) { + AXUIElementRef menubars_ref = NULL; + CFArrayRef children_ref = NULL; + + AXError error = AXUIElementCopyAttributeValue(app, + kAXMenuBarAttribute, + (CFTypeRef*)&menubars_ref); + if (error == kAXErrorSuccess) { + error = AXUIElementCopyAttributeValue(menubars_ref, + kAXVisibleChildrenAttribute, + (CFTypeRef*)&children_ref ); + + if (error == kAXErrorSuccess) { + uint32_t count = CFArrayGetCount(children_ref); + if (id < count) { + AXUIElementRef item = CFArrayGetValueAtIndex(children_ref, id); + ax_perform_click(item); + } + if (children_ref) CFRelease(children_ref); + } + if (menubars_ref) CFRelease(menubars_ref); + } +} + +void ax_print_menu_options(AXUIElementRef app) { + AXUIElementRef menubars_ref = NULL; + CFTypeRef menubar = NULL; + CFArrayRef children_ref = NULL; + + AXError error = AXUIElementCopyAttributeValue(app, + kAXMenuBarAttribute, + (CFTypeRef*)&menubars_ref); + if (error == kAXErrorSuccess) { + error = AXUIElementCopyAttributeValue(menubars_ref, + kAXVisibleChildrenAttribute, + (CFTypeRef*)&children_ref ); + + if (error == kAXErrorSuccess) { + uint32_t count = CFArrayGetCount(children_ref); + + for (int i = 1; i < count; i++) { + AXUIElementRef item = CFArrayGetValueAtIndex(children_ref, i); + CFTypeRef title = ax_get_title(item); + + if (title) { + uint32_t buffer_len = 2*CFStringGetLength(title); + char buffer[2*CFStringGetLength(title)]; + CFStringGetCString(title, buffer, buffer_len, kCFStringEncodingUTF8); + printf("%s\n", buffer); + CFRelease(title); + } + } + } + if (menubars_ref) CFRelease(menubars_ref); + if (children_ref) CFRelease(children_ref); + } +} + +AXUIElementRef ax_get_extra_menu_item(char* alias) { + pid_t pid = 0; + CGRect bounds = CGRectNull; + CFArrayRef window_list = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, + kCGNullWindowID ); + char owner_buffer[256]; + char name_buffer[256]; + char buffer[512]; + int window_count = CFArrayGetCount(window_list); + for (int i = 0; i < window_count; ++i) { + CFDictionaryRef dictionary = CFArrayGetValueAtIndex(window_list, i); + if (!dictionary) continue; + + CFStringRef owner_ref = CFDictionaryGetValue(dictionary, + kCGWindowOwnerName); + + CFNumberRef owner_pid_ref = CFDictionaryGetValue(dictionary, + kCGWindowOwnerPID); + + CFStringRef name_ref = CFDictionaryGetValue(dictionary, kCGWindowName); + CFNumberRef layer_ref = CFDictionaryGetValue(dictionary, kCGWindowLayer); + CFDictionaryRef bounds_ref = CFDictionaryGetValue(dictionary, + kCGWindowBounds); + + if (!name_ref || !owner_ref || !owner_pid_ref || !layer_ref || !bounds_ref) + continue; + + long long int layer = 0; + CFNumberGetValue(layer_ref, CFNumberGetType(layer_ref), &layer); + uint64_t owner_pid = 0; + CFNumberGetValue(owner_pid_ref, + CFNumberGetType(owner_pid_ref), + &owner_pid ); + + if (layer != 0x19) continue; + bounds = CGRectNull; + if (!CGRectMakeWithDictionaryRepresentation(bounds_ref, &bounds)) continue; + CFStringGetCString(owner_ref, + owner_buffer, + sizeof(owner_buffer), + kCFStringEncodingUTF8); + + CFStringGetCString(name_ref, + name_buffer, + sizeof(name_buffer), + kCFStringEncodingUTF8); + snprintf(buffer, sizeof(buffer), "%s,%s", owner_buffer, name_buffer); + + if (strcmp(buffer, alias) == 0) { + pid = owner_pid; + break; + } + } + CFRelease(window_list); + if (!pid) return NULL; + + AXUIElementRef app = AXUIElementCreateApplication(pid); + if (!app) return NULL; + AXUIElementRef result = NULL; + CFTypeRef extras = NULL; + CFArrayRef children_ref = NULL; + AXError error = AXUIElementCopyAttributeValue(app, + kAXExtrasMenuBarAttribute, + &extras ); + if (error == kAXErrorSuccess) { + error = AXUIElementCopyAttributeValue(extras, + kAXVisibleChildrenAttribute, + (CFTypeRef*)&children_ref ); + + if (error == kAXErrorSuccess) { + uint32_t count = CFArrayGetCount(children_ref); + for (uint32_t i = 0; i < count; i++) { + AXUIElementRef item = CFArrayGetValueAtIndex(children_ref, i); + CFTypeRef position_ref = NULL; + CFTypeRef size_ref = NULL; + AXUIElementCopyAttributeValue(item, kAXPositionAttribute, + &position_ref ); + AXUIElementCopyAttributeValue(item, kAXSizeAttribute, + &size_ref ); + if (!position_ref || !size_ref) continue; + + CGPoint position = CGPointZero; + AXValueGetValue(position_ref, kAXValueCGPointType, &position); + CGSize size = CGSizeZero; + AXValueGetValue(size_ref, kAXValueCGSizeType, &size); + CFRelease(position_ref); + CFRelease(size_ref); + // The offset is exactly 8 on macOS Sonoma... + // printf("%f %f\n", position.x, bounds.origin.x); + if (error == kAXErrorSuccess + && fabs(position.x - bounds.origin.x) <= 10) { + result = item; + break; + } + } + } + } + + CFRelease(app); + return result; +} + +extern int SLSMainConnectionID(); +extern void SLSSetMenuBarVisibilityOverrideOnDisplay(int cid, int did, bool enabled); +extern void SLSSetMenuBarVisibilityOverrideOnDisplay(int cid, int did, bool enabled); +extern void SLSSetMenuBarInsetAndAlpha(int cid, double u1, double u2, float alpha); +void ax_select_menu_extra(char* alias) { + AXUIElementRef item = ax_get_extra_menu_item(alias); + if (!item) return; + SLSSetMenuBarInsetAndAlpha(SLSMainConnectionID(), 0, 1, 0.0); + SLSSetMenuBarVisibilityOverrideOnDisplay(SLSMainConnectionID(), 0, true); + SLSSetMenuBarInsetAndAlpha(SLSMainConnectionID(), 0, 1, 0.0); + ax_perform_click(item); + SLSSetMenuBarVisibilityOverrideOnDisplay(SLSMainConnectionID(), 0, false); + SLSSetMenuBarInsetAndAlpha(SLSMainConnectionID(), 0, 1, 1.0); + CFRelease(item); +} + +extern void _SLPSGetFrontProcess(ProcessSerialNumber* psn); +extern void SLSGetConnectionIDForPSN(int cid, ProcessSerialNumber* psn, int* cid_out); +extern void SLSConnectionGetPID(int cid, pid_t* pid_out); +AXUIElementRef ax_get_front_app() { + ProcessSerialNumber psn; + _SLPSGetFrontProcess(&psn); + int target_cid; + SLSGetConnectionIDForPSN(SLSMainConnectionID(), &psn, &target_cid); + + pid_t pid; + SLSConnectionGetPID(target_cid, &pid); + return AXUIElementCreateApplication(pid); +} + +int main (int argc, char **argv) { + if (argc == 1) { + printf("Usage: %s [-l | -s id/alias ]\n", argv[0]); + exit(0); + } + ax_init(); + if (strcmp(argv[1], "-l") == 0) { + AXUIElementRef app = ax_get_front_app(); + if (!app) return 1; + ax_print_menu_options(app); + CFRelease(app); + } else if (argc == 3 && strcmp(argv[1], "-s") == 0) { + int id = 0; + if (sscanf(argv[2], "%d", &id) == 1) { + AXUIElementRef app = ax_get_front_app(); + if (!app) return 1; + ax_select_menu_option(app, id); + CFRelease(app); + } else ax_select_menu_extra(argv[2]); + } + return 0; +} diff --git a/sketchybar/bridge/network_load/Makefile b/sketchybar/bridge/network_load/Makefile new file mode 100644 index 0000000..3eeee83 --- /dev/null +++ b/sketchybar/bridge/network_load/Makefile @@ -0,0 +1,5 @@ +bin/network_load: network_load.c network_load.h ../sketchybar.h | bin + clang -std=c99 -O3 $< -o $@ + +bin: + mkdir bin diff --git a/sketchybar/bridge/network_load/network_load.c b/sketchybar/bridge/network_load/network_load.c new file mode 100644 index 0000000..1be53b0 --- /dev/null +++ b/sketchybar/bridge/network_load/network_load.c @@ -0,0 +1,39 @@ +#include "network_load.h" +#include "../sketchybar.h" +#include + +int main(int argc, char **argv) { + float update_freq; + if (argc < 4 || (sscanf(argv[3], "%f", &update_freq) != 1)) { + printf("Usage: %s \"\" \"\" \"\"\n", + argv[0]); + exit(1); + } + + alarm(0); + // Setup the event in sketchybar + char event_message[512]; + snprintf(event_message, 512, "--add event '%s'", argv[2]); + sketchybar(event_message); + + struct network network; + network_init(&network, argv[1]); + char trigger_message[512]; + for (;;) { + // Acquire new info + network_update(&network); + + // Prepare the event message + snprintf(trigger_message, 512, + "--trigger '%s' upload='%03d%s' download='%03d%s'", argv[2], + network.up, unit_str[network.up_unit], network.down, + unit_str[network.down_unit]); + + // Trigger the event + sketchybar(trigger_message); + + // Wait + usleep(update_freq * 1000000); + } + return 0; +} diff --git a/sketchybar/bridge/network_load/network_load.h b/sketchybar/bridge/network_load/network_load.h new file mode 100644 index 0000000..e3eba26 --- /dev/null +++ b/sketchybar/bridge/network_load/network_load.h @@ -0,0 +1,93 @@ +#include +#include +#include +#include +#include +#include +#include + +static char unit_str[3][6] = { + {" Bps"}, + {"KBps"}, + {"MBps"}, +}; + +enum unit { UNIT_BPS, UNIT_KBPS, UNIT_MBPS }; +struct network { + uint32_t row; + struct ifmibdata data; + struct timeval tv_nm1, tv_n, tv_delta; + + int up; + int down; + enum unit up_unit, down_unit; +}; + +static inline void ifdata(uint32_t net_row, struct ifmibdata *data) { + static size_t size = sizeof(struct ifmibdata); + static int32_t data_option[] = {CTL_NET, PF_LINK, NETLINK_GENERIC, + IFMIB_IFDATA, 0, IFDATA_GENERAL}; + data_option[4] = net_row; + sysctl(data_option, 6, data, &size, NULL, 0); +} + +static inline void network_init(struct network *net, char *ifname) { + memset(net, 0, sizeof(struct network)); + + static int count_option[] = {CTL_NET, PF_LINK, NETLINK_GENERIC, IFMIB_SYSTEM, + IFMIB_IFCOUNT}; + uint32_t interface_count = 0; + size_t size = sizeof(uint32_t); + sysctl(count_option, 5, &interface_count, &size, NULL, 0); + + for (int i = 0; i < interface_count; i++) { + ifdata(i, &net->data); + if (strcmp(net->data.ifmd_name, ifname) == 0) { + net->row = i; + break; + } + } +} + +static inline void network_update(struct network *net) { + gettimeofday(&net->tv_n, NULL); + timersub(&net->tv_n, &net->tv_nm1, &net->tv_delta); + net->tv_nm1 = net->tv_n; + + uint64_t ibytes_nm1 = net->data.ifmd_data.ifi_ibytes; + uint64_t obytes_nm1 = net->data.ifmd_data.ifi_obytes; + ifdata(net->row, &net->data); + + double time_scale = (net->tv_delta.tv_sec + 1e-6 * net->tv_delta.tv_usec); + if (time_scale < 1e-6 || time_scale > 1e2) + return; + double delta_ibytes = + (double)(net->data.ifmd_data.ifi_ibytes - ibytes_nm1) / time_scale; + double delta_obytes = + (double)(net->data.ifmd_data.ifi_obytes - obytes_nm1) / time_scale; + + double exponent_ibytes = log10(delta_ibytes); + double exponent_obytes = log10(delta_obytes); + + if (exponent_ibytes < 3) { + net->down_unit = UNIT_BPS; + net->down = delta_ibytes; + } else if (exponent_ibytes < 6) { + net->down_unit = UNIT_KBPS; + net->down = delta_ibytes / 1000.0; + } else if (exponent_ibytes < 9) { + net->down_unit = UNIT_MBPS; + net->down = delta_ibytes / 1000000.0; + } + + if (exponent_obytes < 3) { + net->up_unit = UNIT_BPS; + net->up = delta_obytes; + } else if (exponent_obytes < 6) { + net->up_unit = UNIT_KBPS; + net->up = delta_obytes / 1000.0; + } else if (exponent_obytes < 9) { + net->up_unit = UNIT_MBPS; + net->up = delta_obytes / 1000000.0; + } +} diff --git a/sketchybar/bridge/sketchybar.h b/sketchybar/bridge/sketchybar.h new file mode 100644 index 0000000..b194d8a --- /dev/null +++ b/sketchybar/bridge/sketchybar.h @@ -0,0 +1,124 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +typedef char *env; + +#define MACH_HANDLER(name) void name(env env) +typedef MACH_HANDLER(mach_handler); + +struct mach_message { + mach_msg_header_t header; + mach_msg_size_t msgh_descriptor_count; + mach_msg_ool_descriptor_t descriptor; +}; + +struct mach_buffer { + struct mach_message message; + mach_msg_trailer_t trailer; +}; + +static mach_port_t g_mach_port = 0; + +static inline mach_port_t mach_get_bs_port() { + mach_port_name_t task = mach_task_self(); + + mach_port_t bs_port; + if (task_get_special_port(task, TASK_BOOTSTRAP_PORT, &bs_port) != + KERN_SUCCESS) { + return 0; + } + + char *name = getenv("BAR_NAME"); + if (!name) + name = "sketchybar"; + uint32_t lookup_len = 16 + strlen(name); + + char buffer[lookup_len]; + snprintf(buffer, lookup_len, "git.felix.%s", name); + + mach_port_t port; + if (bootstrap_look_up(bs_port, buffer, &port) != KERN_SUCCESS) + return 0; + return port; +} + +static inline bool mach_send_message(mach_port_t port, char *message, + uint32_t len) { + if (!message || !port) { + return false; + } + + struct mach_message msg = {0}; + msg.header.msgh_remote_port = port; + msg.header.msgh_local_port = 0; + msg.header.msgh_id = 0; + msg.header.msgh_bits = + MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND, 0, + MACH_MSGH_BITS_COMPLEX); + + msg.header.msgh_size = sizeof(struct mach_message); + msg.msgh_descriptor_count = 1; + msg.descriptor.address = message; + msg.descriptor.size = len * sizeof(char); + msg.descriptor.copy = MACH_MSG_VIRTUAL_COPY; + msg.descriptor.deallocate = false; + msg.descriptor.type = MACH_MSG_OOL_DESCRIPTOR; + + kern_return_t err = + mach_msg(&msg.header, MACH_SEND_MSG, sizeof(struct mach_message), 0, + MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + + return err == KERN_SUCCESS; +} + +static inline uint32_t format_message(char *message, char *formatted_message) { + // This is not actually robust, switch to stack based messaging. + char outer_quote = 0; + uint32_t caret = 0; + uint32_t message_length = strlen(message) + 1; + for (int i = 0; i < message_length; ++i) { + if (message[i] == '"' || message[i] == '\'') { + if (outer_quote && outer_quote == message[i]) + outer_quote = 0; + else if (!outer_quote) + outer_quote = message[i]; + continue; + } + formatted_message[caret] = message[i]; + if (message[i] == ' ' && !outer_quote) + formatted_message[caret] = '\0'; + caret++; + } + + if (caret > 0 && formatted_message[caret] == '\0' && + formatted_message[caret - 1] == '\0') { + caret--; + } + formatted_message[caret] = '\0'; + return caret + 1; +} + +static inline void sketchybar(char *message) { + char formatted_message[strlen(message) + 2]; + uint32_t length = format_message(message, formatted_message); + if (!length) + return; + + if (!g_mach_port) + g_mach_port = mach_get_bs_port(); + if (!mach_send_message(g_mach_port, formatted_message, length)) { + g_mach_port = mach_get_bs_port(); + if (!mach_send_message(g_mach_port, formatted_message, length)) { + // No sketchybar instance running, exit. + exit(0); + } + } +} diff --git a/sketchybar/config/colors.lua b/sketchybar/config/colors.lua new file mode 100644 index 0000000..d052c4e --- /dev/null +++ b/sketchybar/config/colors.lua @@ -0,0 +1,38 @@ +local colors = { + black = 0xff1e1e2e, + white = 0xffcdd6f4, + red = 0xfff38ba8, + green = 0xffa6e3a1, + blue = 0xff89b4fa, + yellow = 0xfff9e2af, + orange = 0xfffab387, + magenta = 0xffcba6f7, + purple = 0xffb4befe, + other_purple = 0xfff5c2e7, + cyan = 0xff94e2d5, + grey = 0xff6c7086, + dirty_white = 0xffa6adc8, + dark_grey = 0xff585b70, + transparent = 0xff1e1e2e, + bar = { + bg = 0xff1e1e2e, + border = 0xff1e1e2e, + }, + popup = { + bg = 0xff1e1e2e, + border = 0xff2c2e34, + }, + slider = { + bg = 0xff1e1e2e, + border = 0xff2c2e34, + }, + bg1 = 0xff1e1e2e, + bg2 = 0xff302c45, + + with_alpha = function(color, alpha) + if alpha > 1.0 or alpha < 0.0 then return color end + return (color & 0x00ffffff) | (math.floor(alpha * 255.0) << 24) + end, +} + +return colors diff --git a/sketchybar/config/dimens.lua b/sketchybar/config/dimens.lua new file mode 100644 index 0000000..444c656 --- /dev/null +++ b/sketchybar/config/dimens.lua @@ -0,0 +1,40 @@ +local padding = { + background = 8, + icon = 10, + label = 8, + bar = 20, + left = 12, + right = 12, + item = 18, + popup = 8, +} + +local graphics = { + bar = { + height = 32, + offset = 6, + }, + background = { + height = 24, + corner_radius = 9, + }, + slider = { + height = 20, + }, + popup = { + width = 200, + large_width = 300, + }, + blur_radius = 30, +} + +local text = { + icon = 16.0, + label = 14.0, +} + +return { + padding = padding, + graphics = graphics, + text = text, +} diff --git a/sketchybar/config/fonts.lua b/sketchybar/config/fonts.lua new file mode 100644 index 0000000..5de8a44 --- /dev/null +++ b/sketchybar/config/fonts.lua @@ -0,0 +1,14 @@ +local dimens = require("config.dimens") + +return { + text = "Hack Nerd Font", + numbers = "Hack Nerd Font", + icons = function(size) + local font = "sketchybar-app-font:Regular" + return size and font .. ":" .. size or font .. ":" .. dimens.text.icon + end, + styles = { + regular = "Regular", + bold = "Bold", + } +} diff --git a/sketchybar/config/icons.lua b/sketchybar/config/icons.lua new file mode 100644 index 0000000..730acdd --- /dev/null +++ b/sketchybar/config/icons.lua @@ -0,0 +1,332 @@ +local apps = { + ["Live"] = ":ableton:", + ["Adobe Bridge 2024"] = ":adobe_bridge:", + ["Affinity Designer"] = ":affinity_designer:", + ["Affinity Designer 2"] = ":affinity_designer_2:", + ["Affinity Photo"] = ":affinity_photo:", + ["Affinity Photo 2"] = ":affinity_photo_2:", + ["Affinity Publisher"] = ":affinity_publisher:", + ["Affinity Publisher 2"] = ":affinity_publisher_2:", + ["Airmail"] = ":airmail:", + ["Alacritty"] = ":alacritty:", + ["Alfred Preferences"] = ":alfred:", + ["Android Messages"] = ":android_messages:", + ["Android Studio"] = ":android_studio:", + ["Anytype"] = ":anytype:", + ["App Eraser"] = ":app_eraser:", + ["App Store"] = ":app_store:", + ["Arc"] = ":arc:", + ["Atom"] = ":atom:", + ["Audacity"] = ":audacity:", + ["Bambu Studio"] = ":bambu_studio:", + ["MoneyMoney"] = ":bank:", + ["Bear"] = ":bear:", + ["BetterTouchTool"] = ":bettertouchtool:", + ["Bilibili"] = ":bilibili:", + ["哔哩哔哩"] = ":bilibili:", + ["Bitwarden"] = ":bit_warden:", + ["Blender"] = ":blender:", + ["BluOS Controller"] = ":bluos_controller:", + ["Calibre"] = ":book:", + ["Brave Browser"] = ":brave_browser:", + ["Calculator"] = ":calculator:", + ["Soulver 3"] = ":calculator:", + ["Calculette"] = ":calculator:", + ["Calendar"] = ":calendar:", + ["日历"] = ":calendar:", + ["Fantastical"] = ":calendar:", + ["Cron"] = ":calendar:", + ["Amie"] = ":calendar:", + ["Calendrier"] = ":calendar:", + ["Notion Calendar"] = ":calendar:", + ["Caprine"] = ":caprine:", + ["Citrix Workspace"] = ":citrix:", + ["Citrix Viewer"] = ":citrix:", + ["ClickUp"] = ":click_up:", + ["Code"] = ":code:", + ["Code - Insiders"] = ":code:", + ["Color Picker"] = ":color_picker:", + ["数码测色计"] = ":color_picker:", + ["CotEditor"] = ":coteditor:", + ["Cypress"] = ":cypress:", + ["DataGrip"] = ":datagrip:", + ["DataSpell"] = ":dataspell:", + ["DaVinci Resolve"] = ":davinciresolve:", + ["Default"] = ":default:", + ["CleanMyMac X"] = ":desktop:", + ["DEVONthink 3"] = ":devonthink3:", + ["DingTalk"] = ":dingtalk:", + ["钉钉"] = ":dingtalk:", + ["阿里钉"] = ":dingtalk:", + ["Discord"] = ":discord:", + ["Discord Canary"] = ":discord:", + ["Discord PTB"] = ":discord:", + ["Docker"] = ":docker:", + ["Docker Desktop"] = ":docker:", + ["GrandTotal"] = ":dollar:", + ["Receipts"] = ":dollar:", + ["Double Commander"] = ":doublecmd:", + ["Drafts"] = ":drafts:", + ["Dropbox"] = ":dropbox:", + ["Element"] = ":element:", + ["Emacs"] = ":emacs:", + ["Evernote Legacy"] = ":evernote_legacy:", + ["FaceTime"] = ":face_time:", + ["FaceTime 通话"] = ":face_time:", + ["Figma"] = ":figma:", + ["Final Cut Pro"] = ":final_cut_pro:", + ["Finder"] = ":finder:", + ["访达"] = ":finder:", + ["Firefox"] = ":firefox:", + ["Firefox Developer Edition"] = ":firefox_developer_edition:", + ["Firefox Nightly"] = ":firefox_developer_edition:", + ["Folx"] = ":folx:", + ["Fusion"] = ":fusion:", + ["System Preferences"] = ":gear:", + ["System Settings"] = ":gear:", + ["Systemeinstellungen"] = ":gear:", + ["系统设置"] = ":gear:", + ["Réglages Système"] = ":gear:", + ["GitHub Desktop"] = ":git_hub:", + ["Godot"] = ":godot:", + ["GoLand"] = ":goland:", + ["Chromium"] = ":google_chrome:", + ["Google Chrome"] = ":google_chrome:", + ["Google Chrome Canary"] = ":google_chrome:", + ["Grammarly Editor"] = ":grammarly:", + ["Home Assistant"] = ":home_assistant:", + ["Hyper"] = ":hyper:", + ["IntelliJ IDEA"] = ":idea:", + ["Inkdrop"] = ":inkdrop:", + ["Inkscape"] = ":inkscape:", + ["Insomnia"] = ":insomnia:", + ["Iris"] = ":iris:", + ["iTerm"] = ":iterm:", + ["iTerm2"] = ":iterm:", + ["Jellyfin Media Player"] = ":jellyfin:", + ["Joplin"] = ":joplin:", + ["카카오톡"] = ":kakaotalk:", + ["KakaoTalk"] = ":kakaotalk:", + ["Kakoune"] = ":kakoune:", + ["KeePassXC"] = ":kee_pass_x_c:", + ["Secrets"] = ":one_password:", + ["Keyboard Maestro"] = ":keyboard_maestro:", + ["Keynote"] = ":keynote:", + ["Keynote 讲演"] = ":keynote:", + ["kitty"] = ":kitty:", + ["League of Legends"] = ":league_of_legends:", + ["LibreWolf"] = ":libre_wolf:", + ["Adobe Lightroom"] = ":lightroom:", + ["Lightroom Classic"] = ":lightroomclassic:", + ["LINE"] = ":line:", + ["Linear"] = ":linear:", + ["LM Studio"] = ":lm_studio:", + ["LocalSend"] = ":localsend:", + ["Logic Pro"] = ":logicpro:", + ["Logseq"] = ":logseq:", + ["Canary Mail"] = ":mail:", + ["HEY"] = ":mail:", + ["Mail"] = ":mail:", + ["Mailspring"] = ":mail:", + ["MailMate"] = ":mail:", + ["Superhuman"] = ":mail:", + ["邮件"] = ":mail:", + ["MAMP"] = ":mamp:", + ["MAMP PRO"] = ":mamp:", + ["Maps"] = ":maps:", + ["Google Maps"] = ":maps:", + ["Matlab"] = ":matlab:", + ["Mattermost"] = ":mattermost:", + ["Messages"] = ":messages:", + ["信息"] = ":messages:", + ["Nachrichten"] = ":messages:", + ["Messenger"] = ":messenger:", + ["Microsoft Edge"] = ":microsoft_edge:", + ["Microsoft Excel"] = ":microsoft_excel:", + ["Microsoft Outlook"] = ":microsoft_outlook:", + ["Microsoft PowerPoint"] = ":microsoft_power_point:", + ["Microsoft Remote Desktop"] = ":microsoft_remote_desktop:", + ["Microsoft Teams"] = ":microsoft_teams:", + ["Microsoft Teams (work or school)"] = ":microsoft_teams:", + ["Microsoft Word"] = ":microsoft_word:", + ["Min"] = ":min_browser:", + ["Miro"] = ":miro:", + ["MongoDB Compass"] = ":mongodb:", + ["mpv"] = ":mpv:", + ["Mullvad Browser"] = ":mullvad_browser:", + ["Music"] = ":music:", + ["音乐"] = ":music:", + ["Musique"] = ":music:", + ["Neovide"] = ":neovide:", + ["neovide"] = ":neovide:", + ["Neovim"] = ":neovim:", + ["neovim"] = ":neovim:", + ["nvim"] = ":neovim:", + ["网易云音乐"] = ":netease_music:", + ["Noodl"] = ":noodl:", + ["Noodl Editor"] = ":noodl:", + ["NordVPN"] = ":nord_vpn:", + ["Notability"] = ":notability:", + ["Notes"] = ":notes:", + ["Notizen"] = ":notes:", + ["备忘录"] = ":notes:", + ["Notion"] = ":notion:", + ["Nova"] = ":nova:", + ["Numbers"] = ":numbers:", + ["Numbers 表格"] = ":numbers:", + ["Obsidian"] = ":obsidian:", + ["OBS"] = ":obsstudio:", + ["OmniFocus"] = ":omni_focus:", + ["1Password"] = ":one_password:", + ["ChatGPT"] = ":openai:", + ["OpenVPN Connect"] = ":openvpn_connect:", + ["Opera"] = ":opera:", + ["OrcaSlicer"] = ":orcaslicer:", + ["Orion"] = ":orion:", + ["Orion RC"] = ":orion:", + ["Pages"] = ":pages:", + ["Pages 文稿"] = ":pages:", + ["Parallels Desktop"] = ":parallels:", + ["Parsec"] = ":parsec:", + ["Preview"] = ":pdf:", + ["预览"] = ":pdf:", + ["Skim"] = ":pdf:", + ["zathura"] = ":pdf:", + ["Aperçu"] = ":pdf:", + ["PDF Expert"] = ":pdf_expert:", + ["Adobe Photoshop"] = ":photoshop:", + ["Pi-hole Remote"] = ":pihole:", + ["Pine"] = ":pine:", + ["Podcasts"] = ":podcasts:", + ["播客"] = ":podcasts:", + ["PomoDone App"] = ":pomodone:", + ["Postman"] = ":postman:", + ["PrusaSlicer"] = ":prusaslicer:", + ["SuperSlicer"] = ":prusaslicer:", + ["PyCharm"] = ":pycharm:", + ["QQ"] = ":qq:", + ["QQ音乐"] = ":qqmusic:", + ["QQMusic"] = ":qqmusic:", + ["Quantumult X"] = ":quantumult_x:", + ["qutebrowser"] = ":qute_browser:", + ["Raindrop.io"] = ":raindrop_io:", + ["Reeder"] = ":reeder5:", + ["Reminders"] = ":reminders:", + ["提醒事项"] = ":reminders:", + ["Rappels"] = ":reminders:", + ["Replit"] = ":replit:", + ["Rider"] = ":rider:", + ["JetBrains Rider"] = ":rider:", + ["Safari"] = ":safari:", + ["Safari浏览器"] = ":safari:", + ["Safari Technology Preview"] = ":safari:", + ["Sequel Ace"] = ":sequel_ace:", + ["Sequel Pro"] = ":sequel_pro:", + ["Setapp"] = ":setapp:", + ["SF Symbols"] = ":sf_symbols:", + ["Signal"] = ":signal:", + ["Sketch"] = ":sketch:", + ["Skype"] = ":skype:", + ["Slack"] = ":slack:", + ["Spark"] = ":spark:", + ["Spotify"] = ":spotify:", + ["Spotlight"] = ":spotlight:", + ["Sublime Text"] = ":sublime_text:", + ["Tana"] = ":tana:", + ["TeamSpeak 3"] = ":team_speak:", + ["Telegram"] = ":telegram:", + ["Terminal"] = ":terminal:", + ["终端"] = ":terminal:", + ["Typora"] = ":text:", + ["Microsoft To Do"] = ":things:", + ["Things"] = ":things:", + ["Thunderbird"] = ":thunderbird:", + ["TickTick"] = ":tick_tick:", + ["TIDAL"] = ":tidal:", + ["Tiny RDM"] = ":tinyrdm:", + ["Todoist"] = ":todoist:", + ["Toggl Track"] = ":toggl_track:", + ["Tor Browser"] = ":tor_browser:", + ["Tower"] = ":tower:", + ["Transmit"] = ":transmit:", + ["Trello"] = ":trello:", + ["Tweetbot"] = ":twitter:", + ["Twitter"] = ":twitter:", + ["MacVim"] = ":vim:", + ["Vim"] = ":vim:", + ["VimR"] = ":vim:", + ["Vivaldi"] = ":vivaldi:", + ["VLC"] = ":vlc:", + ["VMware Fusion"] = ":vmware_fusion:", + ["VSCodium"] = ":vscodium:", + ["Warp"] = ":warp:", + ["WebStorm"] = ":web_storm:", + ["微信"] = ":wechat:", + ["WeChat"] = ":wechat:", + ["企业微信"] = ":wecom:", + ["WeCom"] = ":wecom:", + ["WezTerm"] = ":wezterm:", + ["WhatsApp"] = ":whats_app:", + ["‎WhatsApp"] = ":whats_app:", + ["Xcode"] = ":xcode:", + ["Яндекс Музыка"] = ":yandex_music:", + ["Yuque"] = ":yuque:", + ["语雀"] = ":yuque:", + ["Zed"] = ":zed:", + ["Zeplin"] = ":zeplin:", + ["zoom.us"] = ":zoom:", + ["Zotero"] = ":zotero:", + ["Zulip"] = ":zulip:", + ["default"] = ":default:", + ["Ghostty"] = ":ghostty:" +} + +local text = { + nerdfont = { + plus = "", + loading = "", + apple = "", + gear = "", + cpu = "", + clipboard = "󰅇", + switch = { + on = "󱨥", + off = "󱨦", + }, + volume = { + _100 = "", + _66 = "", + _33 = "", + _10 = "", + _0 = "", + }, + battery = { + _100 = "", + _75 = "", + _50 = "", + _25 = "", + _0 = "", + charging = "", + }, + wifi = { + upload = "", + download = "", + connected = "󰖩", + disconnected = "󰖪", + router = "󰑩", + }, + media = { + back = "", + forward = "", + play_pause = "", + }, + slider = { + knob = "" + } + }, +} + +return { + text = text.nerdfont, + apps = apps, +} diff --git a/sketchybar/config/init.lua b/sketchybar/config/init.lua new file mode 100644 index 0000000..255488f --- /dev/null +++ b/sketchybar/config/init.lua @@ -0,0 +1 @@ +return require("config.settings") diff --git a/sketchybar/config/settings.lua b/sketchybar/config/settings.lua new file mode 100644 index 0000000..d03458c --- /dev/null +++ b/sketchybar/config/settings.lua @@ -0,0 +1,11 @@ +local colors = require("config.colors") +local fonts = require("config.fonts") +local icons = require("config.icons") +local dimens = require("config.dimens") + +return { + fonts = fonts, + dimens = dimens, + colors = colors, + icons = icons, +} diff --git a/sketchybar/constants.lua b/sketchybar/constants.lua new file mode 100644 index 0000000..ed52f12 --- /dev/null +++ b/sketchybar/constants.lua @@ -0,0 +1,34 @@ +local events = { + AEROSPACE_WORKSPACE_CHANGED = "aerospace_workspace_changed", + AEROSPACE_SWITCH = "aerospace_switch", + SWAP_MENU_AND_SPACES = "swap_menu_and_spaces", + FRONT_APP_SWITCHED = "front_app_switched", + UPDATE_WINDOWS = "update_windows", + SEND_MESSAGE = "send_message", + HIDE_MESSAGE = "hide_message", +} + +local items = { + SPACES = "workspaces", + MENU = "menu", + MENU_TOGGLE = "menu_toggle", + FRONT_APPS = "front_apps", + MESSAGE = "message", + VOLUME = "widgets.volume", + WIFI = "widgets.wifi", + BATTERY = "widgets.battery", + CALENDAR = "widgets.calendar", +} + +local aerospace = { + LIST_ALL_WORKSPACES = "aerospace list-workspaces --all", + GET_CURRENT_WORKSPACE = "aerospace list-workspaces --focused", + LIST_WINDOWS = "aerospace list-windows --workspace focused --format \"id=%{window-id}, name=%{app-name}\"", + GET_CURRENT_WINDOW = "aerospace list-windows --focused --format %{app-name}", +} + +return { + items = items, + events = events, + aerospace = aerospace, +} diff --git a/sketchybar/default.lua b/sketchybar/default.lua new file mode 100644 index 0000000..e480ee2 --- /dev/null +++ b/sketchybar/default.lua @@ -0,0 +1,67 @@ +local settings = require("config.settings") + +sbar.default({ + updates = "when_shown", + icon = { + font = { + family = settings.fonts.text, + style = settings.fonts.styles.regular, + size = settings.dimens.text.icon, + }, + color = settings.colors.purple, + padding_left = settings.dimens.padding.icon, + padding_right = settings.dimens.padding.icon, + }, + label = { + font = { + family = settings.fonts.text, + style = settings.fonts.styles.regular, + size = settings.dimens.text.label, + }, + color = settings.colors.purple, + padding_left = settings.dimens.padding.label, + padding_right = settings.dimens.padding.label, + }, + background = { + -- color = settings.colors.black, + height = settings.dimens.graphics.background.height, + corner_radius = settings.dimens.graphics.background.corner_radius, + border_width = 0, + image = { + corner_radius = settings.dimens.graphics.background.corner_radius + } + }, + popup = { + y_offset = settings.dimens.padding.popup, + align = "center", + background = { + border_width = 0, + corner_radius = settings.dimens.graphics.background.corner_radius, + color = settings.colors.popup.bg, + shadow = { drawing = true }, + padding_left = settings.dimens.padding.icon, + padding_right = settings.dimens.padding.icon, + }, + blur_radius = settings.dimens.graphics.blur_radius, + }, + slider = { + highlight_color = settings.colors.red, + background = { + height = settings.dimens.graphics.slider.height, + corner_radius = settings.dimens.graphics.background.corner_radius, + color = settings.colors.slider.bg, + border_color = settings.colors.slider.border, + border_width = 1, + }, + knob = { + font = { + family = settings.fonts.text, + style = settings.fonts.styles.regular, + size = 32, + }, + string = settings.icons.text.slider.knob, + drawing = false, + }, + }, + scroll_texts = true, +}) diff --git a/sketchybar/init.lua b/sketchybar/init.lua new file mode 100644 index 0000000..5dfa643 --- /dev/null +++ b/sketchybar/init.lua @@ -0,0 +1,15 @@ +require("install.sbar") + +sbar = require("sketchybar") + +sbar.begin_config() +sbar.hotload(true) + +require("constants") +require("config") +require("bar") +require("default") +require("items") + +sbar.end_config() +sbar.event_loop() diff --git a/sketchybar/install/init.lua b/sketchybar/install/init.lua new file mode 100644 index 0000000..bde1a41 --- /dev/null +++ b/sketchybar/install/init.lua @@ -0,0 +1 @@ +require("install.sbar") diff --git a/sketchybar/install/sbar.lua b/sketchybar/install/sbar.lua new file mode 100644 index 0000000..10e3074 --- /dev/null +++ b/sketchybar/install/sbar.lua @@ -0,0 +1,25 @@ +local sbarpath = "/Users/" .. os.getenv("USER") .. "/.local/share/sketchybar_lua/" + +local function exists(file) + local ok, err, code = os.rename(file, file) + if not ok then + if code == 13 then + return true + end + end + return ok, err +end + +local function isdir(path) + return exists(path .. "/") +end + +if not isdir(sbarpath) then + os.execute( + "git clone https://github.com/FelixKratz/SbarLua.git /tmp/SbarLua && cd /tmp/SbarLua && make install && rm -rf /tmp/SbarLua/" + ) +end + +package.cpath = package.cpath .. ";" .. sbarpath .. "?.so" + +os.execute("(cd bridge && make)") diff --git a/sketchybar/items/apple.lua b/sketchybar/items/apple.lua new file mode 100644 index 0000000..001f348 --- /dev/null +++ b/sketchybar/items/apple.lua @@ -0,0 +1,7 @@ +local settings = require("config.settings") + +local apple = sbar.add("item", "apple", { + icon = { string = settings.icons.text.apple }, + label = { drawing = false }, + click_script = "$CONFIG_DIR/items/menus/bin/menus -s 0" +}) diff --git a/sketchybar/items/front_apps.lua b/sketchybar/items/front_apps.lua new file mode 100644 index 0000000..0f3761e --- /dev/null +++ b/sketchybar/items/front_apps.lua @@ -0,0 +1,71 @@ +local constants = require("constants") +local settings = require("config.settings") + +local frontApps = {} + +sbar.add("bracket", constants.items.FRONT_APPS, {}, { position = "left" }) + +local frontAppWatcher = sbar.add("item", { + drawing = false, + updates = true, +}) + +local function selectFocusedWindow(frontAppName) + for appName, app in pairs(frontApps) do + local isSelected = appName == frontAppName + local color = isSelected and settings.colors.magenta or settings.colors.white + app:set( + { + label = { color = color }, + icon = { color = color }, + } + ) + end +end + +local function updateWindows(windows) + sbar.remove("/" .. constants.items.FRONT_APPS .. "\\.*/") + + frontApps = {} + local foundWindows = string.gmatch(windows, "[^\n]+") + for window in foundWindows do + local parsedWindow = {} + for key, value in string.gmatch(window, "(%w+)=([%w%s]+)") do + parsedWindow[key] = value + end + + local windowId = parsedWindow["id"] + local windowName = parsedWindow["name"] + local icon = settings.icons.apps[windowName] or settings.icons.apps["default"] + + frontApps[windowName] = sbar.add("item", constants.items.FRONT_APPS .. "." .. windowName, { + label = { + padding_left = 0, + string = windowName, + }, + icon = { + string = icon, + font = settings.fonts.icons(), + }, + click_script = "aerospace focus --window-id " .. windowId, + }) + + frontApps[windowName]:subscribe(constants.events.FRONT_APP_SWITCHED, function(env) + selectFocusedWindow(env.INFO) + end) + end + + sbar.exec(constants.aerospace.GET_CURRENT_WINDOW, function(frontAppName) + selectFocusedWindow(frontAppName:gsub("[\n\r]", "")) + end) +end + +local function getWindows() + sbar.exec(constants.aerospace.LIST_WINDOWS, updateWindows) +end + +frontAppWatcher:subscribe(constants.events.UPDATE_WINDOWS, function() + getWindows() +end) + +getWindows() diff --git a/sketchybar/items/init.lua b/sketchybar/items/init.lua new file mode 100644 index 0000000..91d96e7 --- /dev/null +++ b/sketchybar/items/init.lua @@ -0,0 +1,11 @@ +-- Left items +--require("items.apple") +require("items.menu_spaces_toggle") +require("items.menus") +require("items.spaces") +require("items.front_apps") + +-- Right items +require("items.message") +require("items.widgets") +--require("items.media") diff --git a/sketchybar/items/media.lua b/sketchybar/items/media.lua new file mode 100644 index 0000000..1d34084 --- /dev/null +++ b/sketchybar/items/media.lua @@ -0,0 +1,118 @@ +local colors = require("config.colors") + +local whitelist = { + ["Psst"] = true, +}; + +local media_cover = sbar.add("item", { + position = "left", + background = { + image = { + string = "media.artwork", + scale = 0.80, + }, + color = colors.transparent, + }, + label = { drawing = false }, + icon = { drawing = false }, + drawing = false, + updates = true, + popup = { + align = "center", + horizontal = true, + } +}) + +local media_artist = sbar.add("item", { + position = "left", + drawing = false, + padding_left = 3, + padding_right = 0, + width = 0, + icon = { drawing = false }, + label = { + width = 0, + font = { size = 9 }, + color = colors.with_alpha(colors.white, 0.6), + max_chars = 24, + y_offset = 6, + }, +}) + +local media_title = sbar.add("item", { + position = "left", + drawing = false, + padding_left = 3, + padding_right = 0, + icon = { drawing = false }, + label = { + font = { size = 11 }, + width = 0, + max_chars = 35, + y_offset = -5, + }, +}) + +sbar.add("item", { + position = "popup." .. media_cover.name, + icon = { string = icons.media.back }, + label = { drawing = false }, + click_script = "nowplaying-cli previous", +}) +sbar.add("item", { + position = "popup." .. media_cover.name, + icon = { string = icons.media.play_pause }, + label = { drawing = false }, + click_script = "nowplaying-cli togglePlayPause", +}) +sbar.add("item", { + position = "popup." .. media_cover.name, + icon = { string = icons.media.forward }, + label = { drawing = false }, + click_script = "nowplaying-cli next", +}) + +local interrupt = 0 +local function animate_detail(detail) + if (not detail) then interrupt = interrupt - 1 end + if interrupt > 0 and (not detail) then return end + + sbar.animate("tanh", 30, function() + media_artist:set({ label = { width = detail and "dynamic" or 0 } }) + media_title:set({ label = { width = detail and "dynamic" or 0 } }) + end) +end + +media_cover:subscribe("media_change", function(env) + if whitelist[env.INFO.app] then + local drawing = (env.INFO.state == "playing") + media_artist:set({ drawing = drawing, label = env.INFO.artist, }) + media_title:set({ drawing = drawing, label = env.INFO.title, }) + media_cover:set({ drawing = drawing }) + + if drawing then + animate_detail(true) + interrupt = interrupt + 1 + sbar.delay(5, animate_detail) + else + media_cover:set({ popup = { drawing = false } }) + end + end +end) + +media_cover:subscribe("mouse.entered", function(env) + interrupt = interrupt + 1 + animate_detail(true) +end) + +media_cover:subscribe("mouse.exited", function(env) + animate_detail(false) +end) + +media_cover:subscribe("mouse.clicked", function(env) + media_cover:set({ popup = { drawing = "toggle" } }) +end) + +media_title:subscribe("mouse.exited.global", function(env) + media_cover:set({ popup = { drawing = false } }) +end) diff --git a/sketchybar/items/menu_spaces_toggle.lua b/sketchybar/items/menu_spaces_toggle.lua new file mode 100644 index 0000000..c5c2240 --- /dev/null +++ b/sketchybar/items/menu_spaces_toggle.lua @@ -0,0 +1,71 @@ +local constants = require("constants") +local settings = require("config.settings") + +sbar.add("event", constants.events.SWAP_MENU_AND_SPACES) + +local function switchToggle(menuToggle) + local isShowingMenu = menuToggle:query().icon.value == settings.icons.text.switch.on + + menuToggle:set({ + icon = isShowingMenu and settings.icons.text.switch.off or settings.icons.text.switch.on, + label = isShowingMenu and "Menus" or "Spaces", + }) + + sbar.trigger(constants.events.SWAP_MENU_AND_SPACES, { isShowingMenu = isShowingMenu }) +end + +local function addToggle() + local menuToggle = sbar.add("item", constants.items.MENU_TOGGLE, { + icon = { + string = settings.icons.text.switch.on + }, + label = { + width = 0, + color = settings.colors.bg1, + string = "Spaces", + }, + background = { + color = settings.colors.with_alpha(settings.colors.purple, 0.0), + } + }) + + sbar.add("item", constants.items.MENU_TOGGLE .. ".padding", { + width = settings.dimens.padding.label + }) + + menuToggle:subscribe("mouse.entered", function(env) + sbar.animate("tanh", 30, function() + menuToggle:set({ + background = { + color = { alpha = 1.0 }, + border_color = { alpha = 0.5 }, + }, + icon = { color = settings.colors.bg1 }, + label = { width = "dynamic" } + }) + end) + end) + + menuToggle:subscribe("mouse.exited", function(env) + sbar.animate("tanh", 30, function() + menuToggle:set({ + background = { + color = { alpha = 0.0 }, + border_color = { alpha = 0.0 }, + }, + icon = { color = settings.colors.red }, + label = { width = 0 } + }) + end) + end) + + menuToggle:subscribe("mouse.clicked", function(env) + switchToggle(menuToggle) + end) + + menuToggle:subscribe(constants.events.AEROSPACE_SWITCH, function(env) + switchToggle(menuToggle) + end) +end + +addToggle() diff --git a/sketchybar/items/menus.lua b/sketchybar/items/menus.lua new file mode 100644 index 0000000..674963a --- /dev/null +++ b/sketchybar/items/menus.lua @@ -0,0 +1,74 @@ +local constants = require("constants") +local settings = require("config.settings") + +local maxItems = 15 +local menuItems = {} +local isShowingMenu = false + +local frontAppWatcher = sbar.add("item", { + drawing = false, + updates = true, +}) + +local swapWatcher = sbar.add("item", { + drawing = false, + updates = true, +}) + +local function createPlaceholders() + for index = 1, maxItems, 1 do + local menu = sbar.add("item", constants.items.MENU .. "." .. index, { + drawing = false, + icon = { drawing = false }, + width = "dynamic", + label = { + font = { + style = index == 1 and settings.fonts.styles.bold or settings.fonts.styles.regular, + }, + }, + click_script = "$CONFIG_DIR/bridge/menus/bin/menus -s " .. index, + }) + menuItems[index] = menu + end + + sbar.add("bracket", { "/" .. constants.items.MENU .. "\\..*/" }, { + background = { + color = settings.colors.bg1, + padding_left = settings.dimens.padding.item, + padding_right = settings.dimens.padding.item, + }, + }) +end + +local function updateMenus() + sbar.set("/" .. constants.items.MENU .. "\\..*/", { drawing = false }) + + sbar.exec("$CONFIG_DIR/bridge/menus/bin/menus -l", function(menus) + local index = 1 + for menu in string.gmatch(menus, '[^\r\n]+') do + if index < maxItems then + menuItems[index]:set( + { + width = "dynamic", + label = menu, + drawing = isShowingMenu + } + ) + else + break + end + index = index + 1 + end + end) + + sbar.set(constants.items.MENU .. ".padding", { drawing = isShowingMenu }) +end + +frontAppWatcher:subscribe(constants.events.FRONT_APP_SWITCHED, updateMenus) + +swapWatcher:subscribe(constants.events.SWAP_MENU_AND_SPACES, function(env) + isShowingMenu = env.isShowingMenu == "on" + updateMenus() +end) + +createPlaceholders() diff --git a/sketchybar/items/menus/bin/menus b/sketchybar/items/menus/bin/menus new file mode 100755 index 0000000000000000000000000000000000000000..4d927446236bef52175c30455dc218127dbe5728 GIT binary patch literal 36920 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 AG5`Po literal 0 HcmV?d00001 diff --git a/sketchybar/items/message.lua b/sketchybar/items/message.lua new file mode 100644 index 0000000..1be586a --- /dev/null +++ b/sketchybar/items/message.lua @@ -0,0 +1,55 @@ +local constants = require("constants") +local settings = require("config.settings") + +local message = sbar.add("item", constants.items.MESSAGE, { + width = 0, + position = "center", + popup = { align = "center" }, + label = { + padding_left = 0, + padding_right = 0, + }, + background = { + padding_left = 0, + padding_right = 0, + } +}) + +local messagePopup = sbar.add("item", { + position = "popup." .. message.name, + width = "dynamic", + label = { + padding_right = settings.dimens.padding.label, + padding_left = settings.dimens.padding.label, + }, + icon = { + padding_left = 0, + padding_right = 0, + }, +}) + +local function hideMessage() + message:set({ popup = { drawing = false } }) +end + +local function showMessage(content, hold) + hideMessage() + + message:set({ popup = { drawing = true } }) + messagePopup:set({ label = { string = content } }) + + if hold == false then + sbar.delay(5, function() + if hold then return end + hideMessage() + end) + end +end + +message:subscribe(constants.events.SEND_MESSAGE, function(env) + local content = env.MESSAGE + local hold = env.HOLD ~= nil and env.HOLD == "true" or false + showMessage(content, hold) +end) + +message:subscribe(constants.events.HIDE_MESSAGE, hideMessage) diff --git a/sketchybar/items/spaces.lua b/sketchybar/items/spaces.lua new file mode 100644 index 0000000..b1f9d2b --- /dev/null +++ b/sketchybar/items/spaces.lua @@ -0,0 +1,108 @@ +local constants = require("constants") +local settings = require("config.settings") + +local spaces = {} + +local swapWatcher = sbar.add("item", { + drawing = false, + updates = true, +}) + +local currentWorkspaceWatcher = sbar.add("item", { + drawing = false, + updates = true, +}) + +-- Modify this file with Visual Studio Code - at least vim does have problems with the icons +-- copy "Icons" from the nerd fonts cheat sheet and replace icon and name accordingly below +-- https://www.nerdfonts.com/cheat-sheet +local spaceConfigs = { + ["1"] = { icon = "", name = "Main" }, + ["2"] = { icon = "", name = "Terminal" }, + ["3"] = { icon = "", name = "Code" }, + ["4"] = { icon = "󰘳", name = "Notes" }, + ["5"] = { icon = "", name = "Security" }, + ["D"] = { icon = "", name = "Discord" }, + ["S"] = { icon = "", name = "Music"}, +} + +local function selectCurrentWorkspace(focusedWorkspaceName) + for sid, item in pairs(spaces) do + if item ~= nil then + local isSelected = sid == constants.items.SPACES .. "." .. focusedWorkspaceName + item:set({ + icon = { color = isSelected and settings.colors.bg1 or settings.colors.bg1 }, + label = { color = isSelected and settings.colors.bg1 or settings.colors.bg1 }, + background = { color = isSelected and settings.colors.magenta or settings.colors.purple }, + }) + end + end + + sbar.trigger(constants.events.UPDATE_WINDOWS) +end + +local function findAndSelectCurrentWorkspace() + sbar.exec(constants.aerospace.GET_CURRENT_WORKSPACE, function(focusedWorkspaceOutput) + local focusedWorkspaceName = focusedWorkspaceOutput:match("[^\r\n]+") + selectCurrentWorkspace(focusedWorkspaceName) + end) +end + +local function addWorkspaceItem(workspaceName) + local spaceName = constants.items.SPACES .. "." .. workspaceName + local spaceConfig = spaceConfigs[workspaceName] + + spaces[spaceName] = sbar.add("item", spaceName, { + label = { + width = 0, + padding_left = 0, + string = spaceConfig.name, + }, + icon = { + string = spaceConfig.icon or settings.icons.apps["default"], + color = settings.colors.white, + }, + background = { + color = settings.colors.bg1, + }, + click_script = "aerospace workspace " .. workspaceName, + }) + + spaces[spaceName]:subscribe("mouse.entered", function(env) + sbar.animate("tanh", 30, function() + spaces[spaceName]:set({ label = { width = "dynamic" } }) + end) + end) + + spaces[spaceName]:subscribe("mouse.exited", function(env) + sbar.animate("tanh", 30, function() + spaces[spaceName]:set({ label = { width = 0 } }) + end) + end) + + sbar.add("item", spaceName .. ".padding", { + width = settings.dimens.padding.label + }) +end + +local function createWorkspaces() + sbar.exec(constants.aerospace.LIST_ALL_WORKSPACES, function(workspacesOutput) + for workspaceName in workspacesOutput:gmatch("[^\r\n]+") do + addWorkspaceItem(workspaceName) + end + + findAndSelectCurrentWorkspace() + end) +end + +swapWatcher:subscribe(constants.events.SWAP_MENU_AND_SPACES, function(env) + local isShowingSpaces = env.isShowingMenu == "off" and true or false + sbar.set("/" .. constants.items.SPACES .. "\\..*/", { drawing = isShowingSpaces }) +end) + +currentWorkspaceWatcher:subscribe(constants.events.AEROSPACE_WORKSPACE_CHANGED, function(env) + selectCurrentWorkspace(env.FOCUSED_WORKSPACE) + sbar.trigger(constants.events.UPDATE_WINDOWS) +end) + +createWorkspaces() diff --git a/sketchybar/items/widgets/battery.lua b/sketchybar/items/widgets/battery.lua new file mode 100644 index 0000000..32546f0 --- /dev/null +++ b/sketchybar/items/widgets/battery.lua @@ -0,0 +1,91 @@ +local constants = require("constants") +local settings = require("config.settings") + +local isCharging = false + +local battery = sbar.add("item", constants.items.battery, { + position = "right", + update_freq = 60, +}) + +local batteryPopup = sbar.add("item", { + position = "popup." .. battery.name, + width = "dynamic", + label = { + padding_right = settings.dimens.padding.label, + padding_left = settings.dimens.padding.label, + }, + icon = { + padding_left = 0, + padding_right = 0, + }, +}) + +battery:subscribe({ "routine", "power_source_change", "system_woke" }, function() + sbar.exec("pmset -g batt", function(batteryInfo) + local icon = "!" + local label = "?" + + local found, _, charge = batteryInfo:find("(%d+)%%") + if found then + charge = tonumber(charge) + label = charge .. "%" + end + + local color = settings.colors.green + local charging, _, _ = batteryInfo:find("AC Power") + + isCharging = charging + + if charging then + icon = settings.icons.text.battery.charging + else + if found and charge > 80 then + icon = settings.icons.text.battery._100 + elseif found and charge > 60 then + icon = settings.icons.text.battery._75 + elseif found and charge > 40 then + icon = settings.icons.text.battery._50 + elseif found and charge > 30 then + icon = settings.icons.text.battery._50 + color = settings.colors.yellow + elseif found and charge > 20 then + icon = settings.icons.text.battery._25 + color = settings.colors.orange + else + icon = settings.icons.text.battery._0 + color = settings.colors.red + end + end + + local lead = "" + if found and charge < 10 then + lead = "0" + end + + battery:set({ + icon = { + string = icon, + color = color + }, + label = { + string = lead .. label, + padding_left = 0, + }, + }) + end) +end) + +battery:subscribe("mouse.clicked", function(env) + local drawing = battery:query().popup.drawing + + battery:set({ popup = { drawing = "toggle" } }) + + if drawing == "off" then + sbar.exec("pmset -g batt", function(batteryInfo) + local found, _, remaining = batteryInfo:find("(%d+:%d+) remaining") + local label = found and ("Time remaining: " .. remaining .. "h") or (isCharging and "Charging" or "No estimate") + batteryPopup:set({ label = label }) + end) + end +end) diff --git a/sketchybar/items/widgets/calendar.lua b/sketchybar/items/widgets/calendar.lua new file mode 100644 index 0000000..436591d --- /dev/null +++ b/sketchybar/items/widgets/calendar.lua @@ -0,0 +1,17 @@ +local constants = require("constants") + +local calendar = sbar.add("item", constants.items.CALENDAR, { + position = "right", + update_freq = 1, + icon = { padding_left = 0, padding_right = 0 } +}) + +calendar:subscribe({ "forced", "routine", "system_woke" }, function(env) + calendar:set({ + label = os.date("%a %d %b, %H:%M"), + }) +end) + +calendar:subscribe("mouse.clicked", function(env) + sbar.exec("open -a 'Calendar'") +end) diff --git a/sketchybar/items/widgets/init.lua b/sketchybar/items/widgets/init.lua new file mode 100644 index 0000000..8e9631c --- /dev/null +++ b/sketchybar/items/widgets/init.lua @@ -0,0 +1,4 @@ +require("items.widgets.calendar") +require("items.widgets.battery") +require("items.widgets.volume") +require("items.widgets.wifi") diff --git a/sketchybar/items/widgets/volume.lua b/sketchybar/items/widgets/volume.lua new file mode 100644 index 0000000..7a24847 --- /dev/null +++ b/sketchybar/items/widgets/volume.lua @@ -0,0 +1,128 @@ +local constants = require("constants") +local settings = require("config.settings") + +local currentAudioDevice = "None" + +local volumeValue = sbar.add("item", constants.items.VOLUME .. ".value", { + position = "right", + label = { + string = "??%", + padding_left = 0, + }, +}) + +local volumeBracket = sbar.add("bracket", constants.items.VOLUME .. ".bracket", { volumeValue.name }, { + popup = { + align = "center" + }, +}) + +local volumeSlider = sbar.add("slider", constants.items.VOLUME .. ".slider", settings.dimens.graphics.popup.width, { + position = "popup." .. volumeBracket.name, + click_script = 'osascript -e "set volume output volume $PERCENTAGE"' +}) + +volumeValue:subscribe("volume_change", function(env) + local icon = settings.icons.text.volume._0 + local volume = tonumber(env.INFO) + + sbar.exec("SwitchAudioSource -t output -c", function(result) + -- local currentOutputDevice = result:sub(1, -2) + -- if currentOutputDevice == "AirPods Max" then + -- icon = "􀺹" + -- elseif currentOutputDevice == "AirPods von Longdong Silver" or currentOutputDevice == "AirPods von Anna" then + -- icon = "􀟥" + -- elseif currentOutputDevice == "Arctis Nova Pro Wireless" then + -- icon = "􀑈" + -- elseif currentOutputDevice == "Ear (2)" then + -- icon = "􀪷" + -- elseif currentOutputDevice == "iD4" then + -- icon = "􀝎" + -- else + if volume > 60 then + icon = settings.icons.text.volume._100 + elseif volume > 30 then + icon = settings.icons.text.volume._66 + elseif volume > 10 then + icon = settings.icons.text.volume._33 + elseif volume > 0 then + icon = settings.icons.text.volume._10 + end + -- end + + local lead = "" + if volume < 10 then + lead = "0" + end + + -- volumeIcon:set({ label = icon }) + volumeSlider:set({ slider = { percentage = volume } }) + + local hasVolume = volume ~= 0 + volumeValue:set({ + icon = icon, + label = { + string = hasVolume and lead .. volume .. "%" or "", + padding_right = hasVolume and 8 or 0, + }, + }) + end) +end) + +local function hideVolumeDetails() + local drawing = volumeBracket:query().popup.drawing == "on" + if not drawing then return end + volumeBracket:set({ popup = { drawing = false } }) + sbar.remove("/" .. constants.items.VOLUME .. ".device\\.*/") +end + +local function toggleVolumeDetails(env) + if env.BUTTON == "right" then + sbar.exec("open /System/Library/PreferencePanes/Sound.prefpane") + return + end + + local shouldDraw = volumeBracket:query().popup.drawing == "off" + if shouldDraw then + volumeBracket:set({ popup = { drawing = true } }) + + sbar.exec("SwitchAudioSource -t output -c", function(result) + currentAudioDevice = result:sub(1, -2) + + sbar.exec("SwitchAudioSource -a -t output", function(available) + local current = currentAudioDevice + local counter = 0 + + for device in string.gmatch(available, '[^\r\n]+') do + local color = settings.colors.grey + if current == device then + color = settings.colors.white + end + + sbar.add("item", constants.items.VOLUME .. ".device." .. counter, { + position = "popup." .. volumeBracket.name, + align = "center", + label = { string = device, color = color }, + click_script = 'SwitchAudioSource -s "' .. + device .. + '" && sketchybar --set /' .. constants.items.VOLUME .. '.device\\.*/ label.color=' .. + settings.colors.grey .. ' --set $NAME label.color=' .. settings.colors.white + + }) + counter = counter + 1 + end + end) + end) + else + hideVolumeDetails() + end +end + +local function changeVolume(env) + local delta = env.SCROLL_DELTA + sbar.exec('osascript -e "set volume output volume (output volume of (get volume settings) + ' .. delta .. ')"') +end + +volumeValue:subscribe("mouse.clicked", toggleVolumeDetails) +volumeValue:subscribe("mouse.scrolled", changeVolume) +-- volumeValue:subscribe("mouse.exited.global", hideVolumeDetails) diff --git a/sketchybar/items/widgets/wifi.lua b/sketchybar/items/widgets/wifi.lua new file mode 100644 index 0000000..3cd7c5c --- /dev/null +++ b/sketchybar/items/widgets/wifi.lua @@ -0,0 +1,261 @@ +local constants = require("constants") +local settings = require("config.settings") + +local popupWidth = settings.dimens.graphics.popup.width + 20 + +sbar.exec( + "killall network_load >/dev/null; $CONFIG_DIR/bridge/network_load/bin/network_load en0 network_update 2.0" +) + +local wifiUp = sbar.add("item", constants.items.WIFI .. ".up", { + position = "right", + width = 0, + icon = { + padding_left = 0, + padding_right = 0, + font = { + style = settings.fonts.styles.bold, + size = 10.0, + }, + string = settings.icons.text.wifi.upload, + }, + label = { + font = { + family = settings.fonts.numbers, + style = settings.fonts.styles.bold, + size = 10.0, + }, + color = settings.colors.orange, + string = "??? Bps", + }, + y_offset = 4, +}) + +local wifiDown = sbar.add("item", constants.items.WIFI .. ".down", { + position = "right", + icon = { + padding_left = 0, + padding_right = 0, + font = { + style = settings.fonts.styles.bold, + size = 10.0, + }, + string = settings.icons.text.wifi.download, + }, + label = { + font = { + family = settings.fonts.numbers, + style = settings.fonts.styles.bold, + size = 10, + }, + color = settings.colors.blue, + string = "??? Bps", + }, + y_offset = -4, +}) + +local wifi = sbar.add("item", constants.items.WIFI .. ".padding", { + position = "right", + label = { drawing = false }, + padding_right = 0, +}) + +local wifiBracket = sbar.add("bracket", constants.items.WIFI .. ".bracket", { + wifi.name, + wifiUp.name, + wifiDown.name +}, { + popup = { align = "center" } +}) + +local ssid = sbar.add("item", { + align = "center", + position = "popup." .. wifiBracket.name, + width = popupWidth, + height = 16, + icon = { + string = settings.icons.text.wifi.router, + font = { + style = settings.fonts.styles.bold + }, + }, + label = { + font = { + style = settings.fonts.styles.bold, + size = settings.dimens.text.label, + }, + max_chars = 18, + string = "????????????", + }, +}) + +local hostname = sbar.add("item", { + position = "popup." .. wifiBracket.name, + background = { + height = 16, + }, + icon = { + align = "left", + string = "Hostname:", + width = popupWidth / 2, + font = { + size = settings.dimens.text.label + }, + }, + label = { + max_chars = 20, + string = "????????????", + width = popupWidth / 2, + align = "right", + } +}) + +local ip = sbar.add("item", { + position = "popup." .. wifiBracket.name, + background = { + height = 16, + }, + icon = { + align = "left", + string = "IP:", + width = popupWidth / 2, + font = { + size = settings.dimens.text.label + }, + }, + label = { + align = "right", + string = "???.???.???.???", + width = popupWidth / 2, + } +}) + +local router = sbar.add("item", { + position = "popup." .. wifiBracket.name, + background = { + height = 16, + }, + icon = { + align = "left", + string = "Router:", + width = popupWidth / 2, + font = { + size = settings.dimens.text.label + }, + }, + label = { + align = "right", + string = "???.???.???.???", + width = popupWidth / 2, + }, +}) + +sbar.add("item", { position = "right", width = settings.dimens.padding.item }) + +wifiUp:subscribe("network_update", function(env) + local upColor = (env.upload == "000 Bps") and settings.colors.grey or settings.colors.orange + local downColor = (env.download == "000 Bps") and settings.colors.grey or settings.colors.blue + + wifiUp:set({ + icon = { color = upColor }, + label = { + string = env.upload, + color = upColor + } + }) + wifiDown:set({ + icon = { color = downColor }, + label = { + string = env.download, + color = downColor + } + }) +end) + +wifi:subscribe({ "wifi_change", "system_woke", "forced" }, function(env) + wifi:set({ + icon = { + string = settings.icons.text.wifi.disconnected, + color = settings.colors.magenta, + } + }) + + sbar.exec([[ipconfig getifaddr en0]], function(ip) + local ipConnected = not (ip == "") + + local wifiIcon + local wifiColor + + if ipConnected then + wifiIcon = settings.icons.text.wifi.connected + wifiColor = settings.colors.magenta + end + + wifi:set({ + icon = { + string = wifiIcon, + color = wifiColor, + } + }) + + sbar.exec([[sleep 2; scutil --nwi | grep -m1 'utun' | awk '{ print $1 }']], function(vpn) + local isVPNConnected = not (vpn == "") + + if isVPNConnected then + wifiIcon = settings.icons.text.wifi.vpn + wifiColor = settings.colors.green + end + + wifi:set({ + icon = { + string = wifiIcon, + color = wifiColor, + } + }) + end) + end) +end) + +local function hideDetails() + wifiBracket:set({ popup = { drawing = false } }) +end + +local function toggleDetails() + local shouldDrawDetails = wifiBracket:query().popup.drawing == "off" + + if shouldDrawDetails then + wifiBracket:set({ popup = { drawing = true } }) + sbar.exec("networksetup -getcomputername", function(result) + hostname:set({ label = result }) + end) + sbar.exec("ipconfig getifaddr en0", function(result) + ip:set({ label = result }) + end) + sbar.exec("ipconfig getsummary en0 | awk -F ' SSID : ' '/ SSID : / {print $2}'", function(result) + ssid:set({ label = result }) + end) + sbar.exec("networksetup -getinfo Wi-Fi | awk -F 'Router: ' '/^Router: / {print $2}'", function(result) + router:set({ label = result }) + end) + else + hideDetails() + end +end + +local function copyLabelToClipboard(env) + local label = sbar.query(env.NAME).label.value + sbar.exec("echo \"" .. label .. "\" | pbcopy") + sbar.set(env.NAME, { label = { string = settings.icons.text.clipboard, align = "center" } }) + sbar.delay(1, function() + sbar.set(env.NAME, { label = { string = label, align = "right" } }) + end) +end + +wifiUp:subscribe("mouse.clicked", toggleDetails) +wifiDown:subscribe("mouse.clicked", toggleDetails) +wifi:subscribe("mouse.clicked", toggleDetails) + +ssid:subscribe("mouse.clicked", copyLabelToClipboard) +hostname:subscribe("mouse.clicked", copyLabelToClipboard) +ip:subscribe("mouse.clicked", copyLabelToClipboard) +router:subscribe("mouse.clicked", copyLabelToClipboard) diff --git a/sketchybar/sketchybarrc b/sketchybar/sketchybarrc new file mode 100755 index 0000000..d9f6e17 --- /dev/null +++ b/sketchybar/sketchybarrc @@ -0,0 +1,3 @@ +#!/usr/bin/env lua + +require("init") diff --git a/sketchybar/util/.gitkeep b/sketchybar/util/.gitkeep new file mode 100644 index 0000000..e69de29