├── modules ├── hotkey.lua ├── reload.lua ├── system.lua ├── launcher.lua └── windows.lua ├── init.lua ├── .gitignore ├── LICENSE ├── README_zh-CN.md └── README.md /modules/hotkey.lua: -------------------------------------------------------------------------------- 1 | hyper = {"cmd", "ctrl", "alt"} 2 | hyperShift = {"alt", "shift"} 3 | hyperCtrl = {"alt", "ctrl"} 4 | hyperAlt = {"ctrl", "alt", "shift"} -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | require "modules/reload" 2 | require "modules/hotkey" 3 | require "modules/system" 4 | require "modules/windows" 5 | require "modules/launcher" 6 | require "modules/wifi" 7 | require "modules/timer" 8 | -------------------------------------------------------------------------------- /modules/reload.lua: -------------------------------------------------------------------------------- 1 | local pathwatcher = require "hs.pathwatcher" 2 | local alert = require "hs.alert" 3 | 4 | -- http://www.hammerspoon.org/go/#fancyreload 5 | function reloadConfig(files) 6 | doReload = false 7 | for _, file in pairs(files) do 8 | if file:sub(-4) == ".lua" then 9 | doReload = true 10 | end 11 | end 12 | if doReload then 13 | hs.reload() 14 | end 15 | end 16 | 17 | pathwatcher.new(os.getenv("HOME") .. "/.hammerspoon", reloadConfig):start() 18 | alert.show("Hammerspoon Config Reloaded") 19 | -------------------------------------------------------------------------------- /modules/system.lua: -------------------------------------------------------------------------------- 1 | local hotkey = require "hs.hotkey" 2 | local caffeinate = require "hs.caffeinate" 3 | local audiodevice = require "hs.audiodevice" 4 | 5 | hotkey.bind(hyper, "L", function() 6 | caffeinate.lockScreen() 7 | -- caffeinate.startScreensaver() 8 | end) 9 | 10 | -- mute on sleep 11 | function muteOnWake(eventType) 12 | if (eventType == caffeinate.watcher.systemDidWake) then 13 | local output = audiodevice.defaultOutputDevice() 14 | output:setMuted(true) 15 | end 16 | end 17 | caffeinateWatcher = caffeinate.watcher.new(muteOnWake) 18 | caffeinateWatcher:start() -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/macos 3 | 4 | ### macOS ### 5 | *.DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | 9 | # Icon must end with two \r 10 | Icon 11 | 12 | # Thumbnails 13 | ._* 14 | 15 | # Files that might appear in the root of a volume 16 | .DocumentRevisions-V100 17 | .fseventsd 18 | .Spotlight-V100 19 | .TemporaryItems 20 | .Trashes 21 | .VolumeIcon.icns 22 | .com.apple.timemachine.donotpresent 23 | 24 | # Directories potentially created on remote AFP share 25 | .AppleDB 26 | .AppleDesktop 27 | Network Trash Folder 28 | Temporary Items 29 | .apdisk 30 | 31 | # End of https://www.gitignore.io/api/macos 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 greyby 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /modules/launcher.lua: -------------------------------------------------------------------------------- 1 | local hotkey = require "hs.hotkey" 2 | local grid = require "hs.grid" 3 | local window = require "hs.window" 4 | local application = require "hs.application" 5 | local appfinder = require "hs.appfinder" 6 | local fnutils = require "hs.fnutils" 7 | 8 | grid.setMargins({0, 0}) 9 | 10 | applist = { 11 | {shortcut = 'I',appname = 'IntelliJ IDEA CE'}, 12 | {shortcut = 'C',appname = 'Google Chrome'}, 13 | {shortcut = 'K',appname = 'iTerm'}, 14 | {shortcut = 'D',appname = 'Finder'}, 15 | {shortcut = 'Y',appname = 'Activity Monitor'}, 16 | {shortcut = 'P',appname = 'System Preferences'}, 17 | } 18 | 19 | fnutils.each(applist, function(entry) 20 | hotkey.bind({'ctrl', 'alt', 'cmd'}, entry.shortcut, entry.appname, function() 21 | application.launchOrFocus(entry.appname) 22 | -- toggle_application(applist[i].appname) 23 | end) 24 | end) 25 | 26 | -- Toggle an application between being the frontmost app, and being hidden 27 | function toggle_application(_app) 28 | local app = appfinder.appFromName(_app) 29 | if not app then 30 | application.launchOrFocus(_app) 31 | return 32 | end 33 | local mainwin = app:mainWindow() 34 | if mainwin then 35 | if mainwin == window.focusedWindow() then 36 | mainwin:application():hide() 37 | else 38 | mainwin:application():activate(true) 39 | mainwin:application():unhide() 40 | mainwin:focus() 41 | end 42 | end 43 | end -------------------------------------------------------------------------------- /README_zh-CN.md: -------------------------------------------------------------------------------- 1 | # Hammerspoon 配置 2 | 3 | ## 使用方法 4 | 5 | 1. 安装 [Hammerspoon](http://www.hammerspoon.org/) 6 | 2. `git clone https://github.com/greyby/hammerspoon.git ~/.hammerspoon` 7 | 8 | ## 快捷键图标 9 | | | 键位 | 10 | | --------- | -------------- | 11 | | | Shift | 12 | | | Control | 13 | | | Option | 14 | | | Command | 15 | 16 | ## 功能 17 | 18 | ### 窗口管理 19 | 20 | #### 1/2 屏幕 21 | 22 | * + 将当前窗口移动到左半屏 23 | * + 将当前窗口移动到右半屏 24 | * + 将当前窗口移动到上半屏 25 | * + 将当前窗口移动到下半屏 26 | 27 | #### 1/4 屏幕 28 | 29 | * + 将当前窗口移动到左上 1/4 屏 30 | * + 将当前窗口移动到右下 1/4 屏 31 | * + 将当前窗口移动到右上 1/4 屏 32 | * + 将当前窗口移动到左下 1/4 屏 33 | 34 | #### 多个显示器 35 | 36 | ##### 移动光标 37 | 38 | * + 把光标移动到下一个显示器 39 | * + 把光标移动到上一个显示器 40 | 41 | ##### 移动窗口 42 | 43 | * + 将当前活动窗口移动到上一个显示器 44 | * + 将当前活动窗口移动到下一个显示器 45 | * + 1 将当前活动窗口移动到第一个显示器并窗口最大化 46 | * + 2 将当前活动窗口移动到第二个显示器并窗口最大化 47 | 48 | 49 | #### 其它 50 | 51 | * + F 全屏 52 | * + M 最大化窗口 53 | * + C 将窗口放到中间 54 | * + H 切换活动窗口 55 | * + / 显示窗口切换的快捷键 56 | 57 | ### 系统工具 58 | 59 | * + L 锁屏 60 | 61 | ### 快速启动 62 | 63 | * + I `IntelliJ IDEA CE` 64 | * + C `Google Chrome` 65 | * + K `iTerm` 66 | * + D `Finder` 67 | * + Y `Activity Monitor` 68 | * + P `System Preferences` 69 | * + V `Visual Studio Code` 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hammerspoon Configuration 2 | 3 | ## Usage 4 | 5 | 1. Install [Hammerspoon](http://www.hammerspoon.org/) 6 | 2. `git clone https://github.com/greyby/hammerspoon.git ~/.hammerspoon` 7 | 8 | ## Modifier keys 9 | | | Key | 10 | | --------- | -------------- | 11 | | | Shift | 12 | | | Control | 13 | | | Option | 14 | | | Command | 15 | 16 | ## Features 17 | 18 | ### Window Management 19 | 20 | #### Split Screen Actions 21 | 22 | * + Left half 23 | * + Right half 24 | * + Top half 25 | * + Bottom half 26 | 27 | #### Quarter Screen Actions 28 | 29 | * + Left top quarter 30 | * + Right bottom quarter 31 | * + Right top quarter 32 | * + Left bottom quarter 33 | 34 | #### Multiple Monitor 35 | 36 | ##### Move Cursor 37 | 38 | * + Move cursor to next monitor 39 | * + Move cursor to previous monitor 40 | 41 | ##### Move Windows 42 | 43 | * + Move active window to previous monitor 44 | * + Move active window to next monitor 45 | * + 1 Move active window to monitor 1 and maximize the window 46 | * + 2 Move active window to monitor 2 and maximize the window 47 | 48 | 49 | #### Other 50 | 51 | * + F Full Screen 52 | * + M Maximize Window 53 | * + C Window Center 54 | 55 | 56 | * + H Switch active window 57 | * + / Display a keyboard hint for switching focus to each window 58 | 59 | ### System Tools 60 | 61 | * + L Lock Screen 62 | 63 | ### Launch Application 64 | 65 | * + I `IntelliJ IDEA CE` 66 | * + C `Google Chrome` 67 | * + K `iTerm` 68 | * + D `Finder` 69 | * + Y `Activity Monitor` 70 | * + P `System Preferences` -------------------------------------------------------------------------------- /modules/windows.lua: -------------------------------------------------------------------------------- 1 | -- window management 2 | local application = require "hs.application" 3 | local hotkey = require "hs.hotkey" 4 | local window = require "hs.window" 5 | local layout = require "hs.layout" 6 | local grid = require "hs.grid" 7 | local hints = require "hs.hints" 8 | local screen = require "hs.screen" 9 | local alert = require "hs.alert" 10 | local fnutils = require "hs.fnutils" 11 | local geometry = require "hs.geometry" 12 | local mouse = require "hs.mouse" 13 | 14 | -- default 0.2 15 | window.animationDuration = 0 16 | 17 | -- left half 18 | hotkey.bind(hyper, "Left", function() 19 | if window.focusedWindow() then 20 | window.focusedWindow():moveToUnit(layout.left50) 21 | else 22 | alert.show("No active window") 23 | end 24 | end) 25 | 26 | -- right half 27 | hotkey.bind(hyper, "Right", function() 28 | window.focusedWindow():moveToUnit(layout.right50) 29 | end) 30 | 31 | -- top half 32 | hotkey.bind(hyper, "Up", function() 33 | window.focusedWindow():moveToUnit'[0,0,100,50]' 34 | end) 35 | 36 | -- bottom half 37 | hotkey.bind(hyper, "Down", function() 38 | window.focusedWindow():moveToUnit'[0,50,100,100]' 39 | end) 40 | 41 | -- left top quarter 42 | hotkey.bind(hyperAlt, "Left", function() 43 | window.focusedWindow():moveToUnit'[0,0,50,50]' 44 | end) 45 | 46 | -- right bottom quarter 47 | hotkey.bind(hyperAlt, "Right", function() 48 | window.focusedWindow():moveToUnit'[50,50,100,100]' 49 | end) 50 | 51 | -- right top quarter 52 | hotkey.bind(hyperAlt, "Up", function() 53 | window.focusedWindow():moveToUnit'[50,0,100,50]' 54 | end) 55 | 56 | -- left bottom quarter 57 | hotkey.bind(hyperAlt, "Down", function() 58 | window.focusedWindow():moveToUnit'[0,50,50,100]' 59 | end) 60 | 61 | -- full screen 62 | hotkey.bind(hyper, 'F', function() 63 | window.focusedWindow():toggleFullScreen() 64 | end) 65 | 66 | -- center window 67 | hotkey.bind(hyper, 'C', function() 68 | window.focusedWindow():centerOnScreen() 69 | end) 70 | 71 | -- maximize window 72 | hotkey.bind(hyper, 'M', function() toggle_maximize() end) 73 | 74 | -- defines for window maximize toggler 75 | local frameCache = {} 76 | -- toggle a window between its normal size, and being maximized 77 | function toggle_maximize() 78 | local win = window.focusedWindow() 79 | if frameCache[win:id()] then 80 | win:setFrame(frameCache[win:id()]) 81 | frameCache[win:id()] = nil 82 | else 83 | frameCache[win:id()] = win:frame() 84 | win:maximize() 85 | end 86 | end 87 | 88 | -- display a keyboard hint for switching focus to each window 89 | hotkey.bind(hyperShift, '/', function() 90 | hints.windowHints() 91 | -- Display current application window 92 | -- hints.windowHints(hs.window.focusedWindow():application():allWindows()) 93 | end) 94 | 95 | -- switch active window 96 | hotkey.bind(hyperShift, "H", function() 97 | window.switcher.nextWindow() 98 | end) 99 | 100 | -- move active window to previous monitor 101 | hotkey.bind(hyperShift, "Left", function() 102 | window.focusedWindow():moveOneScreenWest() 103 | end) 104 | 105 | -- move active window to next monitor 106 | hotkey.bind(hyperShift, "Right", function() 107 | window.focusedWindow():moveOneScreenEast() 108 | end) 109 | 110 | -- move cursor to previous monitor 111 | hotkey.bind(hyperCtrl, "Left", function () 112 | focusScreen(window.focusedWindow():screen():previous()) 113 | end) 114 | 115 | -- move cursor to next monitor 116 | hotkey.bind(hyperCtrl, "Right", function () 117 | focusScreen(window.focusedWindow():screen():next()) 118 | end) 119 | 120 | 121 | --Predicate that checks if a window belongs to a screen 122 | function isInScreen(screen, win) 123 | return win:screen() == screen 124 | end 125 | 126 | function focusScreen(screen) 127 | --Get windows within screen, ordered from front to back. 128 | --If no windows exist, bring focus to desktop. Otherwise, set focus on 129 | --front-most application window. 130 | local windows = fnutils.filter( 131 | window.orderedWindows(), 132 | fnutils.partial(isInScreen, screen)) 133 | local windowToFocus = #windows > 0 and windows[1] or window.desktop() 134 | windowToFocus:focus() 135 | 136 | -- move cursor to center of screen 137 | local pt = geometry.rectMidPoint(screen:fullFrame()) 138 | mouse.setAbsolutePosition(pt) 139 | end 140 | 141 | -- maximized active window and move to selected monitor 142 | moveto = function(win, n) 143 | local screens = screen.allScreens() 144 | if n > #screens then 145 | alert.show("Only " .. #screens .. " monitors ") 146 | else 147 | local toWin = screen.allScreens()[n]:name() 148 | alert.show("Move " .. win:application():name() .. " to " .. toWin) 149 | 150 | layout.apply({{nil, win:title(), toWin, layout.maximized, nil, nil}}) 151 | 152 | end 153 | end 154 | 155 | -- move cursor to monitor 1 and maximize the window 156 | hotkey.bind(hyperShift, "1", function() 157 | local win = window.focusedWindow() 158 | moveto(win, 1) 159 | end) 160 | 161 | hotkey.bind(hyperShift, "2", function() 162 | local win = window.focusedWindow() 163 | moveto(win, 2) 164 | end) 165 | 166 | hotkey.bind(hyperShift, "3", function() 167 | local win = window.focusedWindow() 168 | moveto(win, 3) 169 | end) --------------------------------------------------------------------------------