diff --git a/darwin/aerospace/default.nix b/darwin/aerospace/default.nix new file mode 100644 index 0000000..14fd153 --- /dev/null +++ b/darwin/aerospace/default.nix @@ -0,0 +1,144 @@ +{ + pkgs, + lib, + config, + ... +}: let + inherit (lib) mkEnableOption mkIf; + cfg = config.my.aerospace; +in { + options.my.aerospace = { + enable = mkEnableOption "aerospace"; + }; + + config = mkIf cfg.enable { + services.aerospace = { + enable = true; + package = pkgs.unstable.aerospace; + settings = { + # Startup configuration + after-login-command = []; + after-startup-command = []; + + # Layout normalization + enable-normalization-flatten-containers = true; + enable-normalization-opposite-orientation-for-nested-containers = true; + + # Layout settings + accordion-padding = 30; + default-root-container-layout = "tiles"; + default-root-container-orientation = "auto"; + + # Mouse behavior + on-focused-monitor-changed = ["move-mouse monitor-lazy-center"]; + automatically-unhide-macos-hidden-apps = false; + + # Key mapping + key-mapping = { + preset = "qwerty"; + }; + + # Window gaps + gaps = { + inner = { + horizontal = 2; + vertical = 2; + }; + outer = { + left = 0; + bottom = 0; + top = [ + {monitor."built-in" = 2;} + 35 + ]; + right = 0; + }; + }; + + # Main mode bindings + mode.main.binding = { + # Layout commands + "alt-slash" = "layout tiles horizontal vertical"; + "alt-comma" = "layout accordion horizontal vertical"; + + # Focus commands + "alt-h" = "focus left"; + "alt-j" = "focus down"; + "alt-k" = "focus up"; + "alt-l" = "focus right"; + + # Move commands + "alt-shift-h" = "move left"; + "alt-shift-j" = "move down"; + "alt-shift-k" = "move up"; + "alt-shift-l" = "move right"; + + # Resize commands + "alt-minus" = "resize smart -50"; + "alt-equal" = "resize smart +50"; + + # Misc window commands + "alt-q" = "close"; + + # Workspace commands + "alt-1" = "workspace 1"; + "alt-2" = "workspace 2"; + "alt-3" = "workspace 3"; + "alt-4" = "workspace 4"; + "alt-5" = "workspace 5"; + "alt-6" = "workspace 6"; + "alt-7" = "workspace 7"; + "alt-8" = "workspace 8"; + "alt-9" = "workspace 9"; + + # Move node to workspace commands + "alt-shift-1" = "move-node-to-workspace 1"; + "alt-shift-2" = "move-node-to-workspace 2"; + "alt-shift-3" = "move-node-to-workspace 3"; + "alt-shift-4" = "move-node-to-workspace 4"; + "alt-shift-5" = "move-node-to-workspace 5"; + "alt-shift-6" = "move-node-to-workspace 6"; + "alt-shift-7" = "move-node-to-workspace 7"; + "alt-shift-8" = "move-node-to-workspace 8"; + "alt-shift-9" = "move-node-to-workspace 9"; + + # Applications + "alt-t" = '' + exec-and-forget osascript -e ' + tell application "Ghostty" + if it is running then + activate + tell application "System Events" to keystroke "n" using {command down} + else + activate + end if + end tell' + ''; + "alt-f" = "exec-and-forget open -n '/Applications/Zen Browser.app'"; + + # Other commands + # "alt-tab" = "workspace-back-and-forth"; + # "alt-shift-tab" = "move-workspace-to-monitor --wrap-around next"; + "alt-shift-semicolon" = "mode service"; + }; + + # Service mode bindings + mode.service.binding = { + "esc" = ["reload-config" "mode main"]; + "r" = ["flatten-workspace-tree" "mode main"]; + "f" = ["layout floating tiling" "mode main"]; + "backspace" = ["close-all-windows-but-current" "mode main"]; + + "alt-shift-h" = ["join-with left" "mode main"]; + "alt-shift-j" = ["join-with down" "mode main"]; + "alt-shift-k" = ["join-with up" "mode main"]; + "alt-shift-l" = ["join-with right" "mode main"]; + + "down" = "volume down"; + "up" = "volume up"; + "shift-down" = ["volume set 0" "mode main"]; + }; + }; + }; + }; +} diff --git a/darwin/sketchybar/config/.luarc.json b/darwin/sketchybar/config/.luarc.json new file mode 100644 index 0000000..dffff8a --- /dev/null +++ b/darwin/sketchybar/config/.luarc.json @@ -0,0 +1,3 @@ +{ + "runtime.version": "Lua 5.4" +} diff --git a/darwin/sketchybar/config/bar.lua b/darwin/sketchybar/config/bar.lua new file mode 100644 index 0000000..12915e2 --- /dev/null +++ b/darwin/sketchybar/config/bar.lua @@ -0,0 +1,16 @@ +local colors = require("colors").sections.bar + +sbar.bar { + topmost = "window", + height = 34, + notch_display_height = 34, + padding_right = 12, + padding_left = 12, + margin = -1, + corner_radius = 0, + y_offset = -1, + blur_radius = 20, + border_color = colors.border, + border_width = 1, + color = colors.bg, +} diff --git a/darwin/sketchybar/config/colors.lua b/darwin/sketchybar/config/colors.lua new file mode 100644 index 0000000..2a4a444 --- /dev/null +++ b/darwin/sketchybar/config/colors.lua @@ -0,0 +1,99 @@ +local M = {} + +local 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 + +local transparent = 0x00000000 +local black = 0xFF000000 + +local gruvbox = { + rosewater = 0xFFd4be98, -- Gruvbox light4 (closest to gruvbox rosewater) + flamingo = 0xFFea6962, -- Gruvbox bright_red (closest to gruvbox flamingo) + pink = 0xFFd3869b, -- Gruvbox bright_purple (closest to gruvbox pink) + mauve = 0xFFd3869b, -- Gruvbox bright_purple (closest to gruvbox mauve) + red = 0xFFcc241d, -- Gruvbox dark_red + maroon = 0xFFfb4934, -- Gruvbox bright_red (closest to gruvbox maroon) + peach = 0xFFfe8019, -- Gruvbox bright_orange (closest to gruvbox peach) + yellow = 0xFFd79921, -- Gruvbox dark_yellow + green = 0xFFb8bb26, -- Gruvbox bright_green (closest to gruvbox green) + teal = 0xFF8ec07c, -- Gruvbox bright_aqua (closest to gruvbox teal) + sky = 0xFF83a598, -- Gruvbox bright_blue (closest to gruvbox sky) + sapphire = 0xFF83a598, -- Gruvbox bright_blue (closest to gruvbox sapphire) + blue = 0xFF458588, -- Gruvbox dark_blue + lavender = 0xFF83a598, -- Gruvbox bright_blue (closest to gruvbox lavender) + text = 0xFFebdbb2, -- Gruvbox light0 + subtext1 = 0xFFd5c4a1, -- Gruvbox light2 + subtext0 = 0xFFbdae93, -- Gruvbox light3 + overlay2 = 0xFFa89984, -- Gruvbox light4 + overlay1 = 0xFF928374, -- Gruvbox gray + overlay0 = 0xFF665c54, -- Gruvbox dark4 + surface2 = 0xFF504945, -- Gruvbox dark3 + surface1 = 0xFF3c3836, -- Gruvbox dark2 + surface0 = 0xFF32302f, -- Gruvbox dark1 + base = 0xFF282828, -- Gruvbox dark0 + mantle = 0xFF1d2021, -- Gruvbox dark0_hard + crust = 0xFF1d2021, -- Gruvbox dark0_hard +} + +M.sections = { + -- Core Components + bar = { + bg = black, + border = black, + }, + item = { + bg = gruvbox.surface0, + border = black, + text = gruvbox.text, + }, + popup = { + bg = with_alpha(gruvbox.base, 0.7), + border = gruvbox.crust, + }, + + -- Items + apple = gruvbox.flamingo, + media = { label = gruvbox.text }, + calendar = { label = gruvbox.text }, + spaces = { + icon = { + color = gruvbox.subtext0, + highlight = gruvbox.yellow, + }, + label = { + color = gruvbox.subtext0, + highlight = gruvbox.yellow, + }, + indicator = gruvbox.mauve, + }, + widgets = { + battery = { + low = gruvbox.red, + mid = gruvbox.yellow, + high = gruvbox.green, + }, + wifi = { + icon = gruvbox.text, + }, + volume = { + icon = gruvbox.blue, + popup = { + item = gruvbox.text, + highlight = gruvbox.subtext0, + bg = with_alpha(gruvbox.base, 0.7), + }, + slider = { + highlight = gruvbox.text, + bg = with_alpha(gruvbox.base, 0.7), + border = gruvbox.surface0, + }, + }, + messages = { icon = gruvbox.flamingo }, + }, +} + +return M diff --git a/darwin/sketchybar/config/default.lua b/darwin/sketchybar/config/default.lua new file mode 100644 index 0000000..fb50dc4 --- /dev/null +++ b/darwin/sketchybar/config/default.lua @@ -0,0 +1,57 @@ +local settings = require "settings" +local colors = require("colors").sections + +sbar.default { + updates = "when_shown", + icon = { + font = { + family = settings.font.text, + style = settings.font.style_map["Semibold"], + size = 14.0, + }, + color = colors.item.text, + padding_left = settings.paddings, + padding_right = settings.paddings, + background = { image = { corner_radius = 0 } }, + }, + label = { + font = { + family = settings.font.text, + style = settings.font.style_map["Semibold"], + size = 14.0, + }, + color = colors.item.text, + padding_left = settings.paddings, + padding_right = settings.paddings, + }, + background = { + height = 26, + corner_radius = 0, + color = colors.item.bg, + border_color = colors.item.border, + border_width = 2, + shadow = { + drawing = true, + angle = 45, + distance = 4, + color = colors.item.border, + }, + }, + popup = { + background = { + color = colors.popup.bg, + border_color = colors.popup.border, + border_width = 2, + corner_radius = 0, + shadow = { + drawing = true, + angle = 45, + distance = 4, + color = colors.item.border, + }, + }, + }, + padding_left = 4, + padding_right = 4, + scroll_texts = true, +} diff --git a/darwin/sketchybar/config/helpers/.gitignore b/darwin/sketchybar/config/helpers/.gitignore new file mode 100644 index 0000000..ba077a4 --- /dev/null +++ b/darwin/sketchybar/config/helpers/.gitignore @@ -0,0 +1 @@ +bin diff --git a/darwin/sketchybar/config/helpers/default_font.lua b/darwin/sketchybar/config/helpers/default_font.lua new file mode 100644 index 0000000..1c6a01e --- /dev/null +++ b/darwin/sketchybar/config/helpers/default_font.lua @@ -0,0 +1,12 @@ +return { + text = "SF Pro", -- Used for text + numbers = "SF Mono", -- Used for numbers + weather = "Symbols Nerd Font", + style_map = { + ["Regular"] = "Regular", + ["Semibold"] = "Semibold", + ["Bold"] = "Bold", + ["Heavy"] = "Heavy", + ["Black"] = "Black", + } +} diff --git a/darwin/sketchybar/config/helpers/event_providers/cpu_load/cpu.h b/darwin/sketchybar/config/helpers/event_providers/cpu_load/cpu.h new file mode 100644 index 0000000..413f70f --- /dev/null +++ b/darwin/sketchybar/config/helpers/event_providers/cpu_load/cpu.h @@ -0,0 +1,58 @@ +#include +#include +#include +#include + +struct cpu { + host_t host; + mach_msg_type_number_t count; + host_cpu_load_info_data_t load; + host_cpu_load_info_data_t prev_load; + bool has_prev_load; + + int user_load; + int sys_load; + int total_load; +}; + +static inline void cpu_init(struct cpu* cpu) { + cpu->host = mach_host_self(); + cpu->count = HOST_CPU_LOAD_INFO_COUNT; + cpu->has_prev_load = false; +} + +static inline void cpu_update(struct cpu* cpu) { + kern_return_t error = host_statistics(cpu->host, + HOST_CPU_LOAD_INFO, + (host_info_t)&cpu->load, + &cpu->count ); + + if (error != KERN_SUCCESS) { + printf("Error: Could not read cpu host statistics.\n"); + return; + } + + if (cpu->has_prev_load) { + uint32_t delta_user = cpu->load.cpu_ticks[CPU_STATE_USER] + - cpu->prev_load.cpu_ticks[CPU_STATE_USER]; + + uint32_t delta_system = cpu->load.cpu_ticks[CPU_STATE_SYSTEM] + - cpu->prev_load.cpu_ticks[CPU_STATE_SYSTEM]; + + uint32_t delta_idle = cpu->load.cpu_ticks[CPU_STATE_IDLE] + - cpu->prev_load.cpu_ticks[CPU_STATE_IDLE]; + + cpu->user_load = (double)delta_user / (double)(delta_system + + delta_user + + delta_idle) * 100.0; + + cpu->sys_load = (double)delta_system / (double)(delta_system + + delta_user + + delta_idle) * 100.0; + + cpu->total_load = cpu->user_load + cpu->sys_load; + } + + cpu->prev_load = cpu->load; + cpu->has_prev_load = true; +} diff --git a/darwin/sketchybar/config/helpers/event_providers/cpu_load/cpu_load.c b/darwin/sketchybar/config/helpers/event_providers/cpu_load/cpu_load.c new file mode 100644 index 0000000..ee97613 --- /dev/null +++ b/darwin/sketchybar/config/helpers/event_providers/cpu_load/cpu_load.c @@ -0,0 +1,41 @@ +#include "cpu.h" +#include "../sketchybar.h" + +int main (int argc, char** argv) { + float update_freq; + if (argc < 3 || (sscanf(argv[2], "%f", &update_freq) != 1)) { + printf("Usage: %s \"\" \"\"\n", argv[0]); + exit(1); + } + + alarm(0); + struct cpu cpu; + cpu_init(&cpu); + + // Setup the event in sketchybar + char event_message[512]; + snprintf(event_message, 512, "--add event '%s'", argv[1]); + sketchybar(event_message); + + char trigger_message[512]; + for (;;) { + // Acquire new info + cpu_update(&cpu); + + // Prepare the event message + snprintf(trigger_message, + 512, + "--trigger '%s' user_load='%d' sys_load='%02d' total_load='%02d'", + argv[1], + cpu.user_load, + cpu.sys_load, + cpu.total_load ); + + // Trigger the event + sketchybar(trigger_message); + + // Wait + usleep(update_freq * 1000000); + } + return 0; +} diff --git a/darwin/sketchybar/config/helpers/event_providers/cpu_load/makefile b/darwin/sketchybar/config/helpers/event_providers/cpu_load/makefile new file mode 100644 index 0000000..6366a0f --- /dev/null +++ b/darwin/sketchybar/config/helpers/event_providers/cpu_load/makefile @@ -0,0 +1,5 @@ +bin/cpu_load: cpu_load.c cpu.h ../sketchybar.h | bin + clang -std=c99 -O3 $< -o $@ + +bin: + mkdir bin diff --git a/darwin/sketchybar/config/helpers/event_providers/makefile b/darwin/sketchybar/config/helpers/event_providers/makefile new file mode 100644 index 0000000..8c1ca39 --- /dev/null +++ b/darwin/sketchybar/config/helpers/event_providers/makefile @@ -0,0 +1,3 @@ +all: + (cd cpu_load && $(MAKE)) + (cd network_load && $(MAKE)) diff --git a/darwin/sketchybar/config/helpers/event_providers/network_load/makefile b/darwin/sketchybar/config/helpers/event_providers/network_load/makefile new file mode 100644 index 0000000..e464482 --- /dev/null +++ b/darwin/sketchybar/config/helpers/event_providers/network_load/makefile @@ -0,0 +1,5 @@ +bin/network_load: network_load.c network.h ../sketchybar.h | bin + clang -std=c99 -O3 $< -o $@ + +bin: + mkdir bin diff --git a/darwin/sketchybar/config/helpers/event_providers/network_load/network.h b/darwin/sketchybar/config/helpers/event_providers/network_load/network.h new file mode 100644 index 0000000..25175e5 --- /dev/null +++ b/darwin/sketchybar/config/helpers/event_providers/network_load/network.h @@ -0,0 +1,90 @@ +#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/darwin/sketchybar/config/helpers/event_providers/network_load/network_load.c b/darwin/sketchybar/config/helpers/event_providers/network_load/network_load.c new file mode 100644 index 0000000..06afe95 --- /dev/null +++ b/darwin/sketchybar/config/helpers/event_providers/network_load/network_load.c @@ -0,0 +1,42 @@ +#include +#include "network.h" +#include "../sketchybar.h" + +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/darwin/sketchybar/config/helpers/event_providers/sketchybar.h b/darwin/sketchybar/config/helpers/event_providers/sketchybar.h new file mode 100644 index 0000000..72bf06a --- /dev/null +++ b/darwin/sketchybar/config/helpers/event_providers/sketchybar.h @@ -0,0 +1,122 @@ +#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/darwin/sketchybar/config/helpers/icon_map.lua b/darwin/sketchybar/config/helpers/icon_map.lua new file mode 100644 index 0000000..704b0c0 --- /dev/null +++ b/darwin/sketchybar/config/helpers/icon_map.lua @@ -0,0 +1,328 @@ +return { + ["Live"] = ":ableton:", + ["Adobe Bridge"] = ":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"] = ":alfred:", + ["Android Messages"] = ":android_messages:", + ["Android Studio"] = ":android_studio:", + ["Anki"] = ":anki:", + ["Anytype"] = ":anytype:", + ["App Eraser"] = ":app_eraser:", + ["App Store"] = ":app_store:", + ["Arc"] = ":arc:", + ["Arduino"] = ":arduino:", + ["Arduino IDE"] = ":arduino:", + ["Atom"] = ":atom:", + ["Audacity"] = ":audacity:", + ["Bambu Studio"] = ":bambu_studio:", + ["MoneyMoney"] = ":bank:", + ["Battle.net"] = ":battle_net:", + ["Bear"] = ":bear:", + ["BetterTouchTool"] = ":bettertouchtool:", + ["Bilibili"] = ":bilibili:", + ["哔哩哔哩"] = ":bilibili:", + ["Bitwarden"] = ":bit_warden:", + ["Blender"] = ":blender:", + ["BluOS Controller"] = ":bluos_controller:", + ["Calibre"] = ":book:", + ["Brave Browser"] = ":brave_browser:", + ["BusyCal"] = ":busycal:", + ["Calculator"] = ":calculator:", + ["Calculette"] = ":calculator:", + ["Calendar"] = ":calendar:", + ["日历"] = ":calendar:", + ["Fantastical"] = ":calendar:", + ["Cron"] = ":calendar:", + ["Amie"] = ":calendar:", + ["Calendrier"] = ":calendar:", + ["カレンダー"] = ":calendar:", + ["Notion Calendar"] = ":calendar:", + ["calibre"] = ":calibre:", + ["Caprine"] = ":caprine:", + ["Amazon Chime"] = ":chime:", + ["Citrix Workspace"] = ":citrix:", + ["Citrix Viewer"] = ":citrix:", + ["Claude"] = ":claude:", + ["ClickUp"] = ":click_up:", + ["Code"] = ":code:", + ["Code - Insiders"] = ":code:", + ["Cold Turkey Blocker"] = ":cold_turkey_blocker:", + ["Color Picker"] = ":color_picker:", + ["数码测色计"] = ":color_picker:", + ["Copilot"] = ":copilot:", + ["CotEditor"] = ":coteditor:", + ["Creative Cloud"] = ":creative_cloud:", + ["Cursor"] = ":cursor:", + ["Cypress"] = ":cypress:", + ["DataGrip"] = ":datagrip:", + ["DataSpell"] = ":dataspell:", + ["DaVinci Resolve"] = ":davinciresolve:", + ["Deezer"] = ":deezer:", + ["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:", + ["draw.io"] = ":draw_io:", + ["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:", + ["Fork"] = ":fork:", + ["FreeTube"] = ":freetube:", + ["Fusion"] = ":fusion:", + ["System Preferences"] = ":gear:", + ["System Settings"] = ":gear:", + ["系统设置"] = ":gear:", + ["Réglages Système"] = ":gear:", + ["システム設定"] = ":gear:", + ["Ghostty"] = ":ghostty:", + ["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:", + ["IINA"] = ":iina:", + ["Adobe Illustrator"] = ":illustrator:", + ["Illustrator"] = ":illustrator:", + ["Adobe InDesign"] = ":indesign:", + ["InDesign"] = ":indesign:", + ["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:", + ["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:", + ["Spark"] = ":mail:", + ["邮件"] = ":mail:", + ["メール"] = ":mail:", + ["MAMP"] = ":mamp:", + ["MAMP PRO"] = ":mamp:", + ["Maps"] = ":maps:", + ["Google Maps"] = ":maps:", + ["マップ"] = ":maps:", + ["Marta"] = ":marta:", + ["Matlab"] = ":matlab:", + ["Mattermost"] = ":mattermost:", + ["Messages"] = ":messages:", + ["信息"] = ":messages:", + ["Nachrichten"] = ":messages:", + ["メッセージ"] = ":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:", + ["Moonlight"] = ":moonlight:", + ["mpv"] = ":mpv:", + ["Mullvad Browser"] = ":mullvad_browser:", + ["Music"] = ":music:", + ["音乐"] = ":music:", + ["Musique"] = ":music:", + ["ミュージック"] = ":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:", + ["备忘录"] = ":notes:", + ["メモ"] = ":notes:", + ["Notion"] = ":notion:", + ["Nova"] = ":nova:", + ["Numbers"] = ":numbers:", + ["Numbers 表格"] = ":numbers:", + ["Obsidian"] = ":obsidian:", + ["OBS"] = ":obsstudio:", + ["OmniFocus"] = ":omni_focus:", + ["1Password"] = ":one_password:", + ["Open Video Downloader"] = ":open_video_downloader:", + ["ChatGPT"] = ":openai:", + ["OpenVPN Connect"] = ":openvpn_connect:", + ["Opera"] = ":opera:", + ["OrbStack"] = ":orbstack:", + ["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:", + ["PDF Expert"] = ":pdf_expert:", + ["Pearcleaner"] = ":pearcleaner:", + ["Phoenix Slides"] = ":phoenix_slides:", + ["Adobe Photoshop"] = ":photoshop:", + ["PhpStorm"] = ":php_storm:", + ["Pi-hole Remote"] = ":pihole:", + ["Pine"] = ":pine:", + ["Plex"] = ":plex:", + ["Plexamp"] = ":plexamp:", + ["Podcasts"] = ":podcasts:", + ["播客"] = ":podcasts:", + ["PomoDone App"] = ":pomodone:", + ["Postman"] = ":postman:", + ["Proton Mail"] = ":proton_mail:", + ["Proton Mail Bridge"] = ":proton_mail:", + ["Proton VPN"] = ":proton_vpn:", + ["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:", + ["リマインダー"] = ":reminders:", + ["Replit"] = ":replit:", + ["Rider"] = ":rider:", + ["JetBrains Rider"] = ":rider:", + ["Rio"] = ":rio:", + ["Royal TSX"] = ":royaltsx:", + ["Safari"] = ":safari:", + ["Safari浏览器"] = ":safari:", + ["Safari Technology Preview"] = ":safari:", + ["Sequel Ace"] = ":sequel_ace:", + ["Sequel Pro"] = ":sequel_pro:", + ["Setapp"] = ":setapp:", + ["SF Symbols"] = ":sf_symbols:", + ["Signal"] = ":signal:", + ["sioyek"] = ":sioyek:", + ["Sketch"] = ":sketch:", + ["Skype"] = ":skype:", + ["Slack"] = ":slack:", + ["Spark Desktop"] = ":spark:", + ["Spotify"] = ":spotify:", + ["Spotlight"] = ":spotlight:", + ["Sublime Text"] = ":sublime_text:", + ["superProductivity"] = ":superproductivity:", + ["Tana"] = ":tana:", + ["TeamSpeak 3"] = ":team_speak:", + ["Telegram"] = ":telegram:", + ["Terminal"] = ":terminal:", + ["终端"] = ":terminal:", + ["ターミナル"] = ":terminal:", + ["Typora"] = ":text:", + ["Microsoft To Do"] = ":things:", + ["Things"] = ":things:", + ["Thunderbird"] = ":thunderbird:", + ["Thunderbird Beta"] = ":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:", + ["UTM"] = ":utm:", + ["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"] = ":yandex_music:", + ["Yuque"] = ":yuque:", + ["语雀"] = ":yuque:", + ["Zed"] = ":zed:", + ["Zen Browser"] = ":zen_browser:", + ["Zeplin"] = ":zeplin:", + ["zoom.us"] = ":zoom:", + ["Zotero"] = ":zotero:", + ["Zulip"] = ":zulip:", +} diff --git a/darwin/sketchybar/config/helpers/init.lua b/darwin/sketchybar/config/helpers/init.lua new file mode 100644 index 0000000..9581892 --- /dev/null +++ b/darwin/sketchybar/config/helpers/init.lua @@ -0,0 +1 @@ +os.execute("(cd helpers && make)") diff --git a/darwin/sketchybar/config/helpers/install.sh b/darwin/sketchybar/config/helpers/install.sh new file mode 100644 index 0000000..15c1234 --- /dev/null +++ b/darwin/sketchybar/config/helpers/install.sh @@ -0,0 +1,2 @@ +# SbarLua +(git clone https://github.com/FelixKratz/SbarLua.git /tmp/SbarLua && cd /tmp/SbarLua/ && make install && rm -rf /tmp/SbarLua/) diff --git a/darwin/sketchybar/config/helpers/makefile b/darwin/sketchybar/config/helpers/makefile new file mode 100644 index 0000000..3246108 --- /dev/null +++ b/darwin/sketchybar/config/helpers/makefile @@ -0,0 +1,3 @@ +all: + (cd event_providers && $(MAKE)) >/dev/null + (cd menus && $(MAKE)) >/dev/null diff --git a/darwin/sketchybar/config/helpers/menus/makefile b/darwin/sketchybar/config/helpers/menus/makefile new file mode 100644 index 0000000..0cb454e --- /dev/null +++ b/darwin/sketchybar/config/helpers/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/darwin/sketchybar/config/helpers/menus/menus.c b/darwin/sketchybar/config/helpers/menus/menus.c new file mode 100644 index 0000000..2e77822 --- /dev/null +++ b/darwin/sketchybar/config/helpers/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/darwin/sketchybar/config/icons.lua b/darwin/sketchybar/config/icons.lua new file mode 100644 index 0000000..022f41c --- /dev/null +++ b/darwin/sketchybar/config/icons.lua @@ -0,0 +1,67 @@ +local settings = require "settings" + +local icons = { + sf_symbols = { + plus = "􀅼", + loading = "􀖇", + apple = "􀣺", --󱚞 + gear = "􀍟", + cpu = "󰒆", + clipboard = "􀉄", + music = "􀑪", + calendar = "􀐫", + message = "􁋬", + separators = { + left = "􀄪", + right = "􀄫", + }, + space_indicator = { + on = "󰄯", + off = "󰄰", + }, + + switch = { + on = "􁏮", + off = "􁏯", + }, + volume = { + _100 = "􀊨", + _66 = "􀊦", + _33 = "􀊤", + _10 = "􀊠", + _0 = "􀊢", + }, + battery = { + _100 = "􀛨", + _75 = "􀺸", + _50 = "􀺶", + _25 = "􀛩", + _0 = "􀛪", + charging = "􀢋", + }, + wifi = { + upload = "􀄨", + download = "􀄩", + connected = "􀙇", + disconnected = "􀙈", + router = "􁓤", + vpn = "󰌾", + test = "", + }, + media = { + back = "􀊊", + forward = "􀊌", + play_pause = "􀊈", + }, + ramicons = { + swap = "󰁄", + ram = "󰍛", + }, + }, +} + +if not (settings.icons == "NerdFont") then + return icons.sf_symbols +else + return icons.nerdfont +end diff --git a/darwin/sketchybar/config/init.lua b/darwin/sketchybar/config/init.lua new file mode 100644 index 0000000..e380b05 --- /dev/null +++ b/darwin/sketchybar/config/init.lua @@ -0,0 +1,11 @@ +sbar = require("sketchybar") + +sbar.begin_config() +sbar.hotload(true) + +require("bar") +require("default") +require("items") + +sbar.end_config() +sbar.event_loop() diff --git a/darwin/sketchybar/config/items/.luarc.json b/darwin/sketchybar/config/items/.luarc.json new file mode 100644 index 0000000..f245e7c --- /dev/null +++ b/darwin/sketchybar/config/items/.luarc.json @@ -0,0 +1,5 @@ +{ + "diagnostics.globals": [ + "sbar" + ] +} \ No newline at end of file diff --git a/darwin/sketchybar/config/items/apple.lua b/darwin/sketchybar/config/items/apple.lua new file mode 100644 index 0000000..ac57f6d --- /dev/null +++ b/darwin/sketchybar/config/items/apple.lua @@ -0,0 +1,39 @@ +local colors = require("colors").sections +local icons = require("icons") + +local apple = sbar.add("item", { + icon = { + font = { size = 16 }, + string = icons.apple, + padding_right = 15, + padding_left = 15, + color = colors.apple, + }, + label = { drawing = false }, + click_script = "$CONFIG_DIR/helpers/menus/bin/menus -s 0", +}) + +apple:subscribe("mouse.clicked", function() + sbar.animate("tanh", 8, function() + apple:set { + background = { + shadow = { + distance = 0, + }, + }, + y_offset = -4, + padding_left = 8, + padding_right = 0, + } + apple:set { + background = { + shadow = { + distance = 4, + }, + }, + y_offset = 0, + padding_left = 4, + padding_right = 4, + } + end) +end) diff --git a/darwin/sketchybar/config/items/calendar.lua b/darwin/sketchybar/config/items/calendar.lua new file mode 100644 index 0000000..1d5e771 --- /dev/null +++ b/darwin/sketchybar/config/items/calendar.lua @@ -0,0 +1,53 @@ +local icons = require("icons") +local colors = require("colors").sections.calendar +local settings = require("settings") + +local cal = sbar.add("item", { + label = { + padding_left = 6, + padding_right = 12, + font = { + -- family = settings.font.numbers, + style = settings.font.style_map["Bold"], + }, + }, + -- label = { + -- color = colors.label, + -- align = "left", + -- padding_right = 8, + -- }, + padding_left = 10, + position = "right", + update_freq = 30, + click_script = "open -a 'Calendar'", +}) + +cal:subscribe("mouse.clicked", function() + sbar.animate("tanh", 8, function() + cal:set { + background = { + shadow = { + distance = 0, + }, + }, + y_offset = -4, + padding_left = 14, + padding_right = 0, + } + cal:set { + background = { + shadow = { + distance = 4, + }, + }, + y_offset = 0, + padding_left = 10, + padding_right = 4, + } + end) +end) + +cal:subscribe({ "forced", "routine", "system_woke" }, function() + ---@diagnostic disable-next-line: param-type-mismatch + cal:set { label = os.date("%I"):gsub("^0+", "") .. ":" .. os.date "%M %p" } +end) diff --git a/darwin/sketchybar/config/items/init.lua b/darwin/sketchybar/config/items/init.lua new file mode 100644 index 0000000..c90fda3 --- /dev/null +++ b/darwin/sketchybar/config/items/init.lua @@ -0,0 +1,13 @@ +--left +require("items.apple") +require("items.spaces") +require("items.menus") + +--center +require("items.notifications") + +--right (reverse order) +require("items.calendar") +require("items.widgets") +require("items.wifi") +require("items.media") diff --git a/darwin/sketchybar/config/items/media.lua b/darwin/sketchybar/config/items/media.lua new file mode 100644 index 0000000..291874d --- /dev/null +++ b/darwin/sketchybar/config/items/media.lua @@ -0,0 +1,92 @@ +local icons = require("icons") +local colors = require("colors").sections.media + +local whitelist = { ["Spotify"] = true, ["Psst"] = true } + +local media_playback = sbar.add("item", { + position = "right", + icon = { + string = icons.music, + color = colors.label, + padding_left = 8, + }, + label = { + max_chars = 50, + padding_right = 8, + }, + popup = { + horizontal = true, + align = "center", + y_offset = 2, + }, + padding_right = 8, +}) + +sbar.add("item", { + position = "popup." .. media_playback.name, + padding_left = 6, + padding_right = 6, + icon = { string = icons.media.back }, + label = { drawing = false }, + background = { drawing = false }, + click_script = "nowplaying-cli previous", +}) + +sbar.add("item", { + position = "popup." .. media_playback.name, + padding_left = 6, + padding_right = 6, + icon = { string = icons.media.play_pause }, + label = { drawing = false }, + background = { drawing = false }, + click_script = "nowplaying-cli togglePlayPause", +}) + +sbar.add("item", { + position = "popup." .. media_playback.name, + padding_left = 6, + padding_right = 6, + icon = { string = icons.media.forward }, + label = { drawing = false }, + background = { drawing = false }, + click_script = "nowplaying-cli next", +}) + +media_playback:subscribe("media_change", function(env) + if whitelist[env.INFO.app] then + local is_playing = (env.INFO.state == "playing") + media_playback:set { + drawing = is_playing, + label = { + string = env.INFO.artist .. " - " .. env.INFO.title, + padding_left = is_playing and 8 or 0, + }, + } + end +end) + +media_playback:subscribe("mouse.clicked", function(_) + sbar.animate("tanh", 8, function() + media_playback:set { + background = { + shadow = { + distance = 0, + }, + }, + y_offset = -4, + padding_left = 8, + padding_right = 4, + } + media_playback:set { + background = { + shadow = { + distance = 4, + }, + }, + y_offset = 0, + padding_left = 4, + padding_right = 8, + } + end) + media_playback:set { popup = { drawing = "toggle" } } +end) diff --git a/darwin/sketchybar/config/items/menus.lua b/darwin/sketchybar/config/items/menus.lua new file mode 100644 index 0000000..4d06fce --- /dev/null +++ b/darwin/sketchybar/config/items/menus.lua @@ -0,0 +1,71 @@ +local settings = require("settings") + +local menu_watcher = sbar.add("item", { + drawing = false, + updates = false, +}) +local space_menu_swap = sbar.add("item", { + drawing = false, + updates = true, +}) +sbar.add("event", "swap_menus_and_spaces") + +local max_items = 15 +local menu_items = {} +for i = 1, max_items, 1 do + local menu = sbar.add("item", "menu." .. i, { + drawing = false, + icon = { drawing = false }, + background = { drawing = false }, + label = { + font = { + style = settings.font.style_map[i == 1 and "Heavy" or "Semibold"], + }, + padding_left = 6, + padding_right = 6, + }, + click_script = "$CONFIG_DIR/lua/helpers/menus/bin/menus -s " .. i, + }) + + menu_items[i] = menu +end + +sbar.add("bracket", { "/menu\\..*/" }, {}) + +local menu_padding = sbar.add("item", "menu.padding", { + drawing = false, + width = 5, +}) + +local function update_menus(env) + sbar.exec("$CONFIG_DIR/lua/helpers/menus/bin/menus -l", function(menus) + sbar.set("/menu\\..*/", { drawing = false }) + menu_padding:set { drawing = true } + Id = 1 + for menu in string.gmatch(menus, "[^\r\n]+") do + if Id < max_items then + menu_items[Id]:set { label = menu, drawing = true } + else + break + end + Id = Id + 1 + end + end) +end + +menu_watcher:subscribe("front_app_switched", update_menus) + +space_menu_swap:subscribe("swap_menus_and_spaces", function(env) + local drawing = menu_items[1]:query().geometry.drawing == "on" + if drawing then + menu_watcher:set { updates = false } + sbar.set("/menu\\..*/", { drawing = false }) + sbar.set("/space\\..*/", { drawing = true }) + else + menu_watcher:set { updates = true } + sbar.set("/space\\..*/", { drawing = false }) + update_menus() + end +end) + +return menu_watcher diff --git a/darwin/sketchybar/config/items/notifications.lua b/darwin/sketchybar/config/items/notifications.lua new file mode 100644 index 0000000..71e0bac --- /dev/null +++ b/darwin/sketchybar/config/items/notifications.lua @@ -0,0 +1,49 @@ +local notification = sbar.add("item", "notifications", { + width = 0, + position = "center", + popup = { + drawing = true, + align = "center", + y_offset = -80, + }, +}) + +local notification_popup = sbar.add("item", { + position = "popup." .. notification.name, + width = "dynamic", + icon = { drawing = false }, + background = { drawing = false }, +}) + +local function hide_notification() + sbar.animate("sin", 30, function() + notification:set({ popup = { y_offset = 2 } }) + notification:set({ popup = { y_offset = -80 } }) + end) +end + +local function show_notification(content, hold) + hide_notification() + + notification_popup:set({ label = { string = content } }) + + sbar.animate("sin", 30, function() + notification:set({ popup = { y_offset = -80 } }) + notification:set({ popup = { y_offset = 2 } }) + end) + + if hold == false then + sbar.delay(5, function() + if hold then return end + hide_notification() + end) + end +end + +notification:subscribe("send_message", function(env) + local content = env.MESSAGE + local hold = env.HOLD ~= nil and env.HOLD == "true" or false + show_notification(content, hold) +end) + +notification:subscribe("hide_message", hide_notification) diff --git a/darwin/sketchybar/config/items/spaces.lua b/darwin/sketchybar/config/items/spaces.lua new file mode 100644 index 0000000..345159c --- /dev/null +++ b/darwin/sketchybar/config/items/spaces.lua @@ -0,0 +1,158 @@ +local colors = require("colors").sections.spaces +local icons = require("icons") +local icon_map = require("helpers.icon_map") + +local function add_windows(space, space_name) + sbar.exec("aerospace list-windows --format %{app-name} --workspace " .. space_name, function(windows) + local icon_line = "" + for app in windows:gmatch "[^\r\n]+" do + local lookup = icon_map[app] + local icon = ((lookup == nil) and icon_map["Default"] or lookup) + icon_line = icon_line .. " " .. icon + end + + sbar.animate("tanh", 10, function() + space:set { + label = { + string = icon_line == "" and "—" or icon_line, + padding_right = icon_line == "" and 8 or 12, + }, + } + end) + end) +end + +sbar.exec("aerospace list-workspaces --all", function(spaces) + for space_name in spaces:gmatch "[^\r\n]+" do + local space = sbar.add("item", "space." .. space_name, { + icon = { + string = space_name, + color = colors.icon.color, + highlight_color = colors.icon.highlight, + padding_left = 8, + }, + label = { + font = "sketchybar-app-font:Regular:14.0", + string = "", + color = colors.label.color, + highlight_color = colors.label.highlight, + y_offset = -1, + }, + click_script = "aerospace workspace " .. space_name, + padding_left = space_name == "1" and 0 or 4, + }) + + add_windows(space, space_name) + + space:subscribe("aerospace_workspace_change", function(env) + local selected = env.FOCUSED_WORKSPACE == space_name + space:set { + icon = { highlight = selected }, + label = { highlight = selected }, + } + + if selected then + sbar.animate("tanh", 8, function() + space:set { + background = { + shadow = { + distance = 0, + }, + }, + y_offset = -4, + padding_left = 8, + padding_right = 0, + } + space:set { + background = { + shadow = { + distance = 4, + }, + }, + y_offset = 0, + padding_left = 4, + padding_right = 4, + } + end) + end + end) + + space:subscribe("space_windows_change", function() + add_windows(space, space_name) + end) + + space:subscribe("mouse.clicked", function() + sbar.animate("tanh", 8, function() + space:set { + background = { + shadow = { + distance = 0, + }, + }, + y_offset = -4, + padding_left = 8, + padding_right = 0, + } + space:set { + background = { + shadow = { + distance = 4, + }, + }, + y_offset = 0, + padding_left = 4, + padding_right = 4, + } + end) + end) + end +end) + +local spaces_indicator = sbar.add("item", { + icon = { + padding_left = 8, + padding_right = 9, + string = icons.switch.on, + color = colors.indicator, + }, + label = { + width = 0, + padding_left = 0, + padding_right = 8, + }, + padding_right = 8, +}) + +spaces_indicator:subscribe("swap_menus_and_spaces", function() + local currently_on = spaces_indicator:query().icon.value == icons.switch.on + spaces_indicator:set { + icon = currently_on and icons.switch.off or icons.switch.on, + } +end) + +spaces_indicator:subscribe("mouse.clicked", function() + sbar.animate("tanh", 8, function() + spaces_indicator:set { + background = { + shadow = { + distance = 0, + }, + }, + y_offset = -4, + padding_left = 8, + padding_right = 4, + } + spaces_indicator:set { + background = { + shadow = { + distance = 4, + }, + }, + y_offset = 0, + padding_left = 4, + padding_right = 8, + } + end) + + sbar.trigger("swap_menus_and_spaces") +end) diff --git a/darwin/sketchybar/config/items/widgets/battery.lua b/darwin/sketchybar/config/items/widgets/battery.lua new file mode 100644 index 0000000..8dbfe6f --- /dev/null +++ b/darwin/sketchybar/config/items/widgets/battery.lua @@ -0,0 +1,83 @@ +local icons = require "icons" +local colors = require("colors").sections.widgets.battery + +local battery = sbar.add("item", "widgets.battery", { + position = "right", + icon = {}, + label = { drawing = false }, + background = { drawing = false }, + padding_left = 8, + padding_right = 4, + update_freq = 180, + popup = { align = "center", y_offset = 4 }, +}) + +local remaining_time = sbar.add("item", { + position = "popup." .. battery.name, + icon = { + string = "Time remaining:", + width = 100, + align = "left", + }, + label = { + string = "??:??h", + width = 100, + align = "right", + }, + background = { drawing = false }, +}) + +battery:subscribe({ "routine", "power_source_change", "system_woke" }, function() + sbar.exec("pmset -g batt", function(batt_info) + local icon = "!" + + local found, _, charge = batt_info:find "(%d+)%%" + if found then + charge = tonumber(charge) + end + + local color = colors.high + local charging, _, _ = batt_info:find "AC Power" + + if charging then + icon = icons.battery.charging + else + if found and charge > 80 then + icon = icons.battery._100 + elseif found and charge > 60 then + icon = icons.battery._75 + elseif found and charge > 40 then + icon = icons.battery._50 + elseif found and charge > 30 then + icon = icons.battery._50 + color = colors.mid + elseif found and charge > 20 then + icon = icons.battery._25 + color = colors.mid + else + icon = icons.battery._0 + color = colors.low + end + end + + battery:set { + icon = { + string = icon, + color = color, + }, + } + end) +end) + +battery:subscribe("mouse.clicked", function() + local drawing = battery:query().popup.drawing + battery:set { popup = { drawing = "toggle" } } + + if drawing == "off" then + sbar.exec("pmset -g batt", function(batt_info) + local found, _, remaining = batt_info:find " (%d+:%d+) remaining" + local label = found and remaining .. "h" or "No estimate" + remaining_time:set { label = label } + end) + end +end) diff --git a/darwin/sketchybar/config/items/widgets/init.lua b/darwin/sketchybar/config/items/widgets/init.lua new file mode 100644 index 0000000..51b2692 --- /dev/null +++ b/darwin/sketchybar/config/items/widgets/init.lua @@ -0,0 +1,9 @@ +-- require "items.widgets.messages" +require("items.widgets.volume") +require("items.widgets.battery") + +sbar.add("bracket", { "/widgets\\..*/" }, {}) + +sbar.add("item", "widget.padding", { + width = 16, +}) diff --git a/darwin/sketchybar/config/items/widgets/messages.lua b/darwin/sketchybar/config/items/widgets/messages.lua new file mode 100644 index 0000000..d64df94 --- /dev/null +++ b/darwin/sketchybar/config/items/widgets/messages.lua @@ -0,0 +1,41 @@ +local icons = require "icons" +local colors = require("colors").sections.widgets.messages + +local messages = sbar.add("item", "widgets.messages", { + position = "right", + icon = { + color = colors.icon, + string = icons.message, + padding_right = 4, + }, + label = { drawing = false }, + background = { drawing = false }, + update_freq = 30, + padding_left = -4, +}) + +messages:subscribe({ "routine", "front_app_changed", "space_change", "space_windows_change" }, function(env) + sbar.exec( + -- requires full disk access + [[sqlite3 ~/Library/Messages/chat.db "SELECT COUNT(guid) FROM message WHERE NOT(is_read) AND NOT(is_from_me) AND text !=''"]], + function(newmess) + local mess = tonumber(newmess) + local drawing = false + + if mess > 0 then + drawing = true + end + + messages:set { + icon = { + drawing = drawing, + }, + padding_right = drawing and 4 or 0, + } + end + ) +end) + +messages:subscribe("mouse.clicked", function(env) + sbar.exec "open -a 'Messages'" +end) diff --git a/darwin/sketchybar/config/items/widgets/volume.lua b/darwin/sketchybar/config/items/widgets/volume.lua new file mode 100644 index 0000000..737d13b --- /dev/null +++ b/darwin/sketchybar/config/items/widgets/volume.lua @@ -0,0 +1,142 @@ +local colors = require("colors").sections.widgets.volume +local icons = require "icons" + +local popup_width = 250 + +local volume_icon = sbar.add("item", "widgets.volume", { + position = "right", + icon = { + color = colors.icon, + }, + label = { drawing = false }, + background = { drawing = false }, + popup = { + align = "center", + y_offset = 2, + }, + padding_right = 8, +}) + +local volume_slider = sbar.add("slider", popup_width, { + position = "popup." .. volume_icon.name, + slider = { + highlight_color = colors.slider.highlight, + background = { + height = 12, + corner_radius = 6, + color = colors.slider.bg, + border_color = colors.slider.border, + border_width = 2, + }, + knob = { + string = "􀀁", + drawing = true, + }, + }, + background = { color = colors.bg1, height = 2, y_offset = -20 }, + click_script = 'osascript -e "set volume output volume $PERCENTAGE"', +}) + +volume_icon:subscribe("volume_change", function(env) + local icon = icons.volume._0 + local volume = tonumber(env.INFO) + sbar.exec("SwitchAudioSource -t output -c", function(result) + if volume > 60 then + icon = icons.volume._100 + elseif volume > 30 then + icon = icons.volume._66 + elseif volume > 10 then + icon = icons.volume._33 + elseif volume > 0 then + icon = icons.volume._10 + end + + volume_icon:set { icon = icon } + volume_slider:set { slider = { percentage = volume } } + end) +end) + +local function volume_collapse_details() + local drawing = volume_icon:query().popup.drawing == "on" + if not drawing then + return + end + volume_icon:set { popup = { drawing = false } } + sbar.remove "/volume.device\\.*/" +end + +local current_audio_device = "None" +local function volume_toggle_details(env) + if env.BUTTON == "right" then + sbar.exec "open /System/Library/PreferencePanes/Sound.prefpane" + return + end + + local should_draw = volume_icon:query().popup.drawing == "off" + if should_draw then + volume_icon:set { popup = { drawing = true } } + sbar.exec("SwitchAudioSource -t output -c", function(result) + current_audio_device = result:sub(1, -2) + sbar.exec("SwitchAudioSource -a -t output", function(available) + local current = current_audio_device + local color = colors.popup.item + local counter = 0 + + for device in string.gmatch(available, "[^\r\n]+") do + if current == device then + color = colors.popup.highlight + end + sbar.add("item", "volume.device." .. counter, { + position = "popup." .. volume_icon.name, + width = popup_width, + align = "center", + label = { string = device, color = color }, + background = { drawing = false }, + click_script = 'SwitchAudioSource -s "' + .. device + .. '" && sketchybar --set /volume.device\\.*/ label.color=' + .. colors.popup.item + .. " --set $NAME label.color=" + .. colors.popup.highlight, + }) + counter = counter + 1 + end + end) + end) + else + volume_collapse_details() + end +end + +local function volume_scroll(env) + local delta = env.SCROLL_DELTA + sbar.exec('osascript -e "set volume output volume (output volume of (get volume settings) + ' .. delta .. ')"') +end + +volume_icon:subscribe("mouse.clicked", function(env) + volume_toggle_details(env) + -- sbar.animate("tanh", 8, function() + -- volume_icon:set({ + -- background = { + -- shadow = { + -- distance = 0, + -- }, + -- }, + -- y_offset = -4, + -- padding_left = 8, + -- padding_right = 0, + -- }) + -- volume_icon:set({ + -- background = { + -- shadow = { + -- distance = 4, + -- }, + -- }, + -- y_offset = 0, + -- padding_left = 4, + -- padding_right = 4, + -- }) + -- end) +end) + +volume_icon:subscribe("mouse.scrolled", volume_scroll) diff --git a/darwin/sketchybar/config/items/wifi.lua b/darwin/sketchybar/config/items/wifi.lua new file mode 100644 index 0000000..674c54a --- /dev/null +++ b/darwin/sketchybar/config/items/wifi.lua @@ -0,0 +1,142 @@ +local icons = require "icons" +local colors = require("colors").sections.widgets.wifi + +sbar.exec( + "killall network_load >/dev/null; $CONFIG_DIR/helpers/event_providers/network_load/bin/network_load en0 network_update 2.0" +) + +local popup_width = 250 + +local wifi = sbar.add("item", "widgets.wifi", { + position = "right", + padding_right = 8, + padding_left = 0, + icon = { + color = colors.icon, + padding_left = 8 + }, + label = { + padding_right = 8, + }, + popup = { + align = "center", + height = 30, + y_offset = 2, + }, +}) + +local ip = sbar.add("item", { + position = "popup." .. wifi.name, + icon = { + align = "left", + string = "IP:", + width = popup_width / 2, + }, + label = { + string = "???.???.???.???", + width = popup_width / 2, + align = "right", + }, + background = { drawing = false }, +}) + +local router = sbar.add("item", { + position = "popup." .. wifi.name, + icon = { + align = "left", + string = "Router:", + width = popup_width / 2, + }, + label = { + string = "???.???.???.???", + width = popup_width / 2, + align = "right", + }, + background = { drawing = false }, +}) + +wifi:subscribe({ "wifi_change", "system_woke", "forced" }, function() + sbar.exec([[ipconfig getsummary en0 | awk -F ' SSID : ' '/ SSID : / {print $2}']], function(wifi_name) + local is_connected = not (wifi_name == "") + wifi:set { + icon = { + string = is_connected and icons.wifi.connected or icons.wifi.disconnected, + }, + label = { + string = is_connected and wifi_name or "", + } + } + + sbar.exec([[sleep 2; scutil --nwi | grep -m1 'utun' | awk '{ print $1 }']], function(vpn) + local is_vpn_connected = not (vpn == "") + + if is_vpn_connected then + wifi:set { + icon = { + string = icons.wifi.vpn, + color = colors.green, + }, + } + end + end) + end) +end) + +local function hide_details() + wifi:set { popup = { drawing = false } } +end + +local function toggle_details() + local should_draw = wifi:query().popup.drawing == "off" + if should_draw then + wifi:set { popup = { drawing = true } } + sbar.exec("ipconfig getifaddr en0", function(result) + ip:set { label = result } + end) + sbar.exec("networksetup -getinfo Wi-Fi | awk -F 'Router: ' '/^Router: / {print $2}'", function(result) + router:set { label = result } + end) + else + hide_details() + end +end + +wifi:subscribe("mouse.clicked", function() + sbar.animate("tanh", 8, function() + wifi:set({ + background = { + shadow = { + distance = 0, + }, + }, + y_offset = -4, + padding_left = 4, + padding_right = 4, + }) + wifi:set({ + background = { + shadow = { + distance = 4, + }, + }, + y_offset = 0, + padding_left = 0, + padding_right = 8, + }) + end) + toggle_details() +end) + +-- wifi:subscribe("mouse.exited.global", hide_details) + +local function copy_label_to_clipboard(env) + local label = sbar.query(env.NAME).label.value + sbar.exec('echo "' .. label .. '" | pbcopy') + sbar.set(env.NAME, { label = { string = icons.clipboard, align = "center" } }) + sbar.delay(1, function() + sbar.set(env.NAME, { label = { string = label, align = "right" } }) + end) +end + +ip:subscribe("mouse.clicked", copy_label_to_clipboard) +router:subscribe("mouse.clicked", copy_label_to_clipboard) diff --git a/darwin/sketchybar/config/settings.lua b/darwin/sketchybar/config/settings.lua new file mode 100644 index 0000000..9e7d5fc --- /dev/null +++ b/darwin/sketchybar/config/settings.lua @@ -0,0 +1,6 @@ +return { + paddings = 2, + group_paddings = 5, + icons = "sf-symbols", -- alternatively available: NerdFont + font = require "helpers.default_font", +} diff --git a/darwin/sketchybar/config/stylua.toml b/darwin/sketchybar/config/stylua.toml new file mode 100644 index 0000000..ecb6dca --- /dev/null +++ b/darwin/sketchybar/config/stylua.toml @@ -0,0 +1,6 @@ +column_width = 120 +line_endings = "Unix" +indent_type = "Spaces" +indent_width = 2 +quote_style = "AutoPreferDouble" +call_parentheses = "None" diff --git a/darwin/sketchybar/default.nix b/darwin/sketchybar/default.nix new file mode 100644 index 0000000..bd21805 --- /dev/null +++ b/darwin/sketchybar/default.nix @@ -0,0 +1,22 @@ +{ + pkgs, + lib, + config, + ... +}: let + inherit (lib) mkEnableOption mkIf; + cfg = config.my.sketchybar; +in { + options.my.sketchybar = { + enable = mkEnableOption "sketchybar"; + }; + + config = mkIf cfg.enable { + services.sketchybar.enable = true; + hm.my.sketchybar.enable = true; + + fonts.packages = with pkgs; [ + sketchybar-app-font + ]; + }; +} diff --git a/darwin/sketchybar/home.nix b/darwin/sketchybar/home.nix new file mode 100644 index 0000000..d0e0ab9 --- /dev/null +++ b/darwin/sketchybar/home.nix @@ -0,0 +1,39 @@ +{ + pkgs, + lib, + config, + ... +}: let + inherit (lib) mkEnableOption mkIf; + cfg = config.my.sketchybar; + luaBasePackage = pkgs.lua5_4; + luaPackage = luaBasePackage.withPackages (ps: + with ps; [ + (pkgs.sbarlua.override {lua = luaBasePackage;}) + luafilesystem + ]); +in { + options.my.sketchybar = { + enable = mkEnableOption "sketchybar"; + }; + + config = mkIf cfg.enable { + home.file.".config/sketchybar/lua".source = + config.lib.file.mkOutOfStoreSymlink + "${config.home.homeDirectory}/Projects/nix-dots/darwin/sketchybar/config"; + + home.file.".config/sketchybar/sketchybarrc" = { + text = '' + #!${luaPackage}/bin/lua + + local lfs = require("lfs") + lfs.chdir("lua") + + -- Load the sketchybar-package and prepare the helper binaries + require("helpers") + require("init") + ''; + executable = true; + }; + }; +} diff --git a/flakes/darwin.nix b/flakes/darwin.nix index 9f8f1f3..fa88a9c 100644 --- a/flakes/darwin.nix +++ b/flakes/darwin.nix @@ -36,7 +36,6 @@ in { hm = import ../user/environments/mac/home.nix; } - ../modules/yabai.nix ../machines/mac/configuration.nix ]; }; diff --git a/machines/mac/configuration.nix b/machines/mac/configuration.nix index f56efff..8c6f6d8 100644 --- a/machines/mac/configuration.nix +++ b/machines/mac/configuration.nix @@ -37,6 +37,11 @@ ]; }; + my = { + aerospace.enable = true; + sketchybar.enable = true; + }; + programs = { zsh = { enable = true; diff --git a/modules/yabai.nix b/modules/yabai.nix deleted file mode 100644 index 63cba8d..0000000 --- a/modules/yabai.nix +++ /dev/null @@ -1,82 +0,0 @@ -{...}: { - services.yabai = { - enable = true; - config = { - mouse_follows_focus = "off"; - focus_follows_mouse = "off"; - window_origin_display = "default"; - window_placement = "second_child"; - window_zoom_persist = "on"; - window_shadow = "on"; - window_animation_duration = 0; - window_animation_frame_rate = 120; - window_opacity_duration = 0; - active_window_opacity = 1; - normal_window_opacity = 0; - window_opacity = "off"; - insert_feedback_color = "0xffd75f5f"; - split_ratio = 0; - split_type = "auto"; - auto_balance = "off"; - top_padding = 0; - bottom_padding = 0; - left_padding = 0; - right_padding = 0; - window_gap = 6; - layout = "bsp"; - mouse_modifier = "fn"; - mouse_action1 = "move"; - mouse_action2 = "resize"; - mouse_drop_action = "swap"; - }; - }; - - services.skhd = { - enable = true; - skhdConfig = '' - ctrl + alt - h: yabai -m window --focus west - ctrl + alt - j: yabai -m window --focus south - ctrl + alt - k: yabai -m window --focus north - ctrl + alt - l: yabai -m window --focus east - - cmd + ctrl + alt - h : yabai -m window --warp west - cmd + ctrl + alt - j : yabai -m window --warp south - cmd + ctrl + alt - k : yabai -m window --warp north - cmd + ctrl + alt - l : yabai -m window --warp east - - ctrl + alt - 1 : yabai -m space --focus 1 - ctrl + alt - 2 : yabai -m space --focus 2 - ctrl + alt - 3 : yabai -m space --focus 3 - ctrl + alt - 4 : yabai -m space --focus 4 - ctrl + alt - 5 : yabai -m space --focus 5 - ctrl + alt - 6 : yabai -m space --focus 6 - ctrl + alt - 7 : yabai -m space --focus 7 - ctrl + alt - 8 : yabai -m space --focus 8 - ctrl + alt - 9 : yabai -m space --focus 9 - - ctrl + alt - q: yabai -m window --close - - ## stack window - # Note that this only works when the active window does *not* already belong to a stack - shift + alt + cmd - left : yabai -m window west --stack $(yabai -m query --windows --window | jq -r '.id') - shift + alt + cmd - right : yabai -m window east --stack $(yabai -m query --windows --window | jq -r '.id') - - # 0x21 - [ - # 0x1E - ] - ctrl + alt - 0x21 : yabai -m query --spaces --space \ - | jq -re ".index" \ - | xargs -I{} yabai -m query --windows --space {} \ - | jq -sre "add | map(select(.minimized != 1)) | sort_by(.display, .frame.y, .frame.y, .id) | nth(index(map(select(.focused == 1))) - 1).id" \ - | xargs -I{} yabai -m window --focus {} - - ctrl + alt - 0x1E : yabai -m query --spaces --space \ - | jq -re ".index" \ - | xargs -I{} yabai -m query --windows --space {} \ - | jq -sre "add | map(select(.minimized != 1)) | sort_by(.display, .frame.y, .frame.x, .id) | reverse | nth(index(map(select(.focused == 1))) - 1).id" \ - | xargs -I{} yabai -m window --focus {} - - # applications - ctrl + alt - t: open -n -a 'Alacritty.app' - ''; - }; -} diff --git a/pkgs/default.nix b/pkgs/default.nix index bb4c612..054fd6e 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -2,4 +2,5 @@ homer = pkgs.callPackage ./homer {}; keycloak-theme-keywind = pkgs.callPackage ./keywind {}; nvim-custom = import ./nvim/default.nix args; + sbarlua = pkgs.callPackage ./sbarlua {}; } diff --git a/pkgs/sbarlua/default.nix b/pkgs/sbarlua/default.nix new file mode 100644 index 0000000..e87e18c --- /dev/null +++ b/pkgs/sbarlua/default.nix @@ -0,0 +1,30 @@ +{ + clang, + fetchFromGitHub, + gcc, + readline, + lua, +}: +lua.pkgs.buildLuaPackage { + pname = "SBarLua"; + version = "0-unstable-2024-08-12"; + + src = fetchFromGitHub { + owner = "FelixKratz"; + repo = "SbarLua"; + rev = "437bd2031da38ccda75827cb7548e7baa4aa9978"; + hash = "sha256-F0UfNxHM389GhiPQ6/GFbeKQq5EvpiqQdvyf7ygzkPg="; + }; + + nativeBuildInputs = [ + clang + gcc + ]; + + buildInputs = [readline]; + + installPhase = '' + mkdir -p $out/lib/lua/${lua.luaversion}/ + cp -r bin/* "$out/lib/lua/${lua.luaversion}/" + ''; +}