feat(mac): add aerospace & sketchybar configs

This commit is contained in:
Michael Thomas 2025-05-10 22:19:01 -04:00
parent 03b48177e6
commit 44da2b9604
45 changed files with 2369 additions and 83 deletions

View File

@ -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"];
};
};
};
};
}

View File

@ -0,0 +1,3 @@
{
"runtime.version": "Lua 5.4"
}

View File

@ -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,
}

View File

@ -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

View File

@ -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,
}

View File

@ -0,0 +1 @@
bin

View File

@ -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",
}
}

View File

@ -0,0 +1,58 @@
#include <mach/mach.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdio.h>
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;
}

View File

@ -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 \"<event-name>\" \"<event_freq>\"\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;
}

View File

@ -0,0 +1,5 @@
bin/cpu_load: cpu_load.c cpu.h ../sketchybar.h | bin
clang -std=c99 -O3 $< -o $@
bin:
mkdir bin

View File

@ -0,0 +1,3 @@
all:
(cd cpu_load && $(MAKE))
(cd network_load && $(MAKE))

View File

@ -0,0 +1,5 @@
bin/network_load: network_load.c network.h ../sketchybar.h | bin
clang -std=c99 -O3 $< -o $@
bin:
mkdir bin

View File

@ -0,0 +1,90 @@
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <net/if.h>
#include <net/if_mib.h>
#include <sys/select.h>
#include <sys/sysctl.h>
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;
}
}

View File

@ -0,0 +1,42 @@
#include <unistd.h>
#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 \"<interface>\" \"<event-name>\" \"<event_freq>\"\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;
}

View File

@ -0,0 +1,122 @@
#pragma once
#include <mach/arm/kern_return.h>
#include <mach/mach.h>
#include <mach/mach_port.h>
#include <mach/message.h>
#include <bootstrap.h>
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
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);
}
}
}

View File

@ -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:",
}

View File

@ -0,0 +1 @@
os.execute("(cd helpers && make)")

View File

@ -0,0 +1,2 @@
# SbarLua
(git clone https://github.com/FelixKratz/SbarLua.git /tmp/SbarLua && cd /tmp/SbarLua/ && make install && rm -rf /tmp/SbarLua/)

View File

@ -0,0 +1,3 @@
all:
(cd event_providers && $(MAKE)) >/dev/null
(cd menus && $(MAKE)) >/dev/null

View File

@ -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

View File

@ -0,0 +1,248 @@
#include <Carbon/Carbon.h>
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;
}

View File

@ -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

View File

@ -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()

View File

@ -0,0 +1,5 @@
{
"diagnostics.globals": [
"sbar"
]
}

View File

@ -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)

View File

@ -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)

View File

@ -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")

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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,
})

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -0,0 +1,6 @@
return {
paddings = 2,
group_paddings = 5,
icons = "sf-symbols", -- alternatively available: NerdFont
font = require "helpers.default_font",
}

View File

@ -0,0 +1,6 @@
column_width = 120
line_endings = "Unix"
indent_type = "Spaces"
indent_width = 2
quote_style = "AutoPreferDouble"
call_parentheses = "None"

View File

@ -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
];
};
}

View File

@ -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;
};
};
}

View File

@ -36,7 +36,6 @@ in {
hm = import ../user/environments/mac/home.nix;
}
../modules/yabai.nix
../machines/mac/configuration.nix
];
};

View File

@ -37,6 +37,11 @@
];
};
my = {
aerospace.enable = true;
sketchybar.enable = true;
};
programs = {
zsh = {
enable = true;

View File

@ -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'
'';
};
}

View File

@ -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 {};
}

30
pkgs/sbarlua/default.nix Normal file
View File

@ -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}/"
'';
}