diff --git a/dot_config/flake/flake.nix b/dot_config/flake/flake.nix index 252ed33..3961f63 100644 --- a/dot_config/flake/flake.nix +++ b/dot_config/flake/flake.nix @@ -74,7 +74,10 @@ specialArgs = {inherit inputs;}; modules = [ + lix-module.nixosModules.default + ./home/hinge/darwin.nix + home-manager.darwinModules.home-manager { home-manager.useGlobalPkgs = true; diff --git a/dot_config/flake/home/hinge/sketchybar.nix b/dot_config/flake/home/hinge/sketchybar.nix new file mode 100644 index 0000000..7d864af --- /dev/null +++ b/dot_config/flake/home/hinge/sketchybar.nix @@ -0,0 +1,6 @@ +{...}: { + # services.sketchybar = { + # enable = true; + # extraPackages = []; + # }; +} diff --git a/dot_config/sketchybar/bar.lua b/dot_config/sketchybar/bar.lua new file mode 100644 index 0000000..b364094 --- /dev/null +++ b/dot_config/sketchybar/bar.lua @@ -0,0 +1,9 @@ +local colors = require("colors") + +-- Equivalent to the --bar domain +sbar.bar({ + height = 40, + color = colors.bar.bg, + padding_right = 2, + padding_left = 2, +}) diff --git a/dot_config/sketchybar/colors.lua b/dot_config/sketchybar/colors.lua new file mode 100644 index 0000000..fb95015 --- /dev/null +++ b/dot_config/sketchybar/colors.lua @@ -0,0 +1,28 @@ +return { + black = 0xff181819, + white = 0xffe2e2e3, + red = 0xfffc5d7c, + green = 0xff9ed072, + blue = 0xff76cce0, + yellow = 0xffe7c664, + orange = 0xfff39660, + magenta = 0xffb39df3, + grey = 0xff7f8490, + transparent = 0x00000000, + + bar = { + bg = 0xf02c2e34, + border = 0xff2c2e34, + }, + popup = { + bg = 0xc02c2e34, + border = 0xff7f8490 + }, + bg1 = 0xff363944, + bg2 = 0xff414550, + + 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, +} diff --git a/dot_config/sketchybar/colors.sh b/dot_config/sketchybar/colors.sh new file mode 100644 index 0000000..f7929b7 --- /dev/null +++ b/dot_config/sketchybar/colors.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env sh + +# Color Palette -- Catppuccin Macchiato +export BLACK=0xff181926 +#export WHITE=0xffcad3f5 +#export RED=0xffed8796 +#export GREEN=0xffa6da95 +#export BLUE=0xff8aadf4 +#export YELLOW=0xffeed49f +export ORANGE=0xfff5a97f +export MAGENTA=0xffc6a0f6 +export GREY=0xff939ab7 +#export SKY=0xff91d7e3 +#export PINK=0xfff5bde6 +export TRANSPARENT=0x00000000 + +export ROSEWATER=0xFFf5e0dc #f5e0dc +export FLAMINGO=0xFFf2cdcd #f2cdcd +export PINK=0xFFf5c2e7 #f5c2e7 +export MAUVE=0xFFcba6f7 #cba6f7 +export RED=0xFFf38ba8 #f38ba8 +export MAROON=0xFFeba0ac #eba0ac +export PEACH=0xFFfab387 #fab387 +export YELLOW=0xFFf9e2af #f9e2af +export GREEN=0xFFa6e3a1 #a6e3a1 +export TEAL=0xFF94e2d5 #94e2d5 +export SKY=0xFF89dceb #89dceb +export SAPPHIRE=0xFF74c7ec #74c7ec +export BLUE=0xFF89b4fa #89b4fa +export LAVENDER=0xFFb4befe #b4befe + +export WHITE=0xFFcdd6f4 #cdd6f4 +export DARK_WHITE=0xFF9399b2 #9399b2 + +export ITEM_COLOR=0xff1e1e2e +#export ITEM_COLOR=$BLACK + +# General bar colors +export BAR_COLOR=$DARK_WHITE +export ICON_COLOR=$WHITE # Color of all icons +export LABEL_COLOR=$WHITE # Color of all labels +export ALT_LABEL_COLOR=$WHITE + +export SPACE_BACKGROUND=$ITEM_COLOR +#export SPACE_BACKGROUND2=0xffa6adc8 +export SPACE_SELECTED=$PEACH +#export SPACE_DESELECTED=$LAVENDER +export SPACE_BACKGROUND2=0xffa6adc8 +#export SPACE_SELECTED=0xffcdd6f4 +export SPACE_DESELECTED=0xff313244 +export OPEN_APPS_BACKGROUND=$ITEM_COLOR +export CALENDAR_BACKGROUND=0xffb8c0e0 + +export POPUP_BACKGROUND_COLOR=$BLACK +export POPUP_BORDER_COLOR=$WHITE + +export SHADOW_COLOR=$BLACK diff --git a/dot_config/sketchybar/default.lua b/dot_config/sketchybar/default.lua new file mode 100644 index 0000000..f9d463b --- /dev/null +++ b/dot_config/sketchybar/default.lua @@ -0,0 +1,52 @@ +local settings = require("settings") +local colors = require("colors") + +-- Equivalent to the --default domain +sbar.default({ + updates = "when_shown", + icon = { + font = { + family = settings.font.text, + style = settings.font.style_map["Bold"], + size = 14.0 + }, + color = colors.white, + padding_left = settings.paddings, + padding_right = settings.paddings, + background = { image = { corner_radius = 9 } }, + }, + label = { + font = { + family = settings.font.text, + style = settings.font.style_map["Semibold"], + size = 13.0 + }, + color = colors.white, + padding_left = settings.paddings, + padding_right = settings.paddings, + }, + background = { + height = 28, + corner_radius = 9, + border_width = 2, + border_color = colors.bg2, + image = { + corner_radius = 9, + border_color = colors.grey, + border_width = 1 + } + }, + popup = { + background = { + border_width = 2, + corner_radius = 9, + border_color = colors.popup.border, + color = colors.popup.bg, + shadow = { drawing = true }, + }, + blur_radius = 50, + }, + padding_left = 5, + padding_right = 5, + scroll_texts = true, +}) diff --git a/dot_config/sketchybar/executable_sketchybarrc b/dot_config/sketchybar/executable_sketchybarrc new file mode 100755 index 0000000..95ece11 --- /dev/null +++ b/dot_config/sketchybar/executable_sketchybarrc @@ -0,0 +1,5 @@ +#!/usr/bin/env lua + +-- Load the sketchybar-package and prepare the helper binaries +require("helpers") +require("init") diff --git a/dot_config/sketchybar/helpers/app_icons.lua b/dot_config/sketchybar/helpers/app_icons.lua new file mode 100644 index 0000000..f87d1bd --- /dev/null +++ b/dot_config/sketchybar/helpers/app_icons.lua @@ -0,0 +1,313 @@ +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:", + ["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:", + ["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:", + ["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:", + ["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:", + ["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:", +} diff --git a/dot_config/sketchybar/helpers/default_font.lua b/dot_config/sketchybar/helpers/default_font.lua new file mode 100644 index 0000000..fb1f16e --- /dev/null +++ b/dot_config/sketchybar/helpers/default_font.lua @@ -0,0 +1,13 @@ +return { + text = "SF Pro", -- Used for text + numbers = "SF Mono", -- Used for numbers + + -- Unified font style map + style_map = { + ["Regular"] = "Regular", + ["Semibold"] = "Semibold", + ["Bold"] = "Bold", + ["Heavy"] = "Heavy", + ["Black"] = "Black", + } +} diff --git a/dot_config/sketchybar/helpers/dot_gitignore b/dot_config/sketchybar/helpers/dot_gitignore new file mode 100644 index 0000000..ba077a4 --- /dev/null +++ b/dot_config/sketchybar/helpers/dot_gitignore @@ -0,0 +1 @@ +bin diff --git a/dot_config/sketchybar/helpers/event_providers/cpu_load/cpu.h b/dot_config/sketchybar/helpers/event_providers/cpu_load/cpu.h new file mode 100644 index 0000000..413f70f --- /dev/null +++ b/dot_config/sketchybar/helpers/event_providers/cpu_load/cpu.h @@ -0,0 +1,58 @@ +#include +#include +#include +#include + +struct cpu { + host_t host; + mach_msg_type_number_t count; + host_cpu_load_info_data_t load; + host_cpu_load_info_data_t prev_load; + bool has_prev_load; + + int user_load; + int sys_load; + int total_load; +}; + +static inline void cpu_init(struct cpu* cpu) { + cpu->host = mach_host_self(); + cpu->count = HOST_CPU_LOAD_INFO_COUNT; + cpu->has_prev_load = false; +} + +static inline void cpu_update(struct cpu* cpu) { + kern_return_t error = host_statistics(cpu->host, + HOST_CPU_LOAD_INFO, + (host_info_t)&cpu->load, + &cpu->count ); + + if (error != KERN_SUCCESS) { + printf("Error: Could not read cpu host statistics.\n"); + return; + } + + if (cpu->has_prev_load) { + uint32_t delta_user = cpu->load.cpu_ticks[CPU_STATE_USER] + - cpu->prev_load.cpu_ticks[CPU_STATE_USER]; + + uint32_t delta_system = cpu->load.cpu_ticks[CPU_STATE_SYSTEM] + - cpu->prev_load.cpu_ticks[CPU_STATE_SYSTEM]; + + uint32_t delta_idle = cpu->load.cpu_ticks[CPU_STATE_IDLE] + - cpu->prev_load.cpu_ticks[CPU_STATE_IDLE]; + + cpu->user_load = (double)delta_user / (double)(delta_system + + delta_user + + delta_idle) * 100.0; + + cpu->sys_load = (double)delta_system / (double)(delta_system + + delta_user + + delta_idle) * 100.0; + + cpu->total_load = cpu->user_load + cpu->sys_load; + } + + cpu->prev_load = cpu->load; + cpu->has_prev_load = true; +} diff --git a/dot_config/sketchybar/helpers/event_providers/cpu_load/cpu_load.c b/dot_config/sketchybar/helpers/event_providers/cpu_load/cpu_load.c new file mode 100644 index 0000000..ee97613 --- /dev/null +++ b/dot_config/sketchybar/helpers/event_providers/cpu_load/cpu_load.c @@ -0,0 +1,41 @@ +#include "cpu.h" +#include "../sketchybar.h" + +int main (int argc, char** argv) { + float update_freq; + if (argc < 3 || (sscanf(argv[2], "%f", &update_freq) != 1)) { + printf("Usage: %s \"\" \"\"\n", argv[0]); + exit(1); + } + + alarm(0); + struct cpu cpu; + cpu_init(&cpu); + + // Setup the event in sketchybar + char event_message[512]; + snprintf(event_message, 512, "--add event '%s'", argv[1]); + sketchybar(event_message); + + char trigger_message[512]; + for (;;) { + // Acquire new info + cpu_update(&cpu); + + // Prepare the event message + snprintf(trigger_message, + 512, + "--trigger '%s' user_load='%d' sys_load='%02d' total_load='%02d'", + argv[1], + cpu.user_load, + cpu.sys_load, + cpu.total_load ); + + // Trigger the event + sketchybar(trigger_message); + + // Wait + usleep(update_freq * 1000000); + } + return 0; +} diff --git a/dot_config/sketchybar/helpers/event_providers/cpu_load/makefile b/dot_config/sketchybar/helpers/event_providers/cpu_load/makefile new file mode 100644 index 0000000..6366a0f --- /dev/null +++ b/dot_config/sketchybar/helpers/event_providers/cpu_load/makefile @@ -0,0 +1,5 @@ +bin/cpu_load: cpu_load.c cpu.h ../sketchybar.h | bin + clang -std=c99 -O3 $< -o $@ + +bin: + mkdir bin diff --git a/dot_config/sketchybar/helpers/event_providers/makefile b/dot_config/sketchybar/helpers/event_providers/makefile new file mode 100644 index 0000000..8c1ca39 --- /dev/null +++ b/dot_config/sketchybar/helpers/event_providers/makefile @@ -0,0 +1,3 @@ +all: + (cd cpu_load && $(MAKE)) + (cd network_load && $(MAKE)) diff --git a/dot_config/sketchybar/helpers/event_providers/network_load/makefile b/dot_config/sketchybar/helpers/event_providers/network_load/makefile new file mode 100644 index 0000000..e464482 --- /dev/null +++ b/dot_config/sketchybar/helpers/event_providers/network_load/makefile @@ -0,0 +1,5 @@ +bin/network_load: network_load.c network.h ../sketchybar.h | bin + clang -std=c99 -O3 $< -o $@ + +bin: + mkdir bin diff --git a/dot_config/sketchybar/helpers/event_providers/network_load/network.h b/dot_config/sketchybar/helpers/event_providers/network_load/network.h new file mode 100644 index 0000000..25175e5 --- /dev/null +++ b/dot_config/sketchybar/helpers/event_providers/network_load/network.h @@ -0,0 +1,90 @@ +#include +#include +#include +#include +#include +#include +#include + +static char unit_str[3][6] = { { " Bps" }, { "KBps" }, { "MBps" }, }; + +enum unit { + UNIT_BPS, + UNIT_KBPS, + UNIT_MBPS +}; +struct network { + uint32_t row; + struct ifmibdata data; + struct timeval tv_nm1, tv_n, tv_delta; + + int up; + int down; + enum unit up_unit, down_unit; +}; + +static inline void ifdata(uint32_t net_row, struct ifmibdata* data) { + static size_t size = sizeof(struct ifmibdata); + static int32_t data_option[] = { CTL_NET, PF_LINK, NETLINK_GENERIC, IFMIB_IFDATA, 0, IFDATA_GENERAL }; + data_option[4] = net_row; + sysctl(data_option, 6, data, &size, NULL, 0); +} + +static inline void network_init(struct network* net, char* ifname) { + memset(net, 0, sizeof(struct network)); + + static int count_option[] = { CTL_NET, PF_LINK, NETLINK_GENERIC, IFMIB_SYSTEM, IFMIB_IFCOUNT }; + uint32_t interface_count = 0; + size_t size = sizeof(uint32_t); + sysctl(count_option, 5, &interface_count, &size, NULL, 0); + + for (int i = 0; i < interface_count; i++) { + ifdata(i, &net->data); + if (strcmp(net->data.ifmd_name, ifname) == 0) { + net->row = i; + break; + } + } +} + +static inline void network_update(struct network* net) { + gettimeofday(&net->tv_n, NULL); + timersub(&net->tv_n, &net->tv_nm1, &net->tv_delta); + net->tv_nm1 = net->tv_n; + + uint64_t ibytes_nm1 = net->data.ifmd_data.ifi_ibytes; + uint64_t obytes_nm1 = net->data.ifmd_data.ifi_obytes; + ifdata(net->row, &net->data); + + double time_scale = (net->tv_delta.tv_sec + 1e-6*net->tv_delta.tv_usec); + if (time_scale < 1e-6 || time_scale > 1e2) return; + double delta_ibytes = (double)(net->data.ifmd_data.ifi_ibytes - ibytes_nm1) + / time_scale; + double delta_obytes = (double)(net->data.ifmd_data.ifi_obytes - obytes_nm1) + / time_scale; + + double exponent_ibytes = log10(delta_ibytes); + double exponent_obytes = log10(delta_obytes); + + if (exponent_ibytes < 3) { + net->down_unit = UNIT_BPS; + net->down = delta_ibytes; + } else if (exponent_ibytes < 6) { + net->down_unit = UNIT_KBPS; + net->down = delta_ibytes / 1000.0; + } else if (exponent_ibytes < 9) { + net->down_unit = UNIT_MBPS; + net->down = delta_ibytes / 1000000.0; + } + + if (exponent_obytes < 3) { + net->up_unit = UNIT_BPS; + net->up = delta_obytes; + } else if (exponent_obytes < 6) { + net->up_unit = UNIT_KBPS; + net->up = delta_obytes / 1000.0; + } else if (exponent_obytes < 9) { + net->up_unit = UNIT_MBPS; + net->up = delta_obytes / 1000000.0; + } +} diff --git a/dot_config/sketchybar/helpers/event_providers/network_load/network_load.c b/dot_config/sketchybar/helpers/event_providers/network_load/network_load.c new file mode 100644 index 0000000..06afe95 --- /dev/null +++ b/dot_config/sketchybar/helpers/event_providers/network_load/network_load.c @@ -0,0 +1,42 @@ +#include +#include "network.h" +#include "../sketchybar.h" + +int main (int argc, char** argv) { + float update_freq; + if (argc < 4 || (sscanf(argv[3], "%f", &update_freq) != 1)) { + printf("Usage: %s \"\" \"\" \"\"\n", argv[0]); + exit(1); + } + + alarm(0); + // Setup the event in sketchybar + char event_message[512]; + snprintf(event_message, 512, "--add event '%s'", argv[2]); + sketchybar(event_message); + + struct network network; + network_init(&network, argv[1]); + char trigger_message[512]; + for (;;) { + // Acquire new info + network_update(&network); + + // Prepare the event message + snprintf(trigger_message, + 512, + "--trigger '%s' upload='%03d%s' download='%03d%s'", + argv[2], + network.up, + unit_str[network.up_unit], + network.down, + unit_str[network.down_unit]); + + // Trigger the event + sketchybar(trigger_message); + + // Wait + usleep(update_freq * 1000000); + } + return 0; +} diff --git a/dot_config/sketchybar/helpers/event_providers/sketchybar.h b/dot_config/sketchybar/helpers/event_providers/sketchybar.h new file mode 100644 index 0000000..72bf06a --- /dev/null +++ b/dot_config/sketchybar/helpers/event_providers/sketchybar.h @@ -0,0 +1,122 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +typedef char* env; + +#define MACH_HANDLER(name) void name(env env) +typedef MACH_HANDLER(mach_handler); + +struct mach_message { + mach_msg_header_t header; + mach_msg_size_t msgh_descriptor_count; + mach_msg_ool_descriptor_t descriptor; +}; + +struct mach_buffer { + struct mach_message message; + mach_msg_trailer_t trailer; +}; + +static mach_port_t g_mach_port = 0; + +static inline mach_port_t mach_get_bs_port() { + mach_port_name_t task = mach_task_self(); + + mach_port_t bs_port; + if (task_get_special_port(task, + TASK_BOOTSTRAP_PORT, + &bs_port ) != KERN_SUCCESS) { + return 0; + } + + char* name = getenv("BAR_NAME"); + if (!name) name = "sketchybar"; + uint32_t lookup_len = 16 + strlen(name); + + char buffer[lookup_len]; + snprintf(buffer, lookup_len, "git.felix.%s", name); + + mach_port_t port; + if (bootstrap_look_up(bs_port, buffer, &port) != KERN_SUCCESS) return 0; + return port; +} + +static inline bool mach_send_message(mach_port_t port, char* message, uint32_t len) { + if (!message || !port) { + return false; + } + + struct mach_message msg = { 0 }; + msg.header.msgh_remote_port = port; + msg.header.msgh_local_port = 0; + msg.header.msgh_id = 0; + msg.header.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, + MACH_MSG_TYPE_MAKE_SEND, + 0, + MACH_MSGH_BITS_COMPLEX ); + + msg.header.msgh_size = sizeof(struct mach_message); + msg.msgh_descriptor_count = 1; + msg.descriptor.address = message; + msg.descriptor.size = len * sizeof(char); + msg.descriptor.copy = MACH_MSG_VIRTUAL_COPY; + msg.descriptor.deallocate = false; + msg.descriptor.type = MACH_MSG_OOL_DESCRIPTOR; + + kern_return_t err = mach_msg(&msg.header, + MACH_SEND_MSG, + sizeof(struct mach_message), + 0, + MACH_PORT_NULL, + MACH_MSG_TIMEOUT_NONE, + MACH_PORT_NULL ); + + return err == KERN_SUCCESS; +} + +static inline uint32_t format_message(char* message, char* formatted_message) { + // This is not actually robust, switch to stack based messaging. + char outer_quote = 0; + uint32_t caret = 0; + uint32_t message_length = strlen(message) + 1; + for (int i = 0; i < message_length; ++i) { + if (message[i] == '"' || message[i] == '\'') { + if (outer_quote && outer_quote == message[i]) outer_quote = 0; + else if (!outer_quote) outer_quote = message[i]; + continue; + } + formatted_message[caret] = message[i]; + if (message[i] == ' ' && !outer_quote) formatted_message[caret] = '\0'; + caret++; + } + + if (caret > 0 && formatted_message[caret] == '\0' + && formatted_message[caret - 1] == '\0') { + caret--; + } + formatted_message[caret] = '\0'; + return caret + 1; +} + +static inline void sketchybar(char* message) { + char formatted_message[strlen(message) + 2]; + uint32_t length = format_message(message, formatted_message); + if (!length) return; + + if (!g_mach_port) g_mach_port = mach_get_bs_port(); + if (!mach_send_message(g_mach_port, formatted_message, length)) { + g_mach_port = mach_get_bs_port(); + if (!mach_send_message(g_mach_port, formatted_message, length)) { + // No sketchybar instance running, exit. + exit(0); + } + } +} diff --git a/dot_config/sketchybar/helpers/init.lua b/dot_config/sketchybar/helpers/init.lua new file mode 100644 index 0000000..351531a --- /dev/null +++ b/dot_config/sketchybar/helpers/init.lua @@ -0,0 +1,4 @@ +-- Add the sketchybar module to the package cpath +package.cpath = package.cpath .. ";/Users/" .. os.getenv("USER") .. "/.local/share/sketchybar_lua/?.so" + +os.execute("(cd helpers && make)") diff --git a/dot_config/sketchybar/helpers/makefile b/dot_config/sketchybar/helpers/makefile new file mode 100644 index 0000000..3246108 --- /dev/null +++ b/dot_config/sketchybar/helpers/makefile @@ -0,0 +1,3 @@ +all: + (cd event_providers && $(MAKE)) >/dev/null + (cd menus && $(MAKE)) >/dev/null diff --git a/dot_config/sketchybar/helpers/menus/makefile b/dot_config/sketchybar/helpers/menus/makefile new file mode 100644 index 0000000..0cb454e --- /dev/null +++ b/dot_config/sketchybar/helpers/menus/makefile @@ -0,0 +1,5 @@ +bin/menus: menus.c | bin + clang -std=c99 -O3 -F/System/Library/PrivateFrameworks/ -framework Carbon -framework SkyLight $< -o $@ + +bin: + mkdir bin diff --git a/dot_config/sketchybar/helpers/menus/menus.c b/dot_config/sketchybar/helpers/menus/menus.c new file mode 100644 index 0000000..2e77822 --- /dev/null +++ b/dot_config/sketchybar/helpers/menus/menus.c @@ -0,0 +1,248 @@ +#include + +void ax_init() { + const void *keys[] = { kAXTrustedCheckOptionPrompt }; + const void *values[] = { kCFBooleanTrue }; + + CFDictionaryRef options; + options = CFDictionaryCreate(kCFAllocatorDefault, + keys, + values, + sizeof(keys) / sizeof(*keys), + &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks ); + + bool trusted = AXIsProcessTrustedWithOptions(options); + CFRelease(options); + if (!trusted) exit(1); +} + +void ax_perform_click(AXUIElementRef element) { + if (!element) return; + AXUIElementPerformAction(element, kAXCancelAction); + usleep(150000); + AXUIElementPerformAction(element, kAXPressAction); +} + +CFStringRef ax_get_title(AXUIElementRef element) { + CFTypeRef title = NULL; + AXError error = AXUIElementCopyAttributeValue(element, + kAXTitleAttribute, + &title ); + + if (error != kAXErrorSuccess) return NULL; + return title; +} + +void ax_select_menu_option(AXUIElementRef app, int id) { + AXUIElementRef menubars_ref = NULL; + CFArrayRef children_ref = NULL; + + AXError error = AXUIElementCopyAttributeValue(app, + kAXMenuBarAttribute, + (CFTypeRef*)&menubars_ref); + if (error == kAXErrorSuccess) { + error = AXUIElementCopyAttributeValue(menubars_ref, + kAXVisibleChildrenAttribute, + (CFTypeRef*)&children_ref ); + + if (error == kAXErrorSuccess) { + uint32_t count = CFArrayGetCount(children_ref); + if (id < count) { + AXUIElementRef item = CFArrayGetValueAtIndex(children_ref, id); + ax_perform_click(item); + } + if (children_ref) CFRelease(children_ref); + } + if (menubars_ref) CFRelease(menubars_ref); + } +} + +void ax_print_menu_options(AXUIElementRef app) { + AXUIElementRef menubars_ref = NULL; + CFTypeRef menubar = NULL; + CFArrayRef children_ref = NULL; + + AXError error = AXUIElementCopyAttributeValue(app, + kAXMenuBarAttribute, + (CFTypeRef*)&menubars_ref); + if (error == kAXErrorSuccess) { + error = AXUIElementCopyAttributeValue(menubars_ref, + kAXVisibleChildrenAttribute, + (CFTypeRef*)&children_ref ); + + if (error == kAXErrorSuccess) { + uint32_t count = CFArrayGetCount(children_ref); + + for (int i = 1; i < count; i++) { + AXUIElementRef item = CFArrayGetValueAtIndex(children_ref, i); + CFTypeRef title = ax_get_title(item); + + if (title) { + uint32_t buffer_len = 2*CFStringGetLength(title); + char buffer[2*CFStringGetLength(title)]; + CFStringGetCString(title, buffer, buffer_len, kCFStringEncodingUTF8); + printf("%s\n", buffer); + CFRelease(title); + } + } + } + if (menubars_ref) CFRelease(menubars_ref); + if (children_ref) CFRelease(children_ref); + } +} + +AXUIElementRef ax_get_extra_menu_item(char* alias) { + pid_t pid = 0; + CGRect bounds = CGRectNull; + CFArrayRef window_list = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, + kCGNullWindowID ); + char owner_buffer[256]; + char name_buffer[256]; + char buffer[512]; + int window_count = CFArrayGetCount(window_list); + for (int i = 0; i < window_count; ++i) { + CFDictionaryRef dictionary = CFArrayGetValueAtIndex(window_list, i); + if (!dictionary) continue; + + CFStringRef owner_ref = CFDictionaryGetValue(dictionary, + kCGWindowOwnerName); + + CFNumberRef owner_pid_ref = CFDictionaryGetValue(dictionary, + kCGWindowOwnerPID); + + CFStringRef name_ref = CFDictionaryGetValue(dictionary, kCGWindowName); + CFNumberRef layer_ref = CFDictionaryGetValue(dictionary, kCGWindowLayer); + CFDictionaryRef bounds_ref = CFDictionaryGetValue(dictionary, + kCGWindowBounds); + + if (!name_ref || !owner_ref || !owner_pid_ref || !layer_ref || !bounds_ref) + continue; + + long long int layer = 0; + CFNumberGetValue(layer_ref, CFNumberGetType(layer_ref), &layer); + uint64_t owner_pid = 0; + CFNumberGetValue(owner_pid_ref, + CFNumberGetType(owner_pid_ref), + &owner_pid ); + + if (layer != 0x19) continue; + bounds = CGRectNull; + if (!CGRectMakeWithDictionaryRepresentation(bounds_ref, &bounds)) continue; + CFStringGetCString(owner_ref, + owner_buffer, + sizeof(owner_buffer), + kCFStringEncodingUTF8); + + CFStringGetCString(name_ref, + name_buffer, + sizeof(name_buffer), + kCFStringEncodingUTF8); + snprintf(buffer, sizeof(buffer), "%s,%s", owner_buffer, name_buffer); + + if (strcmp(buffer, alias) == 0) { + pid = owner_pid; + break; + } + } + CFRelease(window_list); + if (!pid) return NULL; + + AXUIElementRef app = AXUIElementCreateApplication(pid); + if (!app) return NULL; + AXUIElementRef result = NULL; + CFTypeRef extras = NULL; + CFArrayRef children_ref = NULL; + AXError error = AXUIElementCopyAttributeValue(app, + kAXExtrasMenuBarAttribute, + &extras ); + if (error == kAXErrorSuccess) { + error = AXUIElementCopyAttributeValue(extras, + kAXVisibleChildrenAttribute, + (CFTypeRef*)&children_ref ); + + if (error == kAXErrorSuccess) { + uint32_t count = CFArrayGetCount(children_ref); + for (uint32_t i = 0; i < count; i++) { + AXUIElementRef item = CFArrayGetValueAtIndex(children_ref, i); + CFTypeRef position_ref = NULL; + CFTypeRef size_ref = NULL; + AXUIElementCopyAttributeValue(item, kAXPositionAttribute, + &position_ref ); + AXUIElementCopyAttributeValue(item, kAXSizeAttribute, + &size_ref ); + if (!position_ref || !size_ref) continue; + + CGPoint position = CGPointZero; + AXValueGetValue(position_ref, kAXValueCGPointType, &position); + CGSize size = CGSizeZero; + AXValueGetValue(size_ref, kAXValueCGSizeType, &size); + CFRelease(position_ref); + CFRelease(size_ref); + // The offset is exactly 8 on macOS Sonoma... + // printf("%f %f\n", position.x, bounds.origin.x); + if (error == kAXErrorSuccess + && fabs(position.x - bounds.origin.x) <= 10) { + result = item; + break; + } + } + } + } + + CFRelease(app); + return result; +} + +extern int SLSMainConnectionID(); +extern void SLSSetMenuBarVisibilityOverrideOnDisplay(int cid, int did, bool enabled); +extern void SLSSetMenuBarVisibilityOverrideOnDisplay(int cid, int did, bool enabled); +extern void SLSSetMenuBarInsetAndAlpha(int cid, double u1, double u2, float alpha); +void ax_select_menu_extra(char* alias) { + AXUIElementRef item = ax_get_extra_menu_item(alias); + if (!item) return; + SLSSetMenuBarInsetAndAlpha(SLSMainConnectionID(), 0, 1, 0.0); + SLSSetMenuBarVisibilityOverrideOnDisplay(SLSMainConnectionID(), 0, true); + SLSSetMenuBarInsetAndAlpha(SLSMainConnectionID(), 0, 1, 0.0); + ax_perform_click(item); + SLSSetMenuBarVisibilityOverrideOnDisplay(SLSMainConnectionID(), 0, false); + SLSSetMenuBarInsetAndAlpha(SLSMainConnectionID(), 0, 1, 1.0); + CFRelease(item); +} + +extern void _SLPSGetFrontProcess(ProcessSerialNumber* psn); +extern void SLSGetConnectionIDForPSN(int cid, ProcessSerialNumber* psn, int* cid_out); +extern void SLSConnectionGetPID(int cid, pid_t* pid_out); +AXUIElementRef ax_get_front_app() { + ProcessSerialNumber psn; + _SLPSGetFrontProcess(&psn); + int target_cid; + SLSGetConnectionIDForPSN(SLSMainConnectionID(), &psn, &target_cid); + + pid_t pid; + SLSConnectionGetPID(target_cid, &pid); + return AXUIElementCreateApplication(pid); +} + +int main (int argc, char **argv) { + if (argc == 1) { + printf("Usage: %s [-l | -s id/alias ]\n", argv[0]); + exit(0); + } + ax_init(); + if (strcmp(argv[1], "-l") == 0) { + AXUIElementRef app = ax_get_front_app(); + if (!app) return 1; + ax_print_menu_options(app); + CFRelease(app); + } else if (argc == 3 && strcmp(argv[1], "-s") == 0) { + int id = 0; + if (sscanf(argv[2], "%d", &id) == 1) { + AXUIElementRef app = ax_get_front_app(); + if (!app) return 1; + ax_select_menu_option(app, id); + CFRelease(app); + } else ax_select_menu_extra(argv[2]); + } + return 0; +} diff --git a/dot_config/sketchybar/icons.lua b/dot_config/sketchybar/icons.lua new file mode 100644 index 0000000..2e52a91 --- /dev/null +++ b/dot_config/sketchybar/icons.lua @@ -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 diff --git a/dot_config/sketchybar/icons.sh b/dot_config/sketchybar/icons.sh new file mode 100644 index 0000000..147c4f5 --- /dev/null +++ b/dot_config/sketchybar/icons.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env sh + +# General Icons +LOADING=􀖇 +APPLE=􀣺 +PREFERENCES=􀺽 +ACTIVITY=􀒓 +LOCK=􀒳 +MAIL=􀍜 +MAIL_OPEN=􀍜 +BELL=􀋚 +BELL_DOT=􀝗 +CALENDAR=􀐫 + +# Git Icons +GIT_ISSUE=􀍷 +GIT_DISCUSSION=􀒤 +GIT_PULL_REQUEST=􀙡 +GIT_COMMIT=􀡚 +GIT_INDICATOR=􀂓 + +# Spotify Icons +SPOTIFY_BACK=􀊎 +SPOTIFY_PLAY_PAUSE=􀊈 +SPOTIFY_NEXT=􀊐 +SPOTIFY_SHUFFLE=􀊝 +SPOTIFY_REPEAT=􀊞 + +# Yabai Icons +YABAI_STACK=􀏭 +YABAI_FULLSCREEN_ZOOM=􀏜 +YABAI_PARENT_ZOOM=􀥃 +YABAI_FLOAT=􀢌 +YABAI_GRID=􀧍 + +# Battery Icons +BATTERY_FULL=􀛨 +BATTERY_75=􀺸 +BATTERY_50=􀺶 +BATTERY_25=􀛩 +BATTERY_0=􀛪 +BATTERY_CHARGING=􀢋 diff --git a/dot_config/sketchybar/init.lua b/dot_config/sketchybar/init.lua new file mode 100644 index 0000000..e1bf892 --- /dev/null +++ b/dot_config/sketchybar/init.lua @@ -0,0 +1,16 @@ +-- Require the sketchybar module +sbar = require("sketchybar") + +-- Set the bar name, if you are using another bar instance than sketchybar +-- sbar.set_bar_name("bottom_bar") + +-- Bundle the entire initial configuration into a single message to sketchybar +sbar.begin_config() +require("bar") +require("default") +require("items") +sbar.end_config() + +-- Run the event loop of the sketchybar module (without this there will be no +-- callback functions executed in the lua module) +sbar.event_loop() diff --git a/dot_config/sketchybar/items/apple.lua b/dot_config/sketchybar/items/apple.lua new file mode 100644 index 0000000..8b11ef1 --- /dev/null +++ b/dot_config/sketchybar/items/apple.lua @@ -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 = 8, + padding_left = 8, + }, + label = { drawing = false }, + background = { + color = colors.bg2, + border_color = colors.black, + border_width = 1 + }, + padding_left = 1, + padding_right = 1, + click_script = "$CONFIG_DIR/helpers/menus/bin/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 }) diff --git a/dot_config/sketchybar/items/calendar.lua b/dot_config/sketchybar/items/calendar.lua new file mode 100644 index 0000000..4a42249 --- /dev/null +++ b/dot_config/sketchybar/items/calendar.lua @@ -0,0 +1,49 @@ +local settings = require("settings") +local colors = require("colors") + +-- 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 = 8, + font = { + style = settings.font.style_map["Black"], + size = 12.0, + }, + }, + label = { + color = colors.white, + padding_right = 8, + width = 49, + align = "right", + font = { family = settings.font.numbers }, + }, + position = "right", + update_freq = 30, + padding_left = 1, + padding_right = 1, + background = { + color = colors.bg2, + border_color = colors.black, + border_width = 1 + }, + click_script = "open -a 'Calendar'" +}) + +-- Double border for calendar using a single item bracket +sbar.add("bracket", { cal.name }, { + background = { + color = colors.transparent, + height = 30, + border_color = colors.grey, + } +}) + +-- 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("%a. %d %b."), label = os.date("%H:%M") }) +end) diff --git a/dot_config/sketchybar/items/front_app.lua b/dot_config/sketchybar/items/front_app.lua new file mode 100644 index 0000000..a5be8ad --- /dev/null +++ b/dot_config/sketchybar/items/front_app.lua @@ -0,0 +1,22 @@ +local colors = require("colors") +local settings = require("settings") + +local front_app = sbar.add("item", "front_app", { + display = "active", + icon = { drawing = false }, + label = { + font = { + style = settings.font.style_map["Black"], + size = 12.0, + }, + }, + updates = true, +}) + +front_app:subscribe("front_app_switched", function(env) + front_app:set({ label = { string = env.INFO } }) +end) + +front_app:subscribe("mouse.clicked", function(env) + sbar.trigger("swap_menus_and_spaces") +end) diff --git a/dot_config/sketchybar/items/init.lua b/dot_config/sketchybar/items/init.lua new file mode 100644 index 0000000..dad59fa --- /dev/null +++ b/dot_config/sketchybar/items/init.lua @@ -0,0 +1,7 @@ +require("items.apple") +require("items.menus") +require("items.spaces") +require("items.front_app") +require("items.calendar") +require("items.widgets") +require("items.media") diff --git a/dot_config/sketchybar/items/media.lua b/dot_config/sketchybar/items/media.lua new file mode 100644 index 0000000..adcaea2 --- /dev/null +++ b/dot_config/sketchybar/items/media.lua @@ -0,0 +1,118 @@ +local icons = require("icons") +local colors = require("colors") + +local whitelist = { ["Spotify"] = true, + ["Music"] = true }; + +local media_cover = sbar.add("item", { + position = "right", + background = { + image = { + string = "media.artwork", + scale = 0.85, + }, + color = colors.transparent, + }, + label = { drawing = false }, + icon = { drawing = false }, + drawing = false, + updates = true, + popup = { + align = "center", + horizontal = true, + } +}) + +local media_artist = sbar.add("item", { + position = "right", + drawing = false, + padding_left = 3, + padding_right = 0, + width = 0, + icon = { drawing = false }, + label = { + width = 0, + font = { size = 9 }, + color = colors.with_alpha(colors.white, 0.6), + max_chars = 18, + y_offset = 6, + }, +}) + +local media_title = sbar.add("item", { + position = "right", + drawing = false, + padding_left = 3, + padding_right = 0, + icon = { drawing = false }, + label = { + font = { size = 11 }, + width = 0, + max_chars = 16, + y_offset = -5, + }, +}) + +sbar.add("item", { + position = "popup." .. media_cover.name, + icon = { string = icons.media.back }, + label = { drawing = false }, + click_script = "nowplaying-cli previous", +}) +sbar.add("item", { + position = "popup." .. media_cover.name, + icon = { string = icons.media.play_pause }, + label = { drawing = false }, + click_script = "nowplaying-cli togglePlayPause", +}) +sbar.add("item", { + position = "popup." .. media_cover.name, + icon = { string = icons.media.forward }, + label = { drawing = false }, + click_script = "nowplaying-cli next", +}) + +local interrupt = 0 +local function animate_detail(detail) + if (not detail) then interrupt = interrupt - 1 end + if interrupt > 0 and (not detail) then return end + + sbar.animate("tanh", 30, function() + media_artist:set({ label = { width = detail and "dynamic" or 0 } }) + media_title:set({ label = { width = detail and "dynamic" or 0 } }) + end) +end + +media_cover:subscribe("media_change", function(env) + if whitelist[env.INFO.app] then + local drawing = (env.INFO.state == "playing") + media_artist:set({ drawing = drawing, label = env.INFO.artist, }) + media_title:set({ drawing = drawing, label = env.INFO.title, }) + media_cover:set({ drawing = drawing }) + + if drawing then + animate_detail(true) + interrupt = interrupt + 1 + sbar.delay(5, animate_detail) + else + media_cover:set({ popup = { drawing = false } }) + end + end +end) + +media_cover:subscribe("mouse.entered", function(env) + interrupt = interrupt + 1 + animate_detail(true) +end) + +media_cover:subscribe("mouse.exited", function(env) + animate_detail(false) +end) + +media_cover:subscribe("mouse.clicked", function(env) + media_cover:set({ popup = { drawing = "toggle" }}) +end) + +media_title:subscribe("mouse.exited.global", function(env) + media_cover:set({ popup = { drawing = false }}) +end) diff --git a/dot_config/sketchybar/items/menus.lua b/dot_config/sketchybar/items/menus.lua new file mode 100644 index 0000000..6a5e344 --- /dev/null +++ b/dot_config/sketchybar/items/menus.lua @@ -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 = "$CONFIG_DIR/helpers/menus/bin/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("$CONFIG_DIR/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 }) + 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 diff --git a/dot_config/sketchybar/items/spaces.lua b/dot_config/sketchybar/items/spaces.lua new file mode 100644 index 0000000..9d0dcf5 --- /dev/null +++ b/dot_config/sketchybar/items/spaces.lua @@ -0,0 +1,177 @@ +local colors = require("colors") +local icons = require("icons") +local settings = require("settings") +local app_icons = require("helpers.app_icons") + +local spaces = {} + +for i = 1, 10, 1 do + local space = sbar.add("space", "space." .. i, { + space = i, + icon = { + font = { family = settings.font.numbers }, + string = i, + padding_left = 15, + padding_right = 8, + color = colors.white, + highlight_color = colors.red, + }, + label = { + padding_right = 20, + color = colors.grey, + highlight_color = colors.white, + font = "sketchybar-app-font:Regular:16.0", + y_offset = -1, + }, + padding_right = 1, + padding_left = 1, + background = { + color = colors.bg1, + border_width = 1, + height = 26, + border_color = colors.black, + }, + popup = { background = { border_width = 5, border_color = colors.black } } + }) + + spaces[i] = space + + -- Single item bracket for space items to achieve double border on highlight + local space_bracket = sbar.add("bracket", { space.name }, { + background = { + color = colors.transparent, + border_color = colors.bg2, + height = 28, + border_width = 2 + } + }) + + -- Padding space + sbar.add("space", "space.padding." .. i, { + space = i, + script = "", + width = settings.group_paddings, + }) + + local space_popup = sbar.add("item", { + position = "popup." .. space.name, + padding_left= 5, + padding_right= 0, + background = { + drawing = true, + image = { + corner_radius = 9, + scale = 0.2 + } + } + }) + + space:subscribe("space_change", function(env) + local selected = env.SELECTED == "true" + local color = selected and colors.grey or colors.bg2 + space:set({ + icon = { highlight = selected, }, + label = { highlight = selected }, + background = { border_color = selected and colors.black or colors.bg2 } + }) + space_bracket:set({ + background = { border_color = selected and colors.grey or colors.bg2 } + }) + end) + + space:subscribe("mouse.clicked", function(env) + if env.BUTTON == "other" then + space_popup:set({ background = { image = "space." .. env.SID } }) + space:set({ popup = { drawing = "toggle" } }) + else + local op = (env.BUTTON == "right") and "--destroy" or "--focus" + sbar.exec("yabai -m space " .. op .. " " .. env.SID) + end + end) + + space:subscribe("mouse.exited", function(_) + space:set({ popup = { drawing = false } }) + end) +end + +local space_window_observer = sbar.add("item", { + drawing = false, + updates = true, +}) + +local spaces_indicator = sbar.add("item", { + padding_left = -3, + padding_right = 0, + icon = { + padding_left = 8, + padding_right = 9, + color = colors.grey, + string = icons.switch.on, + }, + label = { + width = 0, + padding_left = 0, + padding_right = 8, + string = "Spaces", + color = colors.bg1, + }, + background = { + color = colors.with_alpha(colors.grey, 0.0), + border_color = colors.with_alpha(colors.bg1, 0.0), + } +}) + +space_window_observer:subscribe("space_windows_change", function(env) + local icon_line = "" + local no_app = true + for app, count in pairs(env.INFO.apps) do + no_app = false + local lookup = app_icons[app] + local icon = ((lookup == nil) and app_icons["Default"] or lookup) + icon_line = icon_line .. icon + end + + if (no_app) then + icon_line = " —" + end + sbar.animate("tanh", 10, function() + spaces[env.INFO.space]:set({ label = icon_line }) + end) +end) + +spaces_indicator:subscribe("swap_menus_and_spaces", function(env) + 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.entered", function(env) + sbar.animate("tanh", 30, function() + spaces_indicator:set({ + background = { + color = { alpha = 1.0 }, + border_color = { alpha = 1.0 }, + }, + icon = { color = colors.bg1 }, + label = { width = "dynamic" } + }) + end) +end) + +spaces_indicator:subscribe("mouse.exited", function(env) + sbar.animate("tanh", 30, function() + spaces_indicator:set({ + background = { + color = { alpha = 0.0 }, + border_color = { alpha = 0.0 }, + }, + icon = { color = colors.grey }, + label = { width = 0, } + }) + end) +end) + +spaces_indicator:subscribe("mouse.clicked", function(env) + sbar.trigger("swap_menus_and_spaces") +end) diff --git a/dot_config/sketchybar/items/widgets/battery.lua b/dot_config/sketchybar/items/widgets/battery.lua new file mode 100644 index 0000000..88e74df --- /dev/null +++ b/dot_config/sketchybar/items/widgets/battery.lua @@ -0,0 +1,100 @@ +local icons = require("icons") +local colors = require("colors") +local settings = require("settings") + +local battery = sbar.add("item", "widgets.battery", { + position = "right", + icon = { + font = { + style = settings.font.style_map["Regular"], + size = 19.0, + } + }, + label = { font = { family = settings.font.numbers } }, + update_freq = 180, + popup = { align = "center" } +}) + +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" + }, +}) + + +battery:subscribe({"routine", "power_source_change", "system_woke"}, function() + sbar.exec("pmset -g batt", function(batt_info) + local icon = "!" + local label = "?" + + local found, _, charge = batt_info:find("(%d+)%%") + if found then + charge = tonumber(charge) + label = charge .. "%" + end + + local color = colors.green + 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 > 20 then + icon = icons.battery._25 + color = colors.orange + else + icon = icons.battery._0 + color = colors.red + end + end + + local lead = "" + if found and charge < 10 then + lead = "0" + end + + battery:set({ + icon = { + string = icon, + color = color + }, + label = { string = lead .. label }, + }) + end) +end) + +battery:subscribe("mouse.clicked", function(env) + local drawing = battery:query().popup.drawing + battery:set( { popup = { drawing = "toggle" } }) + + if drawing == "off" then + sbar.exec("pmset -g batt", function(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) + +sbar.add("bracket", "widgets.battery.bracket", { battery.name }, { + background = { color = colors.bg1 } +}) + +sbar.add("item", "widgets.battery.padding", { + position = "right", + width = settings.group_paddings +}) diff --git a/dot_config/sketchybar/items/widgets/cpu.lua b/dot_config/sketchybar/items/widgets/cpu.lua new file mode 100644 index 0000000..970da0e --- /dev/null +++ b/dot_config/sketchybar/items/widgets/cpu.lua @@ -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 cpu_load >/dev/null; $CONFIG_DIR/helpers/event_providers/cpu_load/bin/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 +}) diff --git a/dot_config/sketchybar/items/widgets/init.lua b/dot_config/sketchybar/items/widgets/init.lua new file mode 100644 index 0000000..c919c76 --- /dev/null +++ b/dot_config/sketchybar/items/widgets/init.lua @@ -0,0 +1,4 @@ +require("items.widgets.battery") +require("items.widgets.volume") +require("items.widgets.wifi") +require("items.widgets.cpu") diff --git a/dot_config/sketchybar/items/widgets/volume.lua b/dot_config/sketchybar/items/widgets/volume.lua new file mode 100644 index 0000000..14d1bb6 --- /dev/null +++ b/dot_config/sketchybar/items/widgets/volume.lua @@ -0,0 +1,152 @@ +local colors = require("colors") +local icons = require("icons") +local settings = require("settings") + +local popup_width = 250 + +local volume_percent = sbar.add("item", "widgets.volume1", { + position = "right", + icon = { drawing = false }, + label = { + string = "??%", + padding_left = -1, + font = { family = settings.font.numbers } + }, +}) + +local volume_icon = sbar.add("item", "widgets.volume2", { + position = "right", + padding_right = -1, + icon = { + string = icons.volume._100, + width = 0, + align = "left", + color = colors.grey, + font = { + style = settings.font.style_map["Regular"], + size = 14.0, + }, + }, + label = { + width = 25, + align = "left", + font = { + style = settings.font.style_map["Regular"], + size = 14.0, + }, + }, +}) + +local volume_bracket = sbar.add("bracket", "widgets.volume.bracket", { + volume_icon.name, + volume_percent.name +}, { + background = { color = colors.bg1 }, + popup = { align = "center" } +}) + +sbar.add("item", "widgets.volume.padding", { + position = "right", + width = settings.group_paddings +}) + +local volume_slider = sbar.add("slider", popup_width, { + position = "popup." .. volume_bracket.name, + slider = { + highlight_color = colors.blue, + background = { + height = 6, + corner_radius = 3, + color = colors.bg2, + }, + knob= { + string = "􀀁", + drawing = true, + }, + }, + background = { color = colors.bg1, height = 2, y_offset = -20 }, + click_script = 'osascript -e "set volume output volume $PERCENTAGE"' +}) + +volume_percent: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 + + local lead = "" + if volume < 10 then + lead = "0" + end + + volume_icon:set({ label = icon }) + volume_percent:set({ label = lead .. volume .. "%" }) + volume_slider:set({ slider = { percentage = volume } }) +end) + +local function volume_collapse_details() + local drawing = volume_bracket:query().popup.drawing == "on" + if not drawing then return end + volume_bracket: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_bracket:query().popup.drawing == "off" + if should_draw then + volume_bracket: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) + current = current_audio_device + local color = colors.grey + local counter = 0 + + for device in string.gmatch(available, '[^\r\n]+') do + local color = colors.grey + if current == device then + color = colors.white + end + sbar.add("item", "volume.device." .. counter, { + position = "popup." .. volume_bracket.name, + width = popup_width, + align = "center", + label = { string = device, color = color }, + click_script = 'SwitchAudioSource -s "' .. device .. '" && sketchybar --set /volume.device\\.*/ label.color=' .. colors.grey .. ' --set $NAME label.color=' .. colors.white + + }) + counter = counter + 1 + end + end) + end) + else + volume_collapse_details() + end +end + +local function volume_scroll(env) + local delta = env.INFO.delta + if not (env.INFO.modifier == "ctrl") then delta = delta * 10.0 end + + sbar.exec('osascript -e "set volume output volume (output volume of (get volume settings) + ' .. delta .. ')"') +end + +volume_icon:subscribe("mouse.clicked", volume_toggle_details) +volume_icon:subscribe("mouse.scrolled", volume_scroll) +volume_percent:subscribe("mouse.clicked", volume_toggle_details) +volume_percent:subscribe("mouse.exited.global", volume_collapse_details) +volume_percent:subscribe("mouse.scrolled", volume_scroll) + diff --git a/dot_config/sketchybar/items/widgets/wifi.lua b/dot_config/sketchybar/items/widgets/wifi.lua new file mode 100644 index 0000000..6153360 --- /dev/null +++ b/dot_config/sketchybar/items/widgets/wifi.lua @@ -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 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_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.bg1 }, + 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.grey, + 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) diff --git a/dot_config/sketchybar/settings.lua b/dot_config/sketchybar/settings.lua new file mode 100644 index 0000000..7713dc4 --- /dev/null +++ b/dot_config/sketchybar/settings.lua @@ -0,0 +1,22 @@ +return { + paddings = 3, + group_paddings = 5, + + icons = "sf-symbols", -- alternatively available: NerdFont + + -- This is a font configuration for SF Pro and SF Mono (installed manually) + font = require("helpers.default_font"), + + -- Alternatively, this is a font config for JetBrainsMono Nerd Font + -- font = { + -- text = "JetBrainsMono Nerd Font", -- Used for text + -- numbers = "JetBrainsMono Nerd Font", -- Used for numbers + -- style_map = { + -- ["Regular"] = "Regular", + -- ["Semibold"] = "Medium", + -- ["Bold"] = "SemiBold", + -- ["Heavy"] = "Bold", + -- ["Black"] = "ExtraBold", + -- }, + -- }, +}