add aerospace config

This commit is contained in:
Ray Andrew 2025-04-07 02:23:57 -05:00
parent 0fbad1d781
commit 31b26268be
Signed by: rayandrew
SSH key fingerprint: SHA256:XYrYrxF0Z3A72n8P/p6mqPRNQZT22F88XcLsG+kX4xw
43 changed files with 2638 additions and 31 deletions

View file

@ -2,37 +2,56 @@
pkgs, pkgs,
lib, lib,
config, config,
home-dir,
... ...
}: }:
let
cfg = config.custom.aerospace;
in
{ {
options.custom = with lib; { options.custom = with lib; {
aerospace = { aerospace = {
enable = mkEnableOption "Enable aerospace"; enable = mkEnableOption "Enable aerospace";
logFile = mkOption {
type = types.str;
default = "${home-dir}/Library/Logs/aerospace.log";
description = "Filepath of log output";
};
}; };
}; };
config = lib.mkIf config.custom.aerospace.enable { config = lib.mkIf cfg.enable {
launchd.user.agents.aerospace.serviceConfig = {
StandardErrorPath = cfg.logFile;
StandardOutPath = cfg.logFile;
};
services.aerospace = { services.aerospace = {
enable = config.custom.aerospace.enable; enable = true;
settings = { settings = {
enable-normalization-flatten-containers = true; enable-normalization-flatten-containers = false;
enable-normalization-opposite-orientation-for-nested-containers = true; enable-normalization-opposite-orientation-for-nested-containers = true;
accordion-padding = 0; accordion-padding = 0;
on-focused-monitor-changed = [ "move-mouse monitor-lazy-center" ]; on-focused-monitor-changed = [ "move-mouse monitor-lazy-center" ];
default-root-container-layout = "tiles"; default-root-container-layout = "tiles";
default-root-container-orientation = "auto"; default-root-container-orientation = "auto";
# exec-on-workspace-change = [
# "/bin/bash"
# "-c"
# "sketchybar --trigger aerospace_workspace_change FOCUSED=$AEROSPACE_FOCUSED_WORKSPACE"
# ];
gaps = { gaps = {
inner = { inner = {
horizontal = 0; horizontal = 15;
vertical = 0; vertical = 15;
}; };
outer = { outer = {
left = 0; left = 10;
bottom = 0; bottom = 5;
top = 0; top = 12;
right = 0; right = 10;
}; };
}; };
@ -47,14 +66,25 @@
in in
"exec-and-forget osascript ${script}"; "exec-and-forget osascript ${script}";
# alt-enter = "exec-and-forget \"open -n -a /Applications/Slack.app\"";
alt-shift-f = "fullscreen"; alt-shift-f = "fullscreen";
alt-p = "layout floating tiling"; alt-space = "layout floating tiling";
alt-e = "layout tiles horizontal vertical"; alt-e = "layout tiles horizontal vertical";
alt-t = "layout accordion horizontal vertical"; alt-t = "layout h_accordion";
alt-s = "layout v_accordion";
alt-v = "split vertical";
alt-shift-v = "split horizontal";
# alt-v = "join-with down";
# alt-shift-v = "join-with right";
alt-h = "focus left"; alt-h = "focus left";
alt-l = "focus right"; alt-l = "focus right";
alt-j = "focus down";
alt-k = "focus up";
alt-shift-h = "move left"; alt-shift-h = "move left";
alt-shift-l = "move right"; alt-shift-l = "move right";
alt-shift-j = "move down";
alt-shift-k = "move up";
alt-1 = "workspace 1"; alt-1 = "workspace 1";
alt-2 = "workspace 2"; alt-2 = "workspace 2";
@ -69,46 +99,91 @@
alt-shift-1 = [ alt-shift-1 = [
"move-node-to-workspace 1" "move-node-to-workspace 1"
"workspace 1" # "workspace 1"
]; ];
alt-shift-2 = [ alt-shift-2 = [
"move-node-to-workspace 2" "move-node-to-workspace 2"
"workspace 2" # "workspace 2"
]; ];
alt-shift-3 = [ alt-shift-3 = [
"move-node-to-workspace 3" "move-node-to-workspace 3"
"workspace 3" # "workspace 3"
]; ];
alt-shift-4 = [ alt-shift-4 = [
"move-node-to-workspace 4" "move-node-to-workspace 4"
"workspace 4" # "workspace 4"
]; ];
alt-shift-5 = [ alt-shift-5 = [
"move-node-to-workspace 5" "move-node-to-workspace 5"
"workspace 5" # "workspace 5"
]; ];
alt-shift-6 = [ alt-shift-6 = [
"move-node-to-workspace 6" "move-node-to-workspace 6"
"workspace 6" # "workspace 6"
]; ];
alt-shift-7 = [ alt-shift-7 = [
"move-node-to-workspace 7" "move-node-to-workspace 7"
"workspace 7" # "workspace 7"
]; ];
alt-shift-8 = [ alt-shift-8 = [
"move-node-to-workspace 8" "move-node-to-workspace 8"
"workspace 8" # "workspace 8"
]; ];
alt-shift-9 = [ alt-shift-9 = [
"move-node-to-workspace 9" "move-node-to-workspace 9"
"workspace 9" # "workspace 9"
]; ];
alt-shift-0 = [ alt-shift-0 = [
"move-node-to-workspace 10" "move-node-to-workspace 10"
"workspace 10" # "workspace 10"
]; ];
alt-tab = "workspace-back-and-forth";
alt-shift-tab = "move-workspace-to-monitor --wrap-around next";
alt-r = "mode resize"; alt-r = "mode resize";
alt-shift-semicolon = "mode service";
};
};
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" ];
}; };
}; };
@ -124,11 +199,40 @@
}; };
}; };
workspace-to-monitor-force-assignment = {
"1" = "main";
"2" = "main";
"3" = "main";
"4" = "main";
"5" = "main";
"6" = "main";
"7" = "main";
"8" = "main";
"9" = "main";
"10" = [
"secondary"
"main"
];
};
on-window-detected = [ on-window-detected = [
{
"if".app-id = "com.spotify.client";
run = [ "move-node-to-workspace 7" ];
}
{
"if".app-id = "app.zen-browser.zen";
run = [ "move-node-to-workspace 10" ];
}
{ {
"if".app-id = "com.mitchellh.ghostty"; "if".app-id = "com.mitchellh.ghostty";
run = [ "layout tiling" ]; run = [ "layout tiling" ];
} }
{
"if".app-id = "com.tinyspeck.slackmacgap";
run = [ "move-node-to-workspace 9" ];
}
]; ];
}; };
}; };

