diff --git a/aerospace/.config/aerospace/aerospace.toml b/aerospace/.config/aerospace/aerospace.toml new file mode 100644 index 0000000..1c02e3a --- /dev/null +++ b/aerospace/.config/aerospace/aerospace.toml @@ -0,0 +1,147 @@ +# You can use it to add commands that run after login to macOS user session. +# 'start-at-login' needs to be 'true' for 'after-login-command' to work +# Available commands: https://nikitabobko.github.io/AeroSpace/commands +after-login-command = [] + +# You can use it to add commands that run after AeroSpace startup. +# 'after-startup-command' is run after 'after-login-command' +# Available commands : https://nikitabobko.github.io/AeroSpace/commands +after-startup-command = [] + +# Start AeroSpace at login +start-at-login = true + +# Normalizations. See: https://nikitabobko.github.io/AeroSpace/guide#normalization +enable-normalization-flatten-containers = true +enable-normalization-opposite-orientation-for-nested-containers = true + +# See: https://nikitabobko.github.io/AeroSpace/guide#layouts +# The 'accordion-padding' specifies the size of accordion padding +# You can set 0 to disable the padding feature +accordion-padding = 30 + +# Possible values: tiles|accordion +default-root-container-layout = 'tiles' + +# Possible values: horizontal|vertical|auto +# 'auto' means: wide monitor (anything wider than high) gets horizontal orientation, +# tall monitor (anything higher than wide) gets vertical orientation +default-root-container-orientation = 'auto' + +# Possible values: (qwerty|dvorak) +# See https://nikitabobko.github.io/AeroSpace/guide#key-mapping +key-mapping.preset = 'qwerty' + +# Gaps between windows (inner-*) and between monitor edges (outer-*). +# Possible values: +# - Constant: gaps.outer.top = 8 +# - Per monitor: gaps.outer.top = [{ monitor.main = 16 }, { monitor."some-pattern" = 32 }, 24] +# In this example, 24 is a default value when there is no match. +# Monitor pattern is the same as for 'workspace-to-monitor-force-assignment'. +# See: https://nikitabobko.github.io/AeroSpace/guide#assign-workspaces-to-monitors +[gaps] +inner.horizontal = 10 +inner.vertical = 10 +outer.left = 10 +outer.bottom = 10 +outer.top = 10 +outer.right = 10 + +# See https://nikitabobko.github.io/AeroSpace/guide#exec-env-vars +[exec] # Again, you don't need to copy all config sections to your config. +inherit-env-vars = true # If you don't touch "exec" section, +[exec.env-vars] # it will fallback to "default-config.toml" +PATH = '/opt/homebrew/bin:/opt/homebrew/sbin:${PATH}' + +# 'main' binding mode declaration +# See: https://nikitabobko.github.io/AeroSpace/guide#binding-modes +# 'main' binding mode must be always presented +[mode.main.binding] + +# All possible keys: +# - Letters. a, b, c, ..., z +# - Numbers. 0, 1, 2, ..., 9 +# - Keypad numbers. keypad0, keypad1, keypad2, ..., keypad9 +# - F-keys. f1, f2, ..., f20 +# - Special keys. minus, equal, period, comma, slash, backslash, quote, semicolon, backtick, +# leftSquareBracket, rightSquareBracket, space, enter, esc, backspace, tab +# - Keypad special. keypadClear, keypadDecimalMark, keypadDivide, keypadEnter, keypadEqual, +# keypadMinus, keypadMultiply, keypadPlus +# - Arrows. left, down, up, right + +# All possible modifiers: cmd, alt, ctrl, shift + +# All possible commands: https://nikitabobko.github.io/AeroSpace/commands + +# You can uncomment this line to open up terminal with alt + enter shortcut +# See: https://nikitabobko.github.io/AeroSpace/commands#exec-and-forget +# alt-enter = 'exec-and-forget open -n /System/Applications/Utilities/Terminal.app' + +# See: https://nikitabobko.github.io/AeroSpace/commands#layout +alt-slash = 'layout tiles horizontal' +alt-comma = 'layout tiles vertical' + +# See: https://nikitabobko.github.io/AeroSpace/commands#focus +alt-h = 'focus left' +alt-j = 'focus down' +alt-k = 'focus up' +alt-l = 'focus right' + +# See: https://nikitabobko.github.io/AeroSpace/commands#move +alt-shift-h = 'move left' +alt-shift-j = 'move down' +alt-shift-k = 'move up' +alt-shift-l = 'move right' + +# See: https://nikitabobko.github.io/AeroSpace/commands#resize +alt-shift-minus = 'resize smart -50' +alt-shift-equal = 'resize smart +50' + +# See: https://nikitabobko.github.io/AeroSpace/commands#workspace +alt-1 = 'workspace 1' +alt-2 = 'workspace 2' +alt-3 = 'workspace 3' +alt-4 = 'workspace 4' +alt-5 = 'workspace 5' +alt-6 = 'workspace 6' +alt-7 = 'workspace 7' +alt-8 = 'workspace 8' +alt-9 = 'workspace 9' +alt-0 = 'workspace 0' + +# See: https://nikitabobko.github.io/AeroSpace/commands#move-node-to-workspace +alt-shift-1 = 'move-node-to-workspace 1' +alt-shift-2 = 'move-node-to-workspace 2' +alt-shift-3 = 'move-node-to-workspace 3' +alt-shift-4 = 'move-node-to-workspace 4' +alt-shift-5 = 'move-node-to-workspace 5' +alt-shift-6 = 'move-node-to-workspace 6' +alt-shift-7 = 'move-node-to-workspace 7' +alt-shift-8 = 'move-node-to-workspace 8' +alt-shift-9 = 'move-node-to-workspace 9' +alt-shift-0 = 'move-node-to-workspace 0' + +# See: https://nikitabobko.github.io/AeroSpace/commands#workspace-back-and-forth +alt-tab = 'workspace-back-and-forth' +# See: https://nikitabobko.github.io/AeroSpace/commands#move-workspace-to-monitor +alt-shift-tab = 'move-workspace-to-monitor --wrap-around next' + +# See: https://nikitabobko.github.io/AeroSpace/commands#mode +alt-shift-semicolon = 'mode service' + +# 'service' binding mode declaration. +# See: https://nikitabobko.github.io/AeroSpace/guide#binding-modes +[mode.service.binding] +esc = ['reload-config', 'mode main'] +r = ['flatten-workspace-tree', 'mode main'] # reset layout +#s = ['layout sticky tiling', 'mode main'] # sticky is not yet supported https://github.com/nikitabobko/AeroSpace/issues/2 +f = [ + 'layout floating tiling', + 'mode main', +] # Toggle between floating and tiling layout +backspace = ['close-all-windows-but-current', 'mode main'] + +alt-shift-h = ['join-with left', 'mode main'] +alt-shift-j = ['join-with down', 'mode main'] +alt-shift-k = ['join-with up', 'mode main'] +alt-shift-l = ['join-with right', 'mode main'] diff --git a/karabiner/.config/karabiner/src/apps.ts b/karabiner/.config/karabiner/src/apps.ts new file mode 100644 index 0000000..1cf217e --- /dev/null +++ b/karabiner/.config/karabiner/src/apps.ts @@ -0,0 +1,100 @@ +import { ifApp, map, rule, toKey, withCondition } from 'karabiner.ts' +import { tapModifier, toResizeWindow } from './utils' + +export function appsMapping() { + let historyNavi = [ + // Back/Forward history in most apps + map('p', '⌃').to('[', '⌘'), + map('n', '⌃').to(']', '⌘'), + ] + let tabNavi = [ + // Pre/Next tab in most apps + map('p', '⌥').to('[', '⌘⇧'), + map('n', '⌥').to(']', '⌘⇧'), + ] + let switcher = [ + // Pre/Next switcher in most apps + map('p', '⌘⌥⌃').to('⇥', '⌃⇧'), + map('n', '⌘⌥⌃').to('⇥', '⌃'), + ] + + // Tap ‹⌘ -> Show/Hide UI (e.g. left sidebars) + // Tap ‹⌥ -> Run current task (re-run) + // Tap ‹⌃ -> Run list + // Tap ›⌘ -> Show/Hide UI (e.g. right sidebars) + // Tap ›⌥ -> Command Palette (e.g. ⌘K, ⌘P) + // Tap ›⌃ -> History (e.g. recent files) + + return [ + rule('app mappings').manipulators([ + //region Chrome + withCondition(ifApp('^com.google.Chrome$'))([ + ...historyNavi, + ...tabNavi, + ...switcher, + + tapModifier('‹⌥', toKey('r', '⌘')), // refreshThePage + + tapModifier('›⌘', toKey('i', '⌘⌥')), // developerTools + tapModifier('›⌥', toKey('a', '⌘⇧')), // searchTabs + + map(1, 'Meh').to(toResizeWindow('Google Chrome')), + ]), + //endregion + + //region Safari + withCondition(ifApp('^com.apple.Safari$'))([ + ...historyNavi, + ...tabNavi, + ...switcher, + + tapModifier('‹⌘', toKey('l', '⌘⇧')), // showHideSideBar + tapModifier('‹⌥', toKey('r', '⌘')), // reloadPage + + tapModifier('›⌘', toKey('i', '⌘⌥')), // showWebInspector + + map(1, 'Meh').to(toResizeWindow('Safari')), + ]), + //endregion + + //region Slack + withCondition(ifApp('^com.tinyspeck.slackmacgap$'))([ + ...historyNavi, + + tapModifier('‹⌘', toKey('d', '⌘⇧')), // showHideSideBar + tapModifier('‹⌥', toKey('f6')), // moveFocusToTheNextSection + + tapModifier('›⌘', toKey('.', '⌘')), // hideRightBar + tapModifier('›⌥', toKey('k', '⌘')), // open + + map(1, 'Meh').to( + // After the 1/4 width, leave some space for opening thread in a new window + // before the last 1/4 width + toResizeWindow('Slack', { x: 1263, y: 25 }, { w: 1760, h: 1415 }), + ), + ]), + //endregion + + //region Spark + withCondition(ifApp('^com.readdle.SparkDesktop'))([ + tapModifier('‹⌘', toKey('/')), // openSidebar + tapModifier('‹⌥', toKey('r', '⌘')), // fetch + + tapModifier('›⌘', toKey('/', '⌘')), // changeLayout + tapModifier('›⌥', toKey('k', '⌘')), // actions + + map(1, 'Meh').to(toResizeWindow('Spark Desktop')), + ]), + //endregion + + //region 1Password + withCondition(ifApp('^com.agilebits.onepassword7'))([ + map('j', ['left_control']).to(toKey('down_arrow')), + map('n', ['left_control']).to(toKey('down_arrow')), + map('k', ['left_control']).to(toKey('up_arrow')), + map('p', ['left_control']).to(toKey('up_arrow')), + ]), + //endregion + ]), + ] +} diff --git a/karabiner/.config/karabiner/src/homerow.ts b/karabiner/.config/karabiner/src/homerow.ts new file mode 100644 index 0000000..add7053 --- /dev/null +++ b/karabiner/.config/karabiner/src/homerow.ts @@ -0,0 +1,10 @@ +import { mapSimultaneous, rule } from 'karabiner.ts' + +export function homerow() { + return [ + rule('Homerow').manipulators([ + mapSimultaneous(['f', 'j']).to('␣', 'Hyper'), // Click + mapSimultaneous(['f', 'k']).to('⏎', 'Hyper'), // Scroll + ]), + ] +} diff --git a/karabiner/.config/karabiner/src/layers/emoji.ts b/karabiner/.config/karabiner/src/layers/emoji.ts new file mode 100644 index 0000000..65ba93e --- /dev/null +++ b/karabiner/.config/karabiner/src/layers/emoji.ts @@ -0,0 +1,63 @@ +import { duoLayer, map, toKey, toPaste, withMapper } from 'karabiner.ts' +import { toRaycastExt$ } from './raycast' + +export function emojiLayer() { + // See https://gitmoji.dev/ + let emojiMap = { + b: '🐛', // Fix a bug + c: '🔧', // add or update Configuration files + d: '📝', // add or update Documentation + f: '🚩', // add, update, or remove Feature Flags + h: '💯', // _hundred + j: '😂', // _joy + m: '🔀', // Merge branches + n: '✨', // introduce New features + p: '👍', // _plus_one +1 + r: '♻️', // Refactor code + s: '😅', // _sweat_smile + u: '⬆️', // Upgrade dependencies + v: '🔖', // release / Version tags + + t: '🛠️', // Tools + k: '📚', // Knowledge learnt + o: '💭', // Opinions and thoughts + i: '👨‍💻', // Experiences and stories + } + + let emojiHint = Object.entries(emojiMap) + .slice(0, 15) + .reduce( + (r, [k, v]) => [r[0].concat(v), r[1].concat(k.toUpperCase())], + [[] as string[], [] as string[]], + ) + .map((v, i) => v.join(i === 0 ? ' ' : ' ')) + .join('\n') + + return [ + duoLayer('z', 'x') + .notification(emojiHint) + .manipulators([ + map(';').to( + toRaycastExt$('raycast/emoji-symbols/search-emoji-symbols'), + ), + + withMapper(emojiMap)((k, v) => map(k).toPaste(v)), + + { 2: toPaste('⌫'), 3: toPaste('⌦'), 4: toPaste('⇥'), 5: toPaste('⎋') }, + { 6: toPaste('⌘'), 7: toPaste('⌥'), 8: toPaste('⌃'), 9: toPaste('⇧') }, + { 0: toPaste('⇪'), ',': toPaste('‹'), '.': toPaste('›') }, + + withMapper(['←', '→', '↑', '↓', '␣', '⏎', '⌫', '⌦'])((k) => + map(k).toPaste(k), + ), + + // Code snippets + map('l').toTypeSequence('console.log()←'), + map("'").toTypeSequence('⌫"'), + map('[').toTypeSequence('[␣]␣'), + map(']').toTypeSequence('-␣[␣]␣'), + + { "'": toKey('⌫'), '\\': toKey('⌦') }, + ]), + ] +} diff --git a/karabiner/.config/karabiner/src/layers/open.ts b/karabiner/.config/karabiner/src/layers/open.ts new file mode 100644 index 0000000..6a5c828 --- /dev/null +++ b/karabiner/.config/karabiner/src/layers/open.ts @@ -0,0 +1,22 @@ +import { duoLayer, toApp } from 'karabiner.ts' + +export function openLayer() { + return [ + duoLayer('l', ';') + .description('Apps Layer') + .leaderMode() + .notification('Apps:') + .manipulators({ + b: toApp('Google Chrome'), + c: toApp('Fantastical'), + // B: toApp('Safari'), + f: toApp('Finder'), + p: toApp('1Password'), + t: toApp('WezTerm'), + m: toApp('Spark Desktop'), + n: toApp('Obsidian'), + s: toApp('Slack'), + ',': toApp('System Settings'), + }), + ] +} diff --git a/karabiner/.config/karabiner/src/layers/raycast.ts b/karabiner/.config/karabiner/src/layers/raycast.ts new file mode 100644 index 0000000..cfcd107 --- /dev/null +++ b/karabiner/.config/karabiner/src/layers/raycast.ts @@ -0,0 +1,33 @@ +import { duoLayer, map, rule, to$ } from 'karabiner.ts' + +const layerMap = { + c: toRaycastExt$('raycast/calendar/my-schedule'), + h: toRaycastExt$('raycast/clipboard-history/clipboard-history'), + p: toRaycastExt$('raycast/raycast/confetti', true), + l: toRaycastExt$('raycast/raycast/search-quicklinks', true), + t: toRaycastExt$('doist/todoist/home'), + n: toRaycastExt$('KevinBatdorf/obsidian/createNoteCommand'), +} + +export function raycastLayer() { + return [ + duoLayer(';', 'r') + .description('Raycast Layer') + .leaderMode({ sticky: false, escape: [';', 'escape', 'spacebar'] }) + .manipulators(layerMap), + rule('command + shift + v → Raycast clipboard history').manipulators([ + map({ + key_code: 'v', + modifiers: { mandatory: ['command', 'shift'] }, + }).to(toRaycastExt$('raycast/clipboard-history/clipboard-history')), + ]), + ] +} + +export function toRaycastExt$(name: string, background = false) { + return to$(`open ${background ? '-g' : ''} raycast://extensions/${name}`) +} + +export function toRaycastWin$(name: string) { + return to$(`open -g raycast://extensions/raycast/window-management/${name}`) +} diff --git a/karabiner/.config/karabiner/src/layers/vim.ts b/karabiner/.config/karabiner/src/layers/vim.ts new file mode 100644 index 0000000..5b186f6 --- /dev/null +++ b/karabiner/.config/karabiner/src/layers/vim.ts @@ -0,0 +1,29 @@ +import { duoLayer, toKey, withModifier } from 'karabiner.ts' + +const hint = `\ +← ↓ ↑ → ⌫ ⌦ +H J K L ' \\` + +export function vimLayer() { + return [ + duoLayer('f', ';') + .notification(hint) + .manipulators([ + withModifier('??')({ + h: toKey('←'), + j: toKey('↓'), + k: toKey('↑'), + l: toKey('→'), + + ';': toKey('›⇧'), + d: toKey('‹⌘'), + s: toKey('‹⌃'), + a: toKey('‹⌥'), + }), + { + "'": toKey('⌫'), + '\\': toKey('⌦'), + }, + ]), + ] +} diff --git a/karabiner/.config/karabiner/src/layers/yabai.ts b/karabiner/.config/karabiner/src/layers/yabai.ts new file mode 100644 index 0000000..a692465 --- /dev/null +++ b/karabiner/.config/karabiner/src/layers/yabai.ts @@ -0,0 +1,78 @@ +import { duoLayer, rule, to$, withModifier } from 'karabiner.ts' + +export const toYabai$ = (command: string) => + to$(`/opt/homebrew/bin/yabai -m ${command}`) + +const keymap = [ + // Go to space + withModifier('left_command')({ + 1: toYabai$('window --space 1'), + 2: toYabai$('window --space 2'), + 3: toYabai$('window --space 3'), + 4: toYabai$('window --space 4'), + 5: toYabai$('window --space 5'), + 6: toYabai$('window --space 6'), + 7: toYabai$('window --space 7'), + 8: toYabai$('window --space 8'), + 9: toYabai$('window --space 9'), + 0: toYabai$('window --space 10'), + // 1: toYabai$('space --focus 1'), + // 2: toYabai$('space --focus 2'), + // 3: toYabai$('space --focus 3'), + // 4: toYabai$('space --focus 4'), + // 5: toYabai$('space --focus 5'), + // 6: toYabai$('space --focus 6'), + // 7: toYabai$('space --focus 7'), + // 8: toYabai$('space --focus 8'), + // 9: toYabai$('space --focus 9'), + // 0: toYabai$('space --focus 10'), + h: toYabai$('window --resize right:-100:0'), + j: toYabai$('window --resize bottom:0:100'), + k: toYabai$('window --resize top:0:-100'), + l: toYabai$('window --resize right:100:0'), + }), + // focus window + { f: toYabai$('window --toggle zoom-fullscreen') }, + // move window + // flip & rotate windows + { r: toYabai$('space --rotate 270') }, + { t: toYabai$('space --toggle float') }, + { x: toYabai$('space --mirror x-axis') }, + { y: toYabai$('space --mirror y-axis') }, + { z: toYabai$('window --toggle zoom-fullscreen') }, + { '=': toYabai$('space --balance') }, + { tab: toYabai$('space --focus recent') }, +] + +// const hints = Object.keys(keymap).join(' ') + +export function yabaiLayer() { + return [ + duoLayer('w', ';') + .description('Yabai Layer') + .leaderMode({ sticky: true, escape: [';', 'escape'] }) + .notification(`Yabai:`) + .manipulators(keymap), + rule('Yabai focus window').manipulators([ + withModifier('left_option')({ + h: toYabai$('window --focus west'), + j: toYabai$('window --focus south'), + k: toYabai$('window --focus north'), + l: toYabai$('window --focus east'), + f: toYabai$('window --toggle zoom-fullscreen'), + // choose layout + s: toYabai$('space --layout stack'), + t: toYabai$('space --layout bsp'), + b: toYabai$('space --balance'), + }), + ]), + rule('Yabai move window').manipulators([ + withModifier(['left_option', 'left_shift'])({ + h: toYabai$('window --swap west'), + j: toYabai$('window --swap south'), + k: toYabai$('window --swap north'), + l: toYabai$('window --swap east'), + }), + ]), + ] +} diff --git a/karabiner/.config/karabiner/src/utils.ts b/karabiner/.config/karabiner/src/utils.ts new file mode 100644 index 0000000..071a388 --- /dev/null +++ b/karabiner/.config/karabiner/src/utils.ts @@ -0,0 +1,83 @@ +import { + KeyAlias, + LetterKeyCode, + map, + mapSimultaneous, + ModifierKeyAlias, + modifierKeyAliases, + MultiModifierAlias, + multiModifierAliases, + SideModifierAlias, + to$, + ToEvent, + toRemoveNotificationMessage, +} from 'karabiner.ts' + +/** Map when tap a modifier key; keep as modifier when hold */ +export function tapModifier(v: SideModifierAlias | 'fn', to: ToEvent) { + return map(v).to(v).toIfAlone(to) +} + +export function duoModifier( + keys: `${LetterKeyCode | KeyAlias}${LetterKeyCode | KeyAlias}`, + modifier: '⌘' | '⌥' | '⌃' | '⇧' | MultiModifierAlias, +) { + let id = keys + modifier + let [firstMod, ...restMods] = ( + modifier in modifierKeyAliases + ? [modifierKeyAliases[modifier as ModifierKeyAlias]] + : multiModifierAliases[modifier as MultiModifierAlias] + ) as Array<'command' | 'control' | 'option' | 'shift'> + let to_after_key_up = [toRemoveNotificationMessage(id)] + return mapSimultaneous(keys.split('') as (LetterKeyCode | KeyAlias)[], { + to_after_key_up, + }) + .toNotificationMessage(id, modifier) // Must go first or to() doesn't work + .to(`left_${firstMod}`, restMods) +} + +export function toResizeWindow( + app: string, + position = { x: 0, y: 220 }, // First window, below widgets + size = { w: 1262, h: 1220 }, // First 1/4 width, screen height - widgets height +) { + return to$(`osascript -e '\ +set windowPosition to {${position.x}, ${position.y}} +set windowSize to {${size.w}, ${size.h}} + +tell application "System Events" + tell process "${app}" + set frontWindow to first window + set position of frontWindow to windowPosition + set size of frontWindow to windowSize + end tell +end tell'`) +} + +/** @see https://gist.github.com/lancethomps/a5ac103f334b171f70ce2ff983220b4f?permalink_comment_id=4698498#gistcomment-4698498 */ +export let toClearNotifications = to$(`osascript -e '\ +tell application "System Events" + try + repeat + set _groups to groups of UI element 1 of scroll area 1 of group 1 of window "Notification Center" of application process "NotificationCenter" + set numGroups to number of _groups + if numGroups = 0 then + exit repeat + end if + repeat with _group in _groups + set _actions to actions of _group + set actionPerformed to false + repeat with _action in _actions + if description of _action is in {"Clear All", "Close"} then + perform _action + set actionPerformed to true + exit repeat + end if + end repeat + if actionPerformed then + exit repeat + end if + end repeat + end repeat + end try +end tell'`)