diff --git a/.gitignore b/.gitignore index 2f05c58..dcea6d9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ result /.pre-commit-config.yaml home/emacs/config/var .direnv +.claude diff --git a/.stylua.toml b/.stylua.toml new file mode 100644 index 0000000..edfa506 --- /dev/null +++ b/.stylua.toml @@ -0,0 +1,7 @@ +column_width = 160 +line_endings = "Unix" +indent_type = "Spaces" +indent_width = 2 +quote_style = "AutoPreferSingle" +call_parentheses = "None" +collapse_simple_statement = "Always" diff --git a/bin/yabai-create-space b/bin/yabai-create-space new file mode 100755 index 0000000..7d4d5ab --- /dev/null +++ b/bin/yabai-create-space @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +# Ensure exactly 10 spaces exist on the main display (display 1) +# These can then be moved to other displays as needed + +DESIRED_SPACES=10 +MAIN_DISPLAY=1 + +# Get current number of spaces on main display +CURRENT_SPACE_COUNT=$(yabai -m query --spaces --display "$MAIN_DISPLAY" | jq 'length') + +echo "Current spaces on display $MAIN_DISPLAY: $CURRENT_SPACE_COUNT" +echo "Desired spaces: $DESIRED_SPACES" + +# Focus main display first +yabai -m display --focus "$MAIN_DISPLAY" + +if [ "$CURRENT_SPACE_COUNT" -lt "$DESIRED_SPACES" ]; then + MISSING_SPACES=$((DESIRED_SPACES - CURRENT_SPACE_COUNT)) + echo "Creating $MISSING_SPACES spaces on display $MAIN_DISPLAY..." + + for i in $(seq 1 $MISSING_SPACES); do + yabai -m space --create + echo "Created space $((CURRENT_SPACE_COUNT + i))" + done +elif [ "$CURRENT_SPACE_COUNT" -gt "$DESIRED_SPACES" ]; then + EXTRA_SPACES=$((CURRENT_SPACE_COUNT - DESIRED_SPACES)) + echo "Removing $EXTRA_SPACES extra spaces from display $MAIN_DISPLAY..." + + # Get the last space on main display and destroy it + for i in $(seq 1 $EXTRA_SPACES); do + LAST_SPACE=$(yabai -m query --spaces --display "$MAIN_DISPLAY" | jq 'map(select(."is-native-fullscreen" == false))[-1].index') + yabai -m space --destroy "$LAST_SPACE" + echo "Destroyed space $LAST_SPACE" + done +fi + +echo "Total spaces on display $MAIN_DISPLAY: $(yabai -m query --spaces --display $MAIN_DISPLAY | jq 'length')" + +sketchybar --trigger space_change --trigger windows_on_spaces diff --git a/config/fish/config.fish b/config/fish/config.fish index 318fcae..1426cf4 100644 --- a/config/fish/config.fish +++ b/config/fish/config.fish @@ -49,3 +49,5 @@ if functions -q fish_vi_key_bindings else bind \et sesh-list end + +alias vim "nvim" diff --git a/config/home/.hammerspoon/init.lua b/config/home/.hammerspoon/init.lua new file mode 100644 index 0000000..8da7566 --- /dev/null +++ b/config/home/.hammerspoon/init.lua @@ -0,0 +1,3 @@ +require 'keyboard' + +hs.alert.show 'Hammerspoon config loaded' diff --git a/config/home/.hammerspoon/keyboard/init.lua b/config/home/.hammerspoon/keyboard/init.lua new file mode 100644 index 0000000..75d3c1a --- /dev/null +++ b/config/home/.hammerspoon/keyboard/init.lua @@ -0,0 +1,12 @@ +local keymap = require 'keyboard.keymaps' + +require 'keyboard.yabai' + +-- General keymap +-- reload config +keymap.super { + key = 'c', + mods = { 'shift' }, + message = 'Reload Hammerspoon config', + fn = function() hs.reload() end, +} diff --git a/config/home/.hammerspoon/keyboard/keymaps.lua b/config/home/.hammerspoon/keyboard/keymaps.lua new file mode 100644 index 0000000..cea0219 --- /dev/null +++ b/config/home/.hammerspoon/keyboard/keymaps.lua @@ -0,0 +1,18 @@ +local M = {} + +M.super_key = 'alt' + +M.super = function(opts) + local mods = { M.super_key } + if opts.mods then + for _, mod in ipairs(opts.mods) do + table.insert(mods, mod) + end + end + + if opts.message then return hs.hotkey.bind(mods, opts.key, opts.message, opts.fn, opts.release, opts.repeat_fn) end + + return hs.hotkey.bind(mods, opts.key, opts.fn, opts.release, opts.repeat_fn) +end + +return M diff --git a/config/home/.hammerspoon/keyboard/yabai.lua b/config/home/.hammerspoon/keyboard/yabai.lua new file mode 100644 index 0000000..1e6f628 --- /dev/null +++ b/config/home/.hammerspoon/keyboard/yabai.lua @@ -0,0 +1,137 @@ +local keymap = require 'keyboard.keymaps' + +local yabai_cmd = '/etc/profiles/per-user/rayandrew/bin/yabai' +local jq_cmd = '/etc/profiles/per-user/rayandrew/bin/jq' + +local function yabai_query(query, jq_filter) + local cmd = yabai_cmd .. ' -m query ' .. query + if jq_filter then cmd = cmd .. ' | ' .. jq_cmd .. ' ' .. jq_filter end + local handle = io.popen(cmd) + local result = handle:read('*a'):gsub('%s+', '') + handle:close() + return result +end + +local function yabai(commands, logic) + local cmd_string = '' + for i, cmd in ipairs(commands) do + if i == 1 then + cmd_string = yabai_cmd .. ' -m ' .. cmd + else + if logic == 'and' then + cmd_string = cmd_string .. ' && ' .. yabai_cmd .. ' -m ' .. cmd + elseif logic == 'or' then + cmd_string = cmd_string .. ' || ' .. yabai_cmd .. ' -m ' .. cmd + else + cmd_string = cmd_string .. ' ; ' .. yabai_cmd .. ' -m ' .. cmd + end + end + end + os.execute(cmd_string) +end + +local function yabai_key(opts) + keymap.super { + key = opts.key, + mods = opts.mods, + message = opts.message, + fn = function() yabai(opts.commands, opts.logic) end, + } +end + +keymap.super { + key = 'return', + -- message = "Spawn Ghostty", + fn = function() + -- hs.application.launchOrFocus("Ghostty") + -- hs.timer.doAfter(0.1, function() + -- hs.eventtap.keyStroke({ "cmd" }, "n") + -- end) + os.execute 'open -na Ghostty' + end, +} + +yabai_key { key = 'f', commands = { 'window --toggle zoom-fullscreen' } } +yabai_key { key = 'l', commands = { 'space --focus recent' } } +yabai_key { key = 'm', commands = { 'space --toggle mission-control' } } +yabai_key { key = 'p', commands = { 'window --toggle pip' } } +yabai_key { key = 'g', commands = { 'space --toggle padding', 'space --toggle gap' } } +yabai_key { key = 'r', commands = { 'space --rotate 90' } } +yabai_key { key = 't', commands = { 'window --toggle float', 'window --grid 4:4:1:1:2:2' } } + +yabai_key { key = "'", commands = { 'space --layout stack' } } +yabai_key { key = ';', commands = { 'space --layout bsp' } } +yabai_key { key = 'tab', commands = { 'space --focus recent' } } + +-- navigation +yabai_key { key = 'h', commands = { 'window --focus west', 'display --focus west' }, logic = 'or' } +yabai_key { key = 'j', commands = { 'window --focus south', 'display --focus south' }, logic = 'or' } +yabai_key { key = 'k', commands = { 'window --focus north', 'display --focus north' }, logic = 'or' } +yabai_key { key = 'l', commands = { 'window --focus east', 'display --focus east' }, logic = 'or' } + +-- move windows (swap within space, or move to adjacent display if at edge) +yabai_key { mods = { 'shift' }, key = 'h', commands = { 'window --swap west', 'window --display west', 'display --focus west' }, logic = 'or' } +yabai_key { mods = { 'shift' }, key = 'j', commands = { 'window --swap south', 'window --display south', 'display --focus south' }, logic = 'or' } +yabai_key { mods = { 'shift' }, key = 'k', commands = { 'window --swap north', 'window --display north', 'display --focus north' }, logic = 'or' } +yabai_key { mods = { 'shift' }, key = 'l', commands = { 'window --swap east', 'window --display east', 'display --focus east' }, logic = 'or' } + +-- spaces - focus space and its display +local function focus_space(space_num) + local display = yabai_query('--spaces', "'.[] | select(.index == " .. space_num .. ") | .display'") + + if display and display ~= '' then + os.execute(yabai_cmd .. ' -m display --focus ' .. display) + hs.timer.usleep(50000) -- 50ms delay + end + + os.execute(yabai_cmd .. ' -m space --focus ' .. space_num) +end + +keymap.super { key = '1', fn = function() focus_space(1) end } +keymap.super { key = '2', fn = function() focus_space(2) end } +keymap.super { key = '3', fn = function() focus_space(3) end } +keymap.super { key = '4', fn = function() focus_space(4) end } +keymap.super { key = '5', fn = function() focus_space(5) end } +keymap.super { key = '6', fn = function() focus_space(6) end } +keymap.super { key = '7', fn = function() focus_space(7) end } +keymap.super { key = '8', fn = function() focus_space(8) end } +keymap.super { key = '9', fn = function() focus_space(9) end } +keymap.super { key = '0', fn = function() focus_space(10) end } + +-- move window to space and follow +yabai_key { mods = { 'shift' }, key = '1', commands = { 'window --space 1', 'space --focus 1' } } +yabai_key { mods = { 'shift' }, key = '2', commands = { 'window --space 2', 'space --focus 2' } } +yabai_key { mods = { 'shift' }, key = '3', commands = { 'window --space 3', 'space --focus 3' } } +yabai_key { mods = { 'shift' }, key = '4', commands = { 'window --space 4', 'space --focus 4' } } +yabai_key { mods = { 'shift' }, key = '5', commands = { 'window --space 5', 'space --focus 5' } } +yabai_key { mods = { 'shift' }, key = '6', commands = { 'window --space 6', 'space --focus 6' } } +yabai_key { mods = { 'shift' }, key = '7', commands = { 'window --space 7', 'space --focus 7' } } +yabai_key { mods = { 'shift' }, key = '8', commands = { 'window --space 8', 'space --focus 8' } } +yabai_key { mods = { 'shift' }, key = '9', commands = { 'window --space 9', 'space --focus 9' } } +yabai_key { mods = { 'shift' }, key = '0', commands = { 'window --space 10', 'space --focus 10' } } + +-- move current space between displays +yabai_key { mods = { 'ctrl' }, key = 'h', message = 'Move space to left display', commands = { 'space --display west' } } +yabai_key { mods = { 'ctrl' }, key = 'l', message = 'Move space to right display', commands = { 'space --display east' } } + +-- create new space and move window to it +keymap.super { + key = 'n', + mods = { 'shift' }, + message = 'Create new space and move window', + fn = function() + os.execute(yabai_cmd .. ' -m space --create') + hs.timer.usleep(100000) -- 100ms + + local index = yabai_query('--spaces --display', '\'map(select(."is-native-fullscreen" == false))[-1].index\'') + + if index and index ~= '' then + os.execute(yabai_cmd .. ' -m window --space ' .. index) + os.execute(yabai_cmd .. ' -m space --focus ' .. index) + end + end, +} + +-- split type - use insert direction to control next window placement +yabai_key { key = 'v', commands = { 'window --insert east' } } +yabai_key { key = 'v', mods = { 'shift' }, commands = { 'window --insert south' } } diff --git a/config/sketchybar/app_icons.lua b/config/sketchybar/app_icons.lua new file mode 100644 index 0000000..bbf7b48 --- /dev/null +++ b/config/sketchybar/app_icons.lua @@ -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:', +} diff --git a/config/sketchybar/bar.lua b/config/sketchybar/bar.lua new file mode 100644 index 0000000..3ce9910 --- /dev/null +++ b/config/sketchybar/bar.lua @@ -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, +} diff --git a/config/sketchybar/colors.lua b/config/sketchybar/colors.lua new file mode 100644 index 0000000..e55ab0d --- /dev/null +++ b/config/sketchybar/colors.lua @@ -0,0 +1,49 @@ +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 { + fg = 0xffd3b58d, + black = 0xff181819, + white = 0xffd3b58d, + red = 0xffF92672, + blue = 0xff66D9EF, + grey = 0xff7f8490, + transparent = 0x00000000, + + bg1 = 0xff072626, + bg2 = 0xff1a1b1c, + + bar = { + bg = 0xff072626, + border = 0xffFD971F, + }, + popup = { + bg = with_alpha(0xff072626, 0.6), + border = 0xffFD971F, + }, + + with_alpha = with_alpha, +} + +-- return { +-- fg = 0xff181819, +-- black = 0xff181819, +-- white = 0xffffffff, +-- red = 0xffff0000, +-- blue = 0xff0000ff, +-- grey = 0xff7f8490, +-- transparent = 0x00000000, +-- +-- bar = { +-- bg = 0xffffffff, +-- border = 0xff000000, +-- }, +-- popup = { +-- bg = with_alpha(0xffffffff, 0.6), +-- border = 0xff000000, +-- }, +-- +-- with_alpha = with_alpha, +-- } diff --git a/config/sketchybar/icons.lua b/config/sketchybar/icons.lua new file mode 100644 index 0000000..d9b4d05 --- /dev/null +++ b/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/config/sketchybar/init.lua b/config/sketchybar/init.lua new file mode 100644 index 0000000..91aec1e --- /dev/null +++ b/config/sketchybar/init.lua @@ -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.fg, + 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.fg, + 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' diff --git a/config/sketchybar/items/aerospace.lua b/config/sketchybar/items/aerospace.lua new file mode 100644 index 0000000..852c034 --- /dev/null +++ b/config/sketchybar/items/aerospace.lua @@ -0,0 +1,242 @@ +-- https://github.com/Tnixc/nix-config/blob/main/home/programs/aerospace-sketchybar/sbar-config-libs/items/aerospaces.lua + +local Promise = require 'promise' +local colors = require 'colors' +local utils = require 'utils' +local settings = require 'settings' +local app_icons = require 'app_icons' + +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-name'] then + if obj['monitor-name'] == 'ZOWIE XL LCD' then + return '2' + elseif obj['monitor-name'] == 'LG ULTRAWIDE' then + return '1' + end + end + 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.fg, + highlight_color = colors.red, + }, + label = { + padding_right = 12, + color = colors.fg, + highlight_color = colors.blue, + 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() diff --git a/config/sketchybar/items/apple.lua b/config/sketchybar/items/apple.lua new file mode 100644 index 0000000..82cbde2 --- /dev/null +++ b/config/sketchybar/items/apple.lua @@ -0,0 +1,37 @@ +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, + color = colors.fg, + }, + label = { drawing = false }, + background = { + color = colors.bar.bg, + -- 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 }) diff --git a/config/sketchybar/items/battery.lua b/config/sketchybar/items/battery.lua new file mode 100644 index 0000000..0496538 --- /dev/null +++ b/config/sketchybar/items/battery.lua @@ -0,0 +1,42 @@ +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) diff --git a/config/sketchybar/items/cal.lua b/config/sketchybar/items/cal.lua new file mode 100644 index 0000000..2eac508 --- /dev/null +++ b/config/sketchybar/items/cal.lua @@ -0,0 +1,50 @@ +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.fg, + padding_left = 0, + font = { size = 13 }, + }, + label = { + color = colors.fg, + 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) +cal:subscribe({ 'forced', 'routine', 'system_woke' }, function(env) + cal:set { + icon = os.date '􀉉 %B %d %a', + label = os.date '􀐫 %I:%M %p', -- 12-hour format with AM/PM + } +end) diff --git a/config/sketchybar/items/cpu.lua b/config/sketchybar/items/cpu.lua new file mode 100644 index 0000000..4bb2f3d --- /dev/null +++ b/config/sketchybar/items/cpu.lua @@ -0,0 +1,67 @@ +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, +}) diff --git a/config/sketchybar/items/front_app.lua b/config/sketchybar/items/front_app.lua new file mode 100644 index 0000000..81b3024 --- /dev/null +++ b/config/sketchybar/items/front_app.lua @@ -0,0 +1,31 @@ +local settings = require 'settings' +local app_icons = require 'app_icons' + +local front_app = sbar.add('item', 'front_app', { + icon = { + font = 'sketchybar-app-font:Regular:16.0', + padding_left = 8, + padding_right = 8, + }, + label = { + font = { + style = settings.font.style_map['Bold'], + size = 12.0, + }, + padding_right = 8, + }, +}) + +front_app:subscribe('front_app_switched', function(env) + local app_name = env.INFO + local icon = app_icons[app_name] or app_icons['Default'] + + front_app:set { + icon = { + string = icon, + }, + label = { + string = app_name, + }, + } +end) diff --git a/config/sketchybar/items/init.lua b/config/sketchybar/items/init.lua new file mode 100644 index 0000000..0fac057 --- /dev/null +++ b/config/sketchybar/items/init.lua @@ -0,0 +1,33 @@ +local function file_exists(path) + local f = io.open(path, 'r') + if f then + f:close() + return true + end + return false +end + +local function load_module_if_program_exists(module_name, binary_path) + if file_exists(binary_path) then + print('Loading ' .. module_name .. ' (found at ' .. binary_path .. ')') + local ok, err = pcall(require, module_name) + if not ok then + print('ERROR loading ' .. module_name .. ' module: ' .. tostring(err)) + else + print('Successfully loaded ' .. module_name) + end + else + print('Skipping ' .. module_name .. ' (binary not found at ' .. binary_path .. ')') + end +end + +require 'items.apple' +load_module_if_program_exists('items.aerospace', '/opt/homebrew/bin/aerospace') +load_module_if_program_exists('items.yabai', '/etc/profiles/per-user/rayandrew/bin/yabai') +require 'items.front_app' +require 'items.menu' +require 'items.battery' +require 'items.cal' +require 'items.volume' +require 'items.cpu' +require 'items.wifi' diff --git a/config/sketchybar/items/menu.lua b/config/sketchybar/items/menu.lua new file mode 100644 index 0000000..d116fea --- /dev/null +++ b/config/sketchybar/items/menu.lua @@ -0,0 +1,78 @@ +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 diff --git a/config/sketchybar/items/volume.lua b/config/sketchybar/items/volume.lua new file mode 100644 index 0000000..3dc9739 --- /dev/null +++ b/config/sketchybar/items/volume.lua @@ -0,0 +1,75 @@ +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) diff --git a/config/sketchybar/items/wifi.lua b/config/sketchybar/items/wifi.lua new file mode 100644 index 0000000..be27ad9 --- /dev/null +++ b/config/sketchybar/items/wifi.lua @@ -0,0 +1,219 @@ +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.fg, + 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.fg or colors.red + local down_color = (env.download == '000 Bps') and colors.fg 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.fg 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/config/sketchybar/items/yabai.lua b/config/sketchybar/items/yabai.lua new file mode 100644 index 0000000..cccafdb --- /dev/null +++ b/config/sketchybar/items/yabai.lua @@ -0,0 +1,190 @@ +local colors = require 'colors' +-- local icons = require("icons") +local settings = require 'settings' +local app_icons = require 'app_icons' + +local spaces = {} +local space_has_windows = {} + +local yabai_cmd = '/etc/profiles/per-user/rayandrew/bin/yabai' + +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', 'space.popup.' .. i, { + position = 'popup.space.' .. i, + 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 + local has_windows = space_has_windows[i] or false + + space:set { + icon = { highlight = selected }, + label = { highlight = selected }, + background = { border_color = selected and colors.black or colors.bg2 }, + drawing = selected or has_windows, + } + 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_cmd .. ' -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 + + -- Track window state + space_has_windows[env.INFO.space] = not no_app + + -- Query if this space is currently selected + local space_info = spaces[env.INFO.space]:query() + local is_selected = space_info and space_info.icon and space_info.icon.highlight or false + + sbar.animate('tanh', 10, function() + spaces[env.INFO.space]:set { + label = icon_line, + drawing = is_selected or not no_app, + } + 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/config/sketchybar/settings.lua b/config/sketchybar/settings.lua new file mode 100644 index 0000000..ecc9493 --- /dev/null +++ b/config/sketchybar/settings.lua @@ -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, +} diff --git a/config/sketchybar/utils.lua b/config/sketchybar/utils.lua new file mode 100644 index 0000000..f9e292d --- /dev/null +++ b/config/sketchybar/utils.lua @@ -0,0 +1,33 @@ +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 diff --git a/config/yabai/yabairc b/config/yabai/yabairc new file mode 100644 index 0000000..7d24de8 --- /dev/null +++ b/config/yabai/yabairc @@ -0,0 +1,49 @@ +sudo $(readlink -f $(which yabai)) --load-sa +yabai -m signal --add event=dock_did_restart action="sudo $(readlink -f $(which yabai)) --load-sa" + +yabai -m config layout bsp +yabai -m config top_padding 5 +yabai -m config bottom_padding 5 +yabai -m config left_padding 15 +yabai -m config right_padding 15 +yabai -m config window_gap 15 + +yabai -m config auto_balance off + +yabai -m config mouse_follows_focus off +yabai -m config focus_follows_mouse autofocus +yabai -m config mouse_modifier fn +yabai -m config mouse_action1 move +yabai -m config mouse_action2 resize +yabai -m config mouse_drop_action swap + +yabai -m config external_bar all:45:0 +yabai -m config window_shadow float + +yabai -m config split_ratio 0.50 +yabai -m config window_placement second_child + +yabai -m config window_opacity on +yabai -m config active_window_opacity 1.0 +yabai -m config normal_window_opacity 0.9 + +yabai -m config window_border off +yabai -m config window_border_width 6 +yabai -m config active_window_border_color 0xff775759 +yabai -m config normal_window_border_color 0xff555555 +yabai -m config insert_feedback_color 0xffd75f5f + +yabai -m rule --add app="^System Settings$" manage=off +yabai -m rule --add app="^Archive Utility$" manage=off +yabai -m rule --add app="^Wally$" manage=off +yabai -m rule --add app="^Pika$" manage=off +yabai -m rule --add app="^balenaEtcher$" manage=off +yabai -m rule --add app="^Creative Cloud$" manage=off +yabai -m rule --add app="^Logi Options$" manage=off +yabai -m rule --add app="^Alfred Preferences$" manage=off +yabai -m rule --add app="Raycast" manage=off +yabai -m rule --add app="^Music$" manage=off + +yabai-create-space + +echo "yabai configuration loaded..." diff --git a/darwin/aerospace.nix b/darwin/aerospace.nix index 67b1edc..bda5403 100644 --- a/darwin/aerospace.nix +++ b/darwin/aerospace.nix @@ -7,10 +7,10 @@ }: let - cfg = config.custom.aerospace; + cfg = config.custom.gui.aerospace; in { - options.custom = with lib; { + options.custom.gui = with lib; { aerospace = { enable = mkEnableOption "Enable aerospace"; logFile = mkOption { diff --git a/darwin/default.nix b/darwin/default.nix index c7e9898..1641d28 100644 --- a/darwin/default.nix +++ b/darwin/default.nix @@ -13,6 +13,7 @@ ./keyboard.nix ./sketchybar ./nix.nix + ./yabai.nix ]; options.custom = with lib; { diff --git a/darwin/homebrew.nix b/darwin/homebrew.nix index b99cb07..e564ccb 100644 --- a/darwin/homebrew.nix +++ b/darwin/homebrew.nix @@ -64,6 +64,7 @@ clang-format = mkEnableOption "Enable clang-format"; marta = mkEnableOption "Enable marta"; valgrind = mkEnableOption "Enable valgrind"; + hammerspoon = mkEnableOption "Enable hammerspoon"; }; config = lib.mkMerge [ @@ -215,7 +216,7 @@ }) (lib.mkIf config.custom.brew.flux { homebrew.casks = [ - "flux" + "flux-app" ]; }) (lib.mkIf config.custom.brew.cleanshot { @@ -376,5 +377,10 @@ # } # ]; }) + (lib.mkIf config.custom.brew.hammerspoon { + homebrew.casks = [ + "hammerspoon" + ]; + }) ]; } diff --git a/darwin/jankyborders.nix b/darwin/jankyborders.nix index 7ce0ac8..95c45c5 100644 --- a/darwin/jankyborders.nix +++ b/darwin/jankyborders.nix @@ -1,17 +1,15 @@ { - pkgs, lib, config, - dots, home-dir, ... }: let - cfg = config.custom.jankyborders; + cfg = config.custom.gui.jankyborders; in { - options.custom = with lib; { + options.custom.gui = with lib; { jankyborders = { enable = mkEnableOption "Enable jankyborders"; logFile = mkOption { diff --git a/darwin/keyboard.nix b/darwin/keyboard.nix index d37203d..c460bac 100644 --- a/darwin/keyboard.nix +++ b/darwin/keyboard.nix @@ -1,4 +1,4 @@ -{ config, ... }: +{ ... }: { system.keyboard = { enableKeyMapping = true; diff --git a/darwin/sketchybar/config/app_icons.lua b/darwin/sketchybar/config/app_icons.lua deleted file mode 100644 index 9ddba7c..0000000 --- a/darwin/sketchybar/config/app_icons.lua +++ /dev/null @@ -1,321 +0,0 @@ --- 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:", -} diff --git a/darwin/sketchybar/config/bar.lua b/darwin/sketchybar/config/bar.lua deleted file mode 100644 index 0ec641c..0000000 --- a/darwin/sketchybar/config/bar.lua +++ /dev/null @@ -1,23 +0,0 @@ -#!/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, -}) diff --git a/darwin/sketchybar/config/colors.lua b/darwin/sketchybar/config/colors.lua deleted file mode 100644 index 112965f..0000000 --- a/darwin/sketchybar/config/colors.lua +++ /dev/null @@ -1,48 +0,0 @@ -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 { - fg = 0xffd3b58d, - 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, -} - --- return { --- fg = 0xff181819, --- black = 0xff181819, --- white = 0xffffffff, --- red = 0xffff0000, --- blue = 0xff0000ff, --- grey = 0xff7f8490, --- transparent = 0x00000000, --- --- bar = { --- bg = 0xffffffff, --- border = 0xff000000, --- }, --- popup = { --- bg = with_alpha(0xffffffff, 0.6), --- border = 0xff000000, --- }, --- --- with_alpha = with_alpha, --- } diff --git a/darwin/sketchybar/config/icons.lua b/darwin/sketchybar/config/icons.lua deleted file mode 100644 index 844b35c..0000000 --- a/darwin/sketchybar/config/icons.lua +++ /dev/null @@ -1,92 +0,0 @@ -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/darwin/sketchybar/config/init.lua b/darwin/sketchybar/config/init.lua deleted file mode 100644 index 3194f01..0000000 --- a/darwin/sketchybar/config/init.lua +++ /dev/null @@ -1,56 +0,0 @@ -#!/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.fg, - 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.fg, - 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") diff --git a/darwin/sketchybar/config/items/aerospace.lua b/darwin/sketchybar/config/items/aerospace.lua deleted file mode 100644 index 44466c3..0000000 --- a/darwin/sketchybar/config/items/aerospace.lua +++ /dev/null @@ -1,252 +0,0 @@ --- https://github.com/Tnixc/nix-config/blob/main/home/programs/aerospace-sketchybar/sbar-config-libs/items/aerospaces.lua - -local Promise = require("promise") -local colors = require("colors") -local utils = require("utils") -local settings = require("settings") -local app_icons = require("app_icons") - -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-name"] then - if obj["monitor-name"] == "ZOWIE XL LCD" then - return "2" - elseif obj["monitor-name"] == "LG ULTRAWIDE" then - return "1" - end - end - 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.fg, - highlight_color = colors.red, - }, - label = { - padding_right = 12, - color = colors.fg, - highlight_color = colors.blue, - 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() diff --git a/darwin/sketchybar/config/items/apple.lua b/darwin/sketchybar/config/items/apple.lua deleted file mode 100644 index f4726ce..0000000 --- a/darwin/sketchybar/config/items/apple.lua +++ /dev/null @@ -1,37 +0,0 @@ -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, - color = colors.fg, - }, - label = { drawing = false }, - background = { - color = colors.bar.bg, - -- 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 }) diff --git a/darwin/sketchybar/config/items/battery.lua b/darwin/sketchybar/config/items/battery.lua deleted file mode 100644 index 8239081..0000000 --- a/darwin/sketchybar/config/items/battery.lua +++ /dev/null @@ -1,44 +0,0 @@ -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) diff --git a/darwin/sketchybar/config/items/cal.lua b/darwin/sketchybar/config/items/cal.lua deleted file mode 100644 index 2c67004..0000000 --- a/darwin/sketchybar/config/items/cal.lua +++ /dev/null @@ -1,50 +0,0 @@ -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.fg, - padding_left = 0, - font = { size = 13 }, - }, - label = { - color = colors.fg, - 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) -cal:subscribe({ "forced", "routine", "system_woke" }, function(env) - cal:set({ - icon = os.date("􀉉 %B %d %a"), - label = os.date("􀐫 %I:%M %p"), -- 12-hour format with AM/PM - }) -end) diff --git a/darwin/sketchybar/config/items/cpu.lua b/darwin/sketchybar/config/items/cpu.lua deleted file mode 100644 index d468419..0000000 --- a/darwin/sketchybar/config/items/cpu.lua +++ /dev/null @@ -1,69 +0,0 @@ -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, -}) diff --git a/darwin/sketchybar/config/items/front_app.lua b/darwin/sketchybar/config/items/front_app.lua deleted file mode 100644 index 04fc65b..0000000 --- a/darwin/sketchybar/config/items/front_app.lua +++ /dev/null @@ -1,28 +0,0 @@ -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) diff --git a/darwin/sketchybar/config/items/init.lua b/darwin/sketchybar/config/items/init.lua deleted file mode 100644 index 1ea8912..0000000 --- a/darwin/sketchybar/config/items/init.lua +++ /dev/null @@ -1,9 +0,0 @@ -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") diff --git a/darwin/sketchybar/config/items/menu.lua b/darwin/sketchybar/config/items/menu.lua deleted file mode 100644 index 431c123..0000000 --- a/darwin/sketchybar/config/items/menu.lua +++ /dev/null @@ -1,78 +0,0 @@ -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 diff --git a/darwin/sketchybar/config/items/volume.lua b/darwin/sketchybar/config/items/volume.lua deleted file mode 100644 index 7566cfe..0000000 --- a/darwin/sketchybar/config/items/volume.lua +++ /dev/null @@ -1,79 +0,0 @@ -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) diff --git a/darwin/sketchybar/config/items/wifi.lua b/darwin/sketchybar/config/items/wifi.lua deleted file mode 100644 index bb37ef0..0000000 --- a/darwin/sketchybar/config/items/wifi.lua +++ /dev/null @@ -1,233 +0,0 @@ -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.fg, - 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.fg or colors.red - local down_color = (env.download == "000 Bps") and colors.fg 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.fg 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/darwin/sketchybar/config/settings.lua b/darwin/sketchybar/config/settings.lua deleted file mode 100644 index a7fe033..0000000 --- a/darwin/sketchybar/config/settings.lua +++ /dev/null @@ -1,19 +0,0 @@ -#!/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, -} diff --git a/darwin/sketchybar/config/utils.lua b/darwin/sketchybar/config/utils.lua deleted file mode 100644 index dac811e..0000000 --- a/darwin/sketchybar/config/utils.lua +++ /dev/null @@ -1,41 +0,0 @@ -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 diff --git a/darwin/sketchybar/default.nix b/darwin/sketchybar/default.nix index dd7faf8..0d3c082 100644 --- a/darwin/sketchybar/default.nix +++ b/darwin/sketchybar/default.nix @@ -8,7 +8,7 @@ }: let - cfg = config.custom.sketchybar; + cfg = config.custom.gui.sketchybar; sketchybar = lib.getExe pkgs.sketchybar; lua = pkgs.lua5_4.withPackages ( ps: with ps; [ @@ -19,7 +19,7 @@ let ); in { - options.custom = with lib; { + options.custom.gui = with lib; { sketchybar = { enable = mkEnableOption "Enable sketchybar"; logFile = mkOption { @@ -56,7 +56,7 @@ in -- 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 + package.path = "${dots}/config/sketchybar/?.lua;${dots}/config/sketchybar/?/?.lua;${dots}/config/sketchybar/?/init.lua;" .. package.path sbar = require("sketchybar") sbar.exec("killall sketchyhelper || sketchyhelper git.felix.sketchyhelper >/dev/null 2>&1 &") diff --git a/darwin/yabai.nix b/darwin/yabai.nix new file mode 100644 index 0000000..e6bdf36 --- /dev/null +++ b/darwin/yabai.nix @@ -0,0 +1,52 @@ +{ + config, + pkgs, + lib, + user, + dots, + ... +}: +{ + options.custom.gui = with lib; { + yabai = { + enable = mkEnableOption "Enable yabai"; + }; + }; + + config = lib.mkIf (config.custom.gui.yabai.enable && pkgs.stdenv.isDarwin) { + # csrutil enable --without fs --without debug --without nvram + # nvram boot-args=-arm64e_preview_abi + + # Generate sudoers file with SHA256 hash computed at activation time + system.activationScripts.yabaiSudoers.text = '' + echo "setting up yabai sudoers..." >&2 + YABAI_BIN="${pkgs.yabai}/bin/yabai" + YABAI_HASH=$(shasum -a 256 "$YABAI_BIN" | ${pkgs.gawk}/bin/awk '{print $1}') + + echo "${user} ALL = (root) NOPASSWD: sha256:$YABAI_HASH $YABAI_BIN --load-sa" > /etc/sudoers.d/yabai + chmod 0440 /etc/sudoers.d/yabai + ''; + + system.activationScripts.yabaiScriptingAddition.text = '' + echo "loading yabai scripting addition..." >&2 + sudo ${pkgs.yabai}/bin/yabai --load-sa + ''; + + hm.home.packages = with pkgs; [ + yabai + ]; + + hm.xdg.configFile."yabai".source = config.hm.lib.file.mkOutOfStoreSymlink "${dots}/config/yabai"; + + hm.home.activation.yabaiService = config.hm.lib.dag.entryAfter [ "writeBoundary" ] '' + echo "Restarting yabai service..." >&2 + $DRY_RUN_CMD ${pkgs.yabai}/bin/yabai --uninstall-service || true + $DRY_RUN_CMD ${pkgs.yabai}/bin/yabai --install-service + $DRY_RUN_CMD ${pkgs.yabai}/bin/yabai --start-service + ''; + + hm.home.shellAliases = { + restart-yabai = ''${pkgs.yabai}/bin/yabai --uninstall-service || ${pkgs.yabai}/bin/yabai --install-service || ${pkgs.yabai}/bin/yabai --start-service''; + }; + }; +} diff --git a/home/gui.nix b/home/gui.nix index 4d4f8b9..f302db4 100644 --- a/home/gui.nix +++ b/home/gui.nix @@ -9,6 +9,7 @@ { imports = [ ./i3 + ./hammerspoon.nix ./kitty.nix ./ghostty.nix ]; diff --git a/home/hammerspoon.nix b/home/hammerspoon.nix new file mode 100644 index 0000000..e671636 --- /dev/null +++ b/home/hammerspoon.nix @@ -0,0 +1,19 @@ +{ + lib, + config, + dots, + ... +}: +{ + options.custom.gui = with lib; { + hammerspoon = { + enable = mkEnableOption "Enable hammerspoon"; + }; + }; + + config = lib.mkIf config.custom.gui.hammerspoon.enable { + home.file.".hammerspoon".source = + config.lib.file.mkOutOfStoreSymlink "${dots}/config/home/.hammerspoon"; + }; + +} diff --git a/hosts/dango/default.nix b/hosts/dango/default.nix index 1aa9da3..f214129 100644 --- a/hosts/dango/default.nix +++ b/hosts/dango/default.nix @@ -4,9 +4,12 @@ }: { custom = { - aerospace.enable = false; - sketchybar.enable = false; - jankyborders.enable = false; + gui = { + aerospace.enable = false; + sketchybar.enable = true; + jankyborders.enable = false; + yabai.enable = true; + }; brew = { zen-browser = false; webex = true; @@ -60,6 +63,7 @@ clang-format = true; marta = true; valgrind = true; + hammerspoon = true; }; }; @@ -83,6 +87,7 @@ default.enable = true; darwin.enable = true; ghostty.enable = true; + hammerspoon.enable = true; }; }; } diff --git a/packages/promise-lua/default.nix b/packages/promise-lua/default.nix index a0d2beb..c562f91 100644 --- a/packages/promise-lua/default.nix +++ b/packages/promise-lua/default.nix @@ -1,16 +1,8 @@ # 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"; diff --git a/packages/sbarlua/default.nix b/packages/sbarlua/default.nix index fe6d945..bcf7717 100644 --- a/packages/sbarlua/default.nix +++ b/packages/sbarlua/default.nix @@ -1,47 +1,3 @@ -# 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, @@ -51,9 +7,8 @@ lua5_4, lua54Packages, stdenv, - darwin, lib, - luaPackages, + apple-sdk_12, }: lua54Packages.buildLuaPackage rec { pname = "sbar"; @@ -74,5 +29,6 @@ lua54Packages.buildLuaPackage rec { readline clang stdenv - ] ++ lib.optionals stdenv.isDarwin (with darwin.apple_sdk.frameworks; [ CoreFoundation ]); + ] + ++ lib.optionals stdenv.isDarwin [ apple-sdk_12 ]; }