View file

@ -5,13 +5,16 @@
pkgs, pkgs,
system, system,
host, host,
user,
... ...
}: }:
{ {
imports = [ imports = [
./aerospace.nix ./aerospace.nix
./homebrew.nix ./homebrew.nix
./jankyborders.nix
./keyboard.nix ./keyboard.nix
./sketchybar
]; ];
options.custom = with lib; { options.custom = with lib; {
@ -49,10 +52,101 @@
}; };
config = { config = {
# networking system.defaults = {
# networking.hostName = host; CustomUserPreferences = {
# networking.hostId = builtins.substring 0 8 (builtins.hashString "md5" config.networking.hostName); NSGlobalDomain = {
# networking.networkmanager.enable = true; WebKitDeveloperExtras = true;
# AppleHighlightColor = "0.65098 0.85490 0.58431";
# AppleAccentColor = 1;
};
"com.apple.finder" = {
DisableAllAnimations = true;
ShowExternalHardDrivesOnDesktop = false;
ShowMountedServersOnDesktop = false;
ShowRemovableMediaOnDesktop = false;
ShowHardDrivesOnDesktop = false;
_FXSortFoldersFirst = true;
# search the current folder by default
FXDefaultSearchScope = "SCcf";
};
"com.apple.NetworkBrowser" = {
BrowseAllInterfaces = 1;
};
"com.apple.DesktopServices" = {
DSDontWriteNetworkStores = true;
};
# "com.apple.Safari" = {
# AutoOpenSafeDownloads = false;
# IncludeDevelopMenu = true;
# WebKitDeveloperExtrasEnabledPreferenceKey = true;
# "com.apple.Safari.ContentPageGroupIdentifier.WebKit2DeveloperExtrasEnabled" = true;
# };
# "com.apple.mail" = {
# AddressesIncludeNameOnPasteboard = false;
# };
"net.sourceforge.skim-app.skim" = {
SKPSConversionCommand = "${pkgs.custom.ps2pdfcrop}/bin/ps2pdfcrop";
};
};
screencapture = {
disable-shadow = true;
location = "/Users/${user}/Screenshots";
type = "png";
};
trackpad = {
Clicking = true;
Dragging = true;
TrackpadRightClick = true;
# TrackpadThreeFingerDrag = true;
};
dock = {
autohide = true;
expose-animation-duration = 0.0;
mineffect = "scale";
minimize-to-application = true;
mru-spaces = false;
orientation = "left";
show-recents = false;
wvous-br-corner = 1; # Disabled
};
finder = {
AppleShowAllExtensions = true;
FXDefaultSearchScope = "SCcf";
FXEnableExtensionChangeWarning = false;
FXPreferredViewStyle = "Nlsv";
AppleShowAllFiles = true;
_FXShowPosixPathInTitle = true;
ShowPathbar = true;
ShowStatusBar = false;
};
#
# NSGlobalDomain = {
# # AppleMeasurementUnits = "Centimeters";
# AppleMetricUnits = 1;
# AppleShowAllExtensions = true;
# # AppleTemperatureUnit = "Celsius";
# # AppleInterfaceStyle = "Dark";
# AppleInterfaceStyle = null; # -- light mode
# AppleInterfaceStyleSwitchesAutomatically = false;
# InitialKeyRepeat = 20;
# KeyRepeat = 2;
# NSAutomaticCapitalizationEnabled = false;
# NSAutomaticDashSubstitutionEnabled = false;
# NSAutomaticPeriodSubstitutionEnabled = false;
# NSAutomaticQuoteSubstitutionEnabled = false;
# NSAutomaticSpellingCorrectionEnabled = false;
# NSDisableAutomaticTermination = true;
# NSAutomaticWindowAnimationsEnabled = false;
# NSDocumentSaveNewDocumentsToCloud = false;
# NSNavPanelExpandedStateForSaveMode = true;
# NSNavPanelExpandedStateForSaveMode2 = true;
# NSTableViewDefaultSizeMode = 2;
# NSWindowResizeTime = 1.0e-4;
# PMPrintingExpandedStateForPrint = true;
# PMPrintingExpandedStateForPrint2 = true;
# };
# LaunchServices.LSQuarantine = false;
};
environment.systemPackages = environment.systemPackages =
with pkgs; with pkgs;

View file

@ -17,6 +17,11 @@
ms-office = mkEnableOption "Enable MS Office"; ms-office = mkEnableOption "Enable MS Office";
ms-teams = mkEnableOption "Enable MS Teams"; ms-teams = mkEnableOption "Enable MS Teams";
spotify = mkEnableOption "Enable Spotify"; spotify = mkEnableOption "Enable Spotify";
raycast = mkEnableOption "Enable Raycast";
whatsapp = mkEnableOption "Enable Whatsapp";
vscode = mkEnableOption "Enable VSCode";
firefox = mkEnableOption "Enable Firefox";
chromium = mkEnableOption "Enable Chromium";
}; };
config = lib.mkMerge [ config = lib.mkMerge [
@ -83,5 +88,30 @@
"microsoft-teams" "microsoft-teams"
]; ];
}) })
(lib.mkIf config.custom.brew.raycast {
homebrew.casks = [
"raycast"
];
})
(lib.mkIf config.custom.brew.whatsapp {
homebrew.casks = [
"whatsapp"
];
})
(lib.mkIf config.custom.brew.vscode {
homebrew.casks = [
"visual-studio-code"
];
})
(lib.mkIf config.custom.brew.firefox {
homebrew.casks = [
"firefox"
];
})
(lib.mkIf config.custom.brew.chromium {
homebrew.casks = [
"chromium"
];
})
]; ];
} }

33
darwin/jankyborders.nix Normal file
View file

@ -0,0 +1,33 @@
{
pkgs,
lib,
config,
dots,
home-dir,
...
}:
let
cfg = config.custom.jankyborders;
in
{
options.custom = with lib; {
jankyborders = {
enable = mkEnableOption "Enable jankyborders";
logFile = mkOption {
type = types.str;
default = "${home-dir}/Library/Logs/jankyborders.log";
description = "Filepath of log output";
};
};
};
config = lib.mkIf cfg.enable {
services.jankyborders = {
enable = true;
active_color = "0xFFA1EFE4";
inactive_color = "0xFFd3b58d";
width = 5.0;
};
};
}

View file

@ -0,0 +1,321 @@
-- these seem to get disentangled to https://github.com/kvndrsslr/sketchybar-app-font/tree/main/mappings
return {
["Live"] = ":ableton:",
["Activity Monitor"] = ":numbers:", -- no pre-existing, sadly
["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:",
["Notion Calendar"] = ":calendar:",
["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:",
["Vesktop"] = ":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:",
["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:",
["MAMP"] = ":mamp:",
["MAMP PRO"] = ":mamp:",
["Maps"] = ":maps:",
["News"] = ":sioyek:",
["Ghostty"] = ":terminal:",
["Google Maps"] = ":maps:",
["Marta"] = ":marta:",
["Matlab"] = ":matlab:",
["Mattermost"] = ":mattermost:",
["Messages"] = ":messages:",
["信息"] = ":messages:",
["Nachrichten"] = ":messages:",
["Messenger"] = ":messenger:",
["Microsoft Edge"] = ":microsoft_edge:",
["Microsoft Excel"] = ":microsoft_excel:",
["Microsoft Outlook"] = ":microsoft_outlook:",
["Microsoft PowerPoint"] = ":microsoft_power_point:",
["Microsoft Remote Desktop"] = ":microsoft_remote_desktop:",
["Microsoft Teams"] = ":microsoft_teams:",
["Microsoft Teams (work or school)"] = ":microsoft_teams:",
["Microsoft Word"] = ":microsoft_word:",
["Min"] = ":min_browser:",
["Miro"] = ":miro:",
["MongoDB Compass"] = ":mongodb:",
["mpv"] = ":mpv:",
["Mullvad Browser"] = ":mullvad_browser:",
["Music"] = ":music:",
["音乐"] = ":music:",
["Musique"] = ":music:",
["PWNeovide"] = ":neovide:",
["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:",
["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 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:",
["PrusaSlicer"] = ":prusaslicer:",
["SuperSlicer"] = ":prusaslicer:",
["PyCharm"] = ":pycharm:",
["QQ"] = ":qq:",
["QQ音乐"] = ":qqmusic:",
["QQMusic"] = ":qqmusic:",
["Quantumult X"] = ":quantumult_x:",
["qutebrowser"] = ":qute_browser:",
["Raindrop.io"] = ":raindrop_io:",
["Reeder"] = ":reeder5:",
["Reminders"] = ":reminders:",
["提醒事项"] = ":reminders:",
["Rappels"] = ":reminders:",
["Replit"] = ":replit:",
["Rider"] = ":rider:",
["JetBrains Rider"] = ":rider:",
["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:",
["Strongbox"] = ":one_password:",
["superProductivity"] = ":superproductivity:",
["Tana"] = ":tana:",
["TeamSpeak 3"] = ":team_speak:",
["Telegram"] = ":telegram:",
["Terminal"] = ":terminal:",
["终端"] = ":terminal:",
["Typora"] = ":text:",
["Microsoft To Do"] = ":things:",
["Things"] = ":things:",
["Thunderbird"] = ":thunderbird:",
["TickTick"] = ":tick_tick:",
["TIDAL"] = ":tidal:",
["Tiny RDM"] = ":tinyrdm:",
["Todoist"] = ":todoist:",
["Toggl Track"] = ":toggl_track:",
["Tor Browser"] = ":tor_browser:",
["Tower"] = ":tower:",
["Transmit"] = ":transmit:",
["Trello"] = ":trello:",
["Tweetbot"] = ":twitter:",
["Twitter"] = ":twitter:",
["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:",
["Zen"] = ":zen_browser:",
}

View file

@ -0,0 +1,23 @@
#!/usr/bin/env lua
local colors = require("colors")
local bar_height = 35
sbar.bar({
blur_radius = 20,
border_color = colors.bar.border,
border_width = 2,
color = colors.bar.bg,
corner_radius = 5,
height = bar_height,
margin = 10,
notch_width = 0,
padding_left = 10,
padding_right = 10,
position = "top",
shadow = true,
sticky = true,
topmost = false,
y_offset = 2,
})

View file

@ -0,0 +1,24 @@
local function with_alpha(color, alpha)
if alpha > 1.0 or alpha < 0.0 then return color end
return (color & 0x00ffffff) | (math.floor(alpha * 255.0) << 24)
end
return {
black = 0xff181819,
white = 0xffd3b58d,
red = 0xffF92672,
blue = 0xff66D9EF,
grey = 0xff7f8490,
transparent = 0x00000000,
bar = {
bg = 0xff072626,
border = 0xffFD971F,
},
popup = {
bg = with_alpha(0xff072626, 0.6),
border = 0xffFD971F,
},
with_alpha = with_alpha,
}

View file

@ -0,0 +1,92 @@
local settings = require("settings")
local icons = {
sf_symbols = {
plus = "􀅼",
loading = "􀖇",
apple = "􀣺",
gear = "􀍟",
cpu = "􀫥",
clipboard = "􀉄",
switch = {
on = "􁏮",
off = "􁏯",
},
volume = {
_100="􀊩",
_66="􀊧",
_33="􀊥",
_10="􀊡",
_0="􀊣",
},
battery = {
_100 = "􀛨",
_75 = "􀺸",
_50 = "􀺶",
_25 = "􀛩",
_0 = "􀛪",
charging = "􀢋"
},
wifi = {
upload = "􀄨",
download = "􀄩",
connected = "􀙇",
disconnected = "􀙈",
router = "􁓤",
},
media = {
back = "􀊊",
forward = "􀊌",
play_pause = "􀊈",
},
},
-- Alternative NerdFont icons
nerdfont = {
plus = "",
loading = "",
apple = "",
gear = "",
cpu = "",
clipboard = "Missing Icon",
switch = {
on = "󱨥",
off = "󱨦",
},
volume = {
_100="",
_66="",
_33="",
_10="",
_0="",
},
battery = {
_100 = "",
_75 = "",
_50 = "",
_25 = "",
_0 = "",
charging = ""
},
wifi = {
upload = "",
download = "",
connected = "󰖩",
disconnected = "󰖪",
router = "Missing Icon"
},
media = {
back = "",
forward = "",
play_pause = "",
},
},
}
if not (settings.icons == "NerdFont") then
return icons.sf_symbols
else
return icons.nerdfont
end

View file

@ -0,0 +1,56 @@
#!/usr/bin/env lua
local colors = require("colors")
local settings = require("settings")
sbar.default({
updates = "when_shown",
icon = {
font = {
family = settings.font.text,
style = "Bold",
size = 13.0,
},
color = colors.white,
padding_left = settings.paddings,
padding_right = settings.paddings,
background = { image = { corner_radius = 9 } },
},
label = {
font = {
family = settings.font.text,
style = "Semibold",
size = 13.0,
},
color = colors.white,
padding_left = settings.paddings,
padding_right = settings.paddings,
},
background = {
height = 30,
corner_radius = 12,
border_width = 1,
border_color = colors.bar.border,
image = {
corner_radius = 8,
border_color = colors.grey,
border_width = 1,
},
},
popup = {
background = {
border_width = 1,
corner_radius = 8,
border_color = colors.popup.border,
color = colors.popup.bg,
shadow = { drawing = true },
},
blur_radius = 20,
},
padding_left = 5,
padding_right = 5,
scroll_texts = true,
})
require("bar")
require("items")

View file

@ -0,0 +1,247 @@
local Promise = require("promise")
local colors = require("colors")
local utils = require("utils")
local settings = require("settings")
local app_icons = require("app_icons")
function parse_string_to_table(s)
local result = {}
for line in s:gmatch("([^\n]+)") do
table.insert(result, line)
end
return result
end
local function getAllWorkspaces()
return utils.sbarExecP("aerospace list-workspaces --all --format '%{workspace}%{monitor-appkit-nsscreen-screens-id}%{monitor-id}%{monitor-name}' --json")
end
local function getVisibleWorkspaces()
return utils.sbarExecP("aerospace list-workspaces --visible --monitor all --format '%{workspace}%{monitor-appkit-nsscreen-screens-id}%{monitor-id}%{monitor-name}' --json")
end
local function getAllWindows()
return utils.sbarExecP(
"aerospace list-windows --all --format '%{app-name}%{window-title}%{workspace}%{monitor-id}%{monitor-appkit-nsscreen-screens-id}%{monitor-name}' --json"
)
end
local function getMonitorId(obj)
if obj["monitor-appkit-nsscreen-screens-id"] then
return obj["monitor-appkit-nsscreen-screens-id"]
end
return obj["monitor-id"]
end
local spaces = {}
local space_paddings = {}
local brackets = {}
local state = {
workspaces = {},
updating = false,
}
function getState()
local newstate = {
workspaces = {},
}
for workspaceid, space in pairs(spaces) do
newstate.workspaces[workspaceid] = {
monitor = 0,
active = false,
empty = true,
apps = {},
appicons = "",
}
end
return Promise.all({ getAllWorkspaces(), getVisibleWorkspaces(), getAllWindows() }):thenCall(function(values)
local all, visible, apps = values[1], values[2], values[3]
for _, workspace in ipairs(all) do
local workspaceid = workspace["workspace"]
newstate.workspaces[workspaceid]["id"] = workspaceid
newstate.workspaces[workspaceid]["monitor"] = getMonitorId(workspace)
end
for _, workspace in ipairs(visible) do
local workspaceid = workspace["workspace"]
newstate.workspaces[workspaceid]["active"] = true
end
for _, window in ipairs(apps) do
local workspaceid = window["workspace"]
local appname = window["app-name"]
newstate.workspaces[workspaceid]["apps"][appname] = true
newstate.workspaces[workspaceid]["empty"] = false
end
for workspaceid, workspacestate in pairs(newstate.workspaces) do
local appkeys = {}
for app in pairs(workspacestate["apps"]) do
table.insert(appkeys, app)
end
table.sort(appkeys)
if #appkeys > 0 then
for _, app in ipairs(appkeys) do
local lookup = app_icons[app]
local icon = ((lookup == nil) and app_icons["Default"] or lookup)
workspacestate["appicons"] = workspacestate["appicons"] .. " " .. icon
end
else
workspacestate["appicons"] = ""
end
-- print(utils.dump(workspacestate))
end
return newstate
end)
end
local function updateState()
if not state.updating then
state.updating = true
return getState():thenCall(function(newstate)
state.workspaces = newstate.workspaces
state.updating = false
end)
end
return Promise.reject("State is already updating")
end
local function highlightSpace(space, space_padding, space_bracket, selected)
space:set({
drawing = true,
icon = { highlight = selected, },
label = { highlight = selected },
-- background = { border_color = selected and colors.white or colors.bg2 }
})
space_padding:set({
drawing = true,
})
if space_bracket then
space_bracket:set({
-- background = { border_color = selected and colors.grey or colors.bg2 },
})
end
end
local function onActiveSpaceChange(env)
local focused_workspace = env.FOCUSED_WORKSPACE
local last_workspace = env.PREV_WORKSPACE
-- print("aerospace_workspace_change from " .. last_workspace .. " to " .. focused_workspace)
local space = spaces[focused_workspace]
local space_padding = space_paddings[focused_workspace]
local prev_space = spaces[last_workspace]
local prev_space_padding = space_paddings[last_workspace]
sbar.animate("tanh", 10, function()
highlightSpace(space, space_padding, nil, true)
if state.workspaces[last_workspace]["monitor"] == state.workspaces[focused_workspace]["monitor"] then
highlightSpace(prev_space, prev_space_padding, nil, false)
end
end)
updateState()
end
local function syncState()
sbar.animate("tanh", 10, function()
for workspaceid, workspacestate in pairs(state.workspaces) do
if not workspacestate["empty"] then
spaces[workspaceid]:set({
drawing = true,
display = workspacestate["monitor"],
-- label = {
-- string = workspaceid,
-- highlight = workspacestate["active"],
-- },
-- icon = {
-- string = workspaceid,
-- color = colors.white,
-- highlight = workspacestate["active"],
-- },
label = {
string = workspacestate["appicons"],
highlight = workspacestate["active"],
},
icon = {
highlight = workspacestate["active"],
},
})
space_paddings[workspaceid]:set({ drawing = true })
else
-- These should be hidden
spaces[workspaceid]:set({
drawing = false,
display = workspacestate["monitor"],
label = workspacestate["appicons"],
})
space_paddings[workspaceid]:set({ drawing = false })
end
end
end)
end
local function updateStateAndSync()
return updateState():thenCall(syncState)
end
function setup()
getAllWorkspaces():thenCall(function(workspaces)
for _, workspace in ipairs(workspaces) do
local workspaceid = workspace["workspace"]
local display = getMonitorId(workspace)
local space = sbar.add("item", "space." .. workspaceid, {
drawing = false, -- default to not showing the space -- we'll show if it has windows or is activated
updates = "when_shown",
display = display,
icon = {
string = workspaceid,
color = colors.white,
highlight_color = colors.red,
},
label = {
padding_right = 12,
color = colors.grey,
highlight_color = colors.white,
font = "sketchybar-app-font:Regular:14.0",
y_offset = -1,
-- drawing = false
},
padding_left = 1,
padding_right = 1,
click_script = "aerospace workspace " .. workspaceid,
})
spaces[workspaceid] = space
local padding = sbar.add("space", "space.padding." .. space.name, {
drawing = false,
updates = "when_shown",
display = display,
script = "",
width = settings.space_paddings,
})
space_paddings[workspaceid] = padding
end
end)
:thenCall(function()
local space_window_observer = sbar.add("item", {
drawing = false,
updates = true,
})
space_window_observer:subscribe("aerospace_workspace_change", onActiveSpaceChange)
space_window_observer:subscribe("space_windows_change", updateStateAndSync)
space_window_observer:subscribe("system_woke", updateStateAndSync)
space_window_observer:subscribe("front_app_switched", updateStateAndSync)
end)
:thenCall(updateStateAndSync)
end
setup()

View file

@ -0,0 +1,36 @@
local colors = require("colors")
local icons = require("icons")
local settings = require("settings")
-- Padding item required because of bracket
sbar.add("item", { width = 5 })
local apple = sbar.add("item", {
icon = {
font = { size = 16.0 },
string = icons.apple,
padding_right = settings.paddings,
padding_left = 0,
},
label = { drawing = false },
background = {
color = colors.bg2,
-- border_color = colors.black,
-- border_width = 1
},
padding_left = 1,
padding_right = 1,
click_script = "sk-menus -s 0"
})
-- Double border for apple using a single item bracket
-- sbar.add("bracket", { apple.name }, {
-- background = {
-- color = colors.transparent,
-- height = 30,
-- -- border_color = colors.grey,
-- }
-- })
-- Padding item required because of bracket
sbar.add("item", { width = 7 })

View file

@ -0,0 +1,45 @@
local icons = require("icons")
local battery = sbar.add("item", {
position = "right",
icon = {
font = {
style = "Regular",
size = 19.0,
}
},
label = { drawing = false },
update_freq = 120,
})
local function battery_update()
sbar.exec("pmset -g batt", function(batt_info)
local icon = "!"
if (string.find(batt_info, 'AC Power')) then
icon = icons.battery.charging
else
local found, _, charge = batt_info:find("(%d+)%%")
if found then
charge = tonumber(charge)
end
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 > 20 then
icon = icons.battery._25
else
icon = icons.battery._0
end
end
battery:set({ icon = icon })
end)
end
battery:subscribe({"routine", "power_source_change", "system_woke"}, battery_update)

View file

@ -0,0 +1,44 @@
local colors = require("colors")
local settings = require("settings")
-- Padding item required because of bracket
sbar.add("item", { position = "right", width = settings.group_paddings })
local cal = sbar.add("item", {
icon = {
color = colors.white,
padding_left = 0,
font = { size = 13 },
},
label = {
color = colors.white,
padding_right = 0,
align = "right",
},
position = "right",
update_freq = 30,
padding_left = 0,
padding_right = 0,
click_script = "open -n -a Calendar",
-- background = {
-- color = colors.bg1,
-- border_color = colors.bar.border,
-- border_width = 1,
-- },
})
-- Double border for calendar using a single item bracket
-- sbar.add("bracket", { cal.name }, {
-- background = {
-- color = colors.transparent,
-- height = 30,
-- border_color = colors.bar.bg,
-- },
-- })
-- Padding item required because of bracket
sbar.add("item", { position = "right", width = settings.group_paddings })
cal:subscribe({ "forced", "routine", "system_woke" }, function(env)
cal:set({ icon = os.date("􀉉 %B %d %a"), label = os.date("􀐫 %H:%M") })
end)

View file

@ -0,0 +1,69 @@
local icons = require("icons")
local colors = require("colors")
local settings = require("settings")
-- Execute the event provider binary which provides the event "cpu_update" for
-- the cpu load data, which is fired every 2.0 seconds.
sbar.exec("killall sk-cpu-load >/dev/null; sk-cpu-load cpu_update 2.0")
local cpu = sbar.add("graph", "widgets.cpu" , 42, {
position = "right",
graph = { color = colors.blue },
background = {
height = 22,
color = { alpha = 0 },
border_color = { alpha = 0 },
drawing = true,
},
icon = { string = icons.cpu },
label = {
string = "cpu ??%",
font = {
family = settings.font.numbers,
style = settings.font.style_map["Bold"],
size = 9.0,
},
align = "right",
padding_right = 0,
width = 0,
y_offset = 4
},
padding_right = settings.paddings + 6
})
cpu:subscribe("cpu_update", function(env)
-- Also available: env.user_load, env.sys_load
local load = tonumber(env.total_load)
cpu:push({ load / 100. })
local color = colors.blue
if load > 30 then
if load < 60 then
color = colors.yellow
elseif load < 80 then
color = colors.orange
else
color = colors.red
end
end
cpu:set({
graph = { color = color },
label = "cpu " .. env.total_load .. "%",
})
end)
cpu:subscribe("mouse.clicked", function(env)
sbar.exec("open -a 'Activity Monitor'")
end)
-- Background around the cpu item
sbar.add("bracket", "widgets.cpu.bracket", { cpu.name }, {
background = { color = colors.bg1 }
})
-- Background around the cpu item
sbar.add("item", "widgets.cpu.padding", {
position = "right",
width = settings.group_paddings
})

View file

@ -0,0 +1,28 @@
local settings = require("settings")
local front_app = sbar.add("item", {
icon = {
drawing = false
},
label = {
font = {
style = settings.font.style_map["Bold"],
size = 12.0,
}
}
})
front_app:subscribe("front_app_switched", function(env)
front_app:set({
label = {
string = env.INFO:upper()
}
})
-- Or equivalently:
-- sbar.set(env.NAME, {
-- label = {
-- string = env.INFO
-- }
-- })
end)

View file

@ -0,0 +1,9 @@
require("items.aerospace")
require("items.apple")
require("items.menu")
require("items.front_app")
require("items.battery")
require("items.cal")
require("items.volume")
require("items.cpu")
require("items.wifi")

View file

@ -0,0 +1,76 @@
local colors = require("colors")
local icons = require("icons")
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, {
padding_left = settings.paddings,
padding_right = settings.paddings,
drawing = false,
icon = { drawing = false },
label = {
font = {
style = settings.font.style_map[i == 1 and "Heavy" or "Semibold"]
},
padding_left = 6,
padding_right = 6,
},
click_script = "sk-menus -s " .. i,
})
menu_items[i] = menu
end
sbar.add("bracket", { '/menu\\..*/' }, {
background = { color = colors.bg1 }
})
local menu_padding = sbar.add("item", "menu.padding", {
drawing = false,
width = 5
})
local function update_menus(env)
sbar.exec("sk-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 })
sbar.set("front_app", { drawing = true })
else
menu_watcher:set( { updates = true })
sbar.set("/space\\..*/", { drawing = false })
sbar.set("front_app", { drawing = false })
update_menus()
end
end)
return menu_watcher

View file

@ -0,0 +1,79 @@
local colors = require("colors")
local icons = require("icons")
local volume_slider = sbar.add("slider", 100, {
position = "right",
updates = true,
label = { drawing = false },
icon = { drawing = false },
slider = {
highlight_color = colors.blue,
width = 0,
background = {
height = 6,
corner_radius = 3,
color = colors.bg2,
},
knob= {
string = "􀀁",
drawing = false,
},
},
})
local volume_icon = sbar.add("item", {
position = "right",
icon = {
string = icons.volume._100,
width = 0,
align = "left",
color = colors.grey,
font = {
style = "Regular",
size = 14.0,
},
},
label = {
width = 25,
align = "left",
font = {
style = "Regular",
size = 14.0,
},
},
})
volume_slider:subscribe("mouse.clicked", function(env)
sbar.exec("osascript -e 'set volume output volume " .. env["PERCENTAGE"] .. "'")
end)
volume_slider:subscribe("volume_change", function(env)
local volume = tonumber(env.INFO)
local icon = icons.volume._0
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({ label = icon })
volume_slider:set({ slider = { percentage = volume } })
end)
local function animate_slider_width(width)
sbar.animate("tanh", 30.0, function()
volume_slider:set({ slider = { width = width }})
end)
end
volume_icon:subscribe("mouse.clicked", function()
if tonumber(volume_slider:query().slider.width) > 0 then
animate_slider_width(0)
else
animate_slider_width(100)
end
end)

View file

@ -0,0 +1,234 @@
local icons = require("icons")
local colors = require("colors")
local settings = require("settings")
-- Execute the event provider binary which provides the event "network_update"
-- for the network interface "en0", which is fired every 2.0 seconds.
sbar.exec("killall sk-network-load >/dev/null; sk-network-load en0 network_update 2.0")
local popup_width = 250
local wifi_up = sbar.add("item", "widgets.wifi1", {
position = "right",
padding_left = -5,
width = 0,
icon = {
padding_right = 0,
font = {
style = settings.font.style_map["Bold"],
size = 9.0,
},
string = icons.wifi.upload,
},
label = {
font = {
family = settings.font.numbers,
style = settings.font.style_map["Bold"],
size = 9.0,
},
color = colors.red,
string = "??? Bps",
},
y_offset = 4,
})
local wifi_down = sbar.add("item", "widgets.wifi2", {
position = "right",
padding_left = -5,
icon = {
padding_right = 0,
font = {
style = settings.font.style_map["Bold"],
size = 9.0,
},
string = icons.wifi.download,
},
label = {
font = {
family = settings.font.numbers,
style = settings.font.style_map["Bold"],
size = 9.0,
},
color = colors.blue,
string = "??? Bps",
},
y_offset = -4,
})
local wifi = sbar.add("item", "widgets.wifi.padding", {
position = "right",
label = { drawing = false },
})
-- Background around the item
local wifi_bracket = sbar.add("bracket", "widgets.wifi.bracket", {
wifi.name,
wifi_up.name,
wifi_down.name
}, {
-- background = { color = colors.bar.bg },
popup = { align = "center", height = 30 }
})
local ssid = sbar.add("item", {
position = "popup." .. wifi_bracket.name,
icon = {
font = {
style = settings.font.style_map["Bold"]
},
string = icons.wifi.router,
},
width = popup_width,
align = "center",
label = {
font = {
size = 15,
style = settings.font.style_map["Bold"]
},
max_chars = 18,
string = "????????????",
},
background = {
height = 2,
color = colors.bar.bg,
y_offset = -15
}
})
local hostname = sbar.add("item", {
position = "popup." .. wifi_bracket.name,
icon = {
align = "left",
string = "Hostname:",
width = popup_width / 2,
},
label = {
max_chars = 20,
string = "????????????",
width = popup_width / 2,
align = "right",
}
})
local ip = sbar.add("item", {
position = "popup." .. wifi_bracket.name,
icon = {
align = "left",
string = "IP:",
width = popup_width / 2,
},
label = {
string = "???.???.???.???",
width = popup_width / 2,
align = "right",
}
})
local mask = sbar.add("item", {
position = "popup." .. wifi_bracket.name,
icon = {
align = "left",
string = "Subnet mask:",
width = popup_width / 2,
},
label = {
string = "???.???.???.???",
width = popup_width / 2,
align = "right",
}
})
local router = sbar.add("item", {
position = "popup." .. wifi_bracket.name,
icon = {
align = "left",
string = "Router:",
width = popup_width / 2,
},
label = {
string = "???.???.???.???",
width = popup_width / 2,
align = "right",
},
})
sbar.add("item", { position = "right", width = settings.group_paddings })
wifi_up:subscribe("network_update", function(env)
local up_color = (env.upload == "000 Bps") and colors.grey or colors.red
local down_color = (env.download == "000 Bps") and colors.grey or colors.blue
wifi_up:set({
icon = { color = up_color },
label = {
string = env.upload,
color = up_color
}
})
wifi_down:set({
icon = { color = down_color },
label = {
string = env.download,
color = down_color
}
})
end)
wifi:subscribe({"wifi_change", "system_woke"}, function(env)
sbar.exec("ipconfig getifaddr en0", function(ip)
local connected = not (ip == "")
wifi:set({
icon = {
string = connected and icons.wifi.connected or icons.wifi.disconnected,
color = connected and colors.white or colors.red,
},
})
end)
end)
local function hide_details()
wifi_bracket:set({ popup = { drawing = false } })
end
local function toggle_details()
local should_draw = wifi_bracket:query().popup.drawing == "off"
if should_draw then
wifi_bracket:set({ popup = { drawing = true }})
sbar.exec("networksetup -getcomputername", function(result)
hostname:set({ label = result })
end)
sbar.exec("ipconfig getifaddr en0", function(result)
ip:set({ label = result })
end)
sbar.exec("ipconfig getsummary en0 | awk -F ' SSID : ' '/ SSID : / {print $2}'", function(result)
ssid:set({ label = result })
end)
sbar.exec("networksetup -getinfo Wi-Fi | awk -F 'Subnet mask: ' '/^Subnet mask: / {print $2}'", function(result)
mask: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_up:subscribe("mouse.clicked", toggle_details)
wifi_down:subscribe("mouse.clicked", toggle_details)
wifi:subscribe("mouse.clicked", toggle_details)
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
ssid:subscribe("mouse.clicked", copy_label_to_clipboard)
hostname:subscribe("mouse.clicked", copy_label_to_clipboard)
ip:subscribe("mouse.clicked", copy_label_to_clipboard)
mask:subscribe("mouse.clicked", copy_label_to_clipboard)
router:subscribe("mouse.clicked", copy_label_to_clipboard)

View file

@ -0,0 +1,19 @@
#!/usr/bin/env lua
return {
icons = "sf-symbols",
font = {
text = "SF Pro",
numbers = "SF Pro",
style_map = {
["Regular"] = "Regular",
["Semibold"] = "Semibold",
["Bold"] = "Bold",
["Heavy"] = "Heavy",
["Black"] = "Black",
},
},
paddings = 3,
group_paddings = 0,
space_paddings = 5,
}

View file

@ -0,0 +1,41 @@
local Promise = require("promise")
local M = {}
function M.dump(o)
if type(o) == "table" then
local s = "{"
for k, v in pairs(o) do
if type(k) ~= "number" then
k = '"' .. k .. '"'
end
s = s .. " [" .. k .. "] = " .. M.dump(v) .. ","
end
return s .. "} "
else
return tostring(o)
end
end
local function onErrorP(reason)
print("Error found: " .. (reason and M.dump(reason) or "unknown"))
end
-- https://github.com/Tnixc/nix-config/blob/main/home/programs/aerospace-sketchybar/sbar-config-libs/items/aerospaces.lua
function M.sbarExecP(cmd)
return Promise.new(function(resolve, failfunc)
sbar.exec(cmd, function(result, exit_code)
if exit_code ~= 0 then
if failfunc ~= nil then
failfunc(string.format("Exit Code: %s Message: %s", tostring(exit_code), M.dump(result)))
end
else
if resolve ~= nil then
resolve(result)
end
end
end)
end):catch(onErrorP)
end
return M

View file

@ -0,0 +1,88 @@
{
pkgs,
lib,
config,
dots,
home-dir,
...
}:
let
cfg = config.custom.sketchybar;
sketchybar = lib.getExe pkgs.sketchybar;
lua = pkgs.lua5_4.withPackages (
ps: with ps; [
pkgs.custom.sbarlua
pkgs.custom.promise-lua
luafilesystem
]
);
in
{
options.custom = with lib; {
sketchybar = {
enable = mkEnableOption "Enable sketchybar";
logFile = mkOption {
type = types.str;
default = "${home-dir}/Library/Logs/sketchybar.log";
description = "Filepath of log output";
};
};
};
config = lib.mkIf cfg.enable {
services.sketchybar = {
enable = true;
extraPackages = with pkgs; [
custom.sk-utils
# sketchybar-app-font
];
};
hm.home.shellAliases = {
restart-sketchybar = ''launchctl kickstart -k gui/"$(id -u)"/org.nixos.sketchybar'';
};
hm.home.packages = with pkgs; [
sketchybar-app-font
];
hm.xdg.configFile = {
"sketchybar/sketchybarrc" = {
executable = true;
text = # Lua
''
#!${lua}/bin/lua
-- Add the sketchybar module to the package cpath (the module could be
-- installed into the default search path then this would not be needed)
package.path = "${dots}/darwin/sketchybar/config/?.lua;${dots}/darwin/sketchybar/config/?/?.lua;${dots}/darwin/sketchybar/config/?/init.lua;" .. package.path
sbar = require("sketchybar")
sbar.exec("killall sketchyhelper || sketchyhelper git.felix.sketchyhelper >/dev/null 2>&1 &")
sbar.begin_config()
require("init")
sbar.hotload(true)
sbar.end_config()
sbar.event_loop()
'';
};
};
services.aerospace.settings.exec-on-workspace-change = lib.mkIf config.custom.aerospace.enable [
"/bin/bash"
"-c"
"${sketchybar} --trigger aerospace_workspace_change FOCUSED_WORKSPACE=$AEROSPACE_FOCUSED_WORKSPACE PREV_WORKSPACE=$AEROSPACE_PREV_WORKSPACE"
];
launchd.user.agents.sketchybar.serviceConfig = {
StandardErrorPath = cfg.logFile;
StandardOutPath = cfg.logFile;
KeepAlive = lib.mkForce {
PathState = {
"/run/current-system/sw/bin/sketchybar" = true;
};
};
};
};
}

View file

@ -28,6 +28,7 @@
sd sd
ugrep ugrep
unzip unzip
fastfetch
# gpclient # gpclient
openconnect openconnect
fd fd

View file

@ -103,8 +103,6 @@
(lib.mkIf config.custom.gui.darwin.enable { (lib.mkIf config.custom.gui.darwin.enable {
home.packages = with pkgs; [ home.packages = with pkgs; [
raycast
custom.whatsapp-for-mac
]; ];
}) })

View file

@ -24,6 +24,8 @@
custom = { custom = {
aerospace.enable = true; aerospace.enable = true;
sketchybar.enable = true;
jankyborders.enable = true;
brew = { brew = {
zen-browser = true; zen-browser = true;
webex = true; webex = true;
@ -33,6 +35,11 @@
ghostty = true; ghostty = true;
ms-office = true; ms-office = true;
ms-teams = true; ms-teams = true;
raycast = true;
whatsapp = true;
vscode = true;
firefox = true;
chromium = true;
}; };
}; };
@ -43,8 +50,6 @@
default.enable = true; default.enable = true;
darwin.enable = true; darwin.enable = true;
ghostty.enable = true; ghostty.enable = true;
firefox.enable = true;
vscode.enable = true;
}; };
}; };
} }

View file

@ -33,6 +33,7 @@ let
specialArgs = specialArgs // { specialArgs = specialArgs // {
inherit host user; inherit host user;
dots = "/home/${user}/dotfiles"; dots = "/home/${user}/dotfiles";
home-dir = "/home/${user}";
}; };
modules = [ modules = [
@ -52,7 +53,6 @@ let
inherit host user system; inherit host user system;
system-font = "Consolas"; system-font = "Consolas";
dots = "/home/${user}/dotfiles"; dots = "/home/${user}/dotfiles";
home-dir = config.home-manager.users.${user}.home.homeDirectory;
}; };
users.${user} = { users.${user} = {
@ -107,6 +107,7 @@ let
specialArgs = specialArgs // { specialArgs = specialArgs // {
inherit host user; inherit host user;
dots = "/Users/${user}/dotfiles"; dots = "/Users/${user}/dotfiles";
home-dir = "/Users/${user}";
}; };
modules = [ modules = [

View file

@ -13,4 +13,8 @@ rec {
whatsapp-for-mac = callPackage ./whatsapp-for-mac { }; whatsapp-for-mac = callPackage ./whatsapp-for-mac { };
sf-symbols = callPackage ./sf-symbols { full = true; }; sf-symbols = callPackage ./sf-symbols { full = true; };
sf-mono = callPackage ./sf-mono { }; sf-mono = callPackage ./sf-mono { };
ps2pdfcrop = callPackage ./ps2pdfcrop { };
sbarlua = callPackage ./sbarlua { };
sk-utils = callPackage ./sk-utils { };
promise-lua = callPackage ./promise-lua { };
} }

View file

@ -0,0 +1,30 @@
# https://github.com/Tnixc/nix-config/blob/main/home/programs/aerospace-sketchybar/flake.nix
{
clang,
fetchFromGitHub,
fetchurl,
gcc,
readline,
lua5_4,
lua54Packages,
stdenv,
darwin,
lib,
luaPackages,
}:
lua54Packages.buildLuarocksPackage rec {
pname = "promise-lua";
version = "0.4.1-1";
name = "${pname}-${version}";
src = fetchFromGitHub {
owner = "pyericz";
repo = "promise-lua";
rev = "20d2ab1bb6aa398fe06ba597c943077a72c22451";
sha256 = "sha256-LNNLTAAEPIalRSjC6z8QTkhnmwpFAHSlwxuFxb9r2OE=";
};
knownRockspec =
(fetchurl {
url = "https://luarocks.org/manifests/pyericz/promise-lua-0.4.1-1.rockspec";
sha256 = "sha256-P/HP015RE/GUfarez/ezMuOflhXYPRTwbPdmz5J6qGE=";
}).outPath;
}

View file

@ -0,0 +1,48 @@
{
bash,
nix,
resholve,
ghostscript,
substituteAll,
isNixOS ? false,
}:
resholve.mkDerivation rec {
pname = "ps2pdfcrop";
version = "0.0.1";
src = substituteAll {
src = ./ps2pdfcrop.sh;
ps2pdf = "${ghostscript}/bin/ps2pdf";
};
dontUnpack = true;
installPhase = ''
runHook preInstall
install -Dm755 "$src" "$out/bin/${pname}"
runHook postInstall
'';
solutions = {
ps2pdfcrop = {
scripts = [ "bin/${pname}" ];
interpreter = "${bash}/bin/bash";
inputs = [ ghostscript ];
keep = {
"$PS2PDF" = true;
};
fake = {
external = [
# https://github.com/abathur/resholve/issues/29
];
};
execer = [
"cannot:${nix}/bin/nix-store"
"cannot:${nix}/bin/nix-collect-garbage"
];
};
};
}

View file

@ -0,0 +1,5 @@
#!/usr/bin/env bash
readonly PS2PDF=@ps2pdf@
"$PS2PDF" -dEPSCrop -dALLOWPSTRANSPARENCY $@

View file

@ -0,0 +1,78 @@
# https://github.com/khaneliman/khanelinix/blob/7703e485a2c7431f63004321da9e02ff7e06eb0b/packages/sbarlua/default.nix
# {
# clang,
# fetchFromGitHub,
# gcc,
# readline,
# lua,
# }:
# lua.stdenv.mkDerivation rec {
# pname = "SBarLua";
# version = "0-unstable-2024-08-12";
#
# name = "lua${lua.luaversion}-" + pname + "-" + version;
#
# src = fetchFromGitHub {
# owner = "FelixKratz";
# repo = "SbarLua";
# rev = "437bd2031da38ccda75827cb7548e7baa4aa9978";
# hash = "sha256-F0UfNxHM389GhiPQ6/GFbeKQq5EvpiqQdvyf7ygzkPg=";
# };
#
# nativeBuildInputs = [
# clang
# gcc
# ];
#
# buildInputs = [ readline ];
#
# propagatedBuildInputs = [ lua ];
#
# makeFlags = [
# "PREFIX=$(out)"
# "LUA_INC=-I${lua}/include"
# "LUA_LIBDIR=$(out)/lib/lua/${lua.luaversion}"
# "LUA_VERSION=${lua.luaversion}"
# ];
#
# installPhase = ''
# mkdir -p $out/lib/lua/${lua.luaversion}/
# cp -r bin/* "$out/lib/lua/${lua.luaversion}/"
# '';
# }
# https://github.com/Tnixc/nix-config/blob/main/home/programs/aerospace-sketchybar/flake.nix
{
clang,
fetchFromGitHub,
gcc,
readline,
lua5_4,
lua54Packages,
stdenv,
darwin,
lib,
luaPackages,
}:
lua54Packages.buildLuaPackage rec {
pname = "sbar";
version = "0-unstable-2024-08-12";
name = "lua${lua5_4.luaversion}-" + pname + "-" + version;
src = fetchFromGitHub {
owner = "FelixKratz";
repo = "SbarLua";
rev = "437bd2031da38ccda75827cb7548e7baa4aa9978";
hash = "sha256-F0UfNxHM389GhiPQ6/GFbeKQq5EvpiqQdvyf7ygzkPg=";
};
installPhase = ''
mkdir -p $out/lib/lua/${lua5_4.luaversion}
cp bin/sketchybar.so $out/lib/lua/${lua5_4.luaversion}/
'';
nativeBuildInputs = [
gcc
readline
clang
stdenv
] ++ lib.optionals stdenv.isDarwin (with darwin.apple_sdk.frameworks; [ CoreFoundation ]);
}

View file

@ -0,0 +1,23 @@
{
stdenv,
clang,
gnumake,
}:
stdenv.mkDerivation {
pname = "sk-utils";
version = "1.0";
src = ./src;
buildInputs = [
clang
gnumake
];
buildPhase = ''
make
'';
installPhase = ''
mkdir -p $out/bin
cp event_providers/cpu_load/bin/cpu_load $out/bin/sk-cpu-load
cp event_providers/network_load/bin/network_load $out/bin/sk-network-load
cp menus/bin/menus $out/bin/sk-menus
'';
}

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