Skip to content

Commit

Permalink
Improves modal system
Browse files Browse the repository at this point in the history
* Beginning

* Improved modals - modal.lua

* Moved addState functions from `init` to their respective modules

* `self.modal` instances have been renamed to `self.hotkeyModal`

- to avoid confusion with `modal` module

* Emacs module - capture

* C-o/C-i for Preview App

* Readme and changelog update
  • Loading branch information
agzam committed Oct 15, 2017
1 parent ccd80da commit e64e4c0
Show file tree
Hide file tree
Showing 11 changed files with 285 additions and 174 deletions.
31 changes: 31 additions & 0 deletions apps.lua
@@ -0,0 +1,31 @@
local apps = {}
local multimedia = require "multimedia"
local windows = require "windows"
local slack = require "slack"

apps.addState = function(modal)
modal.addState("apps", {
init = function(self, fsm)
self.hotkeyModal = hs.hotkey.modal.new()
modal.displayModalText "e\t emacs\ng \t chrome\n i\t iTerm\n s\t slack\n b\t brave"
self.hotkeyModal:bind("","escape", function() fsm:toIdle() end)
self.hotkeyModal:bind({"cmd"}, "space", nil, function() fsm:toMain() end)
for key, app in pairs({
i = "iTerm2",
g = "Google Chrome",
b = "Brave",
e = "Emacs",
m = multimedia.musicApp})
do
self.hotkeyModal:bind("", key, function()
windows.activateApp(app)
fsm:toIdle()
end)
end

slack.bind(self.hotkeyModal, fsm)
self.hotkeyModal:enter()
end})
end

return apps
22 changes: 13 additions & 9 deletions changelog.org
@@ -1,9 +1,13 @@
[2017-06-25 Sun]
- Sierra compatibility
Since Karabiner is not compatible anymore (starting with Sierra), had to find ways to get similar features
- ~keybdings~ module
- App switcher with =Cmd+j/k=
- Simple tab switcher for Chrome and iTerm2 with =Cmd+h/l=
- Simple =Vi-mode= with =Alt+j/k/l/m=
- App specific keybindings
- Changed Slack reaction key to =C-r=, so =Cmd+i= can be used to switch between current application windows
- [2017-06-25 Sun]
- Sierra compatibility
/*Since Karabiner is not compatible anymore (starting with Sierra), had to find a way to get similar features*/
- ~keybdings~ module
- App switcher - =Cmd+j/k=
- Simple tab switcher for Chrome and iTerm2 - =Cmd+h/l=
- Simple =Vi-mode= - =Alt+j/k/l/m=
- App specific keybindings
- Changed Slack reaction key to =C-r=, so =Cmd+i= can be used to switch between current application windows
- [2017-10-14 Sat]
- Improved modal system - simplifies adding and extending modals
- Emacs module
currently invokes Emacs to enable system-wide org-capture. Accompanying emacs-lisp code can be found [[https://github.com/agzam/dot-spacemacs/blob/master/layers/ag-org/funcs.el#L144][here]]
54 changes: 54 additions & 0 deletions emacs.lua
@@ -0,0 +1,54 @@
local emacs = {}

local capture = function(isNote)
local key = ""
if isNote then
key = "\"c\"" -- key is a string associated with org-capture template
end
local currentApp = hs.window.focusedWindow();
local pid = "\"" .. currentApp:pid() .. "\" "
local title = "\"" .. currentApp:title() .. "\" "
hs.timer.delayed.new(0.1, function()
hs.execute("/usr/local/bin/emacsclient" ..
" -c -F '(quote (name . \"capture\"))'" ..
" -e '(activate-capture-frame " .. pid .. title .. key .. " )'")
end):start()
end

local bind = function(hotkeyModal, fsm)
hotkeyModal:bind("", "c", function()
fsm:toIdle()
capture()
end)
hotkeyModal:bind("", "n", function()
fsm:toIdle()
capture(true) -- note on currently clocked in
end)
hotkeyModal:bind("", "t", function()
hs.alert.show(hs.window.focusedWindow():pid(), 2)
end)
end

emacs.switchToApp = function(pid, title)
local app = hs.application.applicationForPID(pid)
if app then
app:activate()
end
end

emacs.addState = function(modal)
modal.addState("emacs", {
init = function(self, fsm)
self.hotkeyModal = hs.hotkey.modal.new()
modal.displayModalText "c \tcapture\nn\tnote"

self.hotkeyModal:bind("","escape", function() fsm:toIdle() end)
self.hotkeyModal:bind({"cmd"}, "space", nil, function() fsm:toMain() end)

bind(self.hotkeyModal, fsm)
self.hotkeyModal:enter()
end
})
end

return emacs
134 changes: 7 additions & 127 deletions init.lua
@@ -1,138 +1,18 @@
require "preload"
local keybindings = require "keybindings"
local machine = require "statemachine"
local windows = require "windows"
local slack = require "slack"
local multimedia = require "multimedia"

local modal = require "modal"
require "preview-app"

local displayModalText = function(txt)
hs.alert.closeAll()
alert(txt, 999999)
end

allowedApps = {"Emacs", "iTerm2"}
hs.hints.style = "vimperator"
hs.hints.showTitleThresh = 4
hs.hints.titleMaxSize = 10
hs.hints.fontSize = 30

local filterAllowedApps = function(w)
if (not w:isStandard()) and (not utils.contains(allowedApps, w:application():name())) then
return false;
end
return true;
end

modals = {
main = {
init = function(self, fsm)
if self.modal then
self.modal:enter()
else
self.modal = hs.hotkey.modal.new({"cmd"}, "space")
end
self.modal:bind("","space", nil, function() fsm:toIdle(); windows.activateApp("Alfred 3") end)
self.modal:bind("","w", nil, function() fsm:toWindows() end)
self.modal:bind("","a", nil, function() fsm:toApps() end)
self.modal:bind("", "m", nil, function() fsm:toMedia() end)
self.modal:bind("","j", nil, function()
local wns = hs.fnutils.filter(hs.window.allWindows(), filterAllowedApps)
hs.hints.windowHints(wns, nil, true)
fsm:toIdle() end)
self.modal:bind("","escape", function() fsm:toIdle() end)
function self.modal:entered() displayModalText "w \t- windows\na \t- apps\n j \t- jump\nm - media" end
end
},
windows = {
init = function(self, fsm)
self.modal = hs.hotkey.modal.new()
displayModalText "cmd + hjkl \t jumping\nhjkl \t\t\t\t halves\nalt + hjkl \t\t increments\nshift + hjkl \t resize\nn, p \t next, prev screen\ng \t\t\t\t\t grid\nm \t\t\t\t maximize\nu \t\t\t\t\t undo"
self.modal:bind("","escape", function() fsm:toIdle() end)
self.modal:bind({"cmd"}, "space", nil, function() fsm:toMain() end)
windows.bind(self.modal, fsm)
self.modal:enter()
end
},
apps = {
init = function(self, fsm)
self.modal = hs.hotkey.modal.new()
displayModalText "e\t emacs\ng \t chrome\n i\t iTerm\n s\t slack\n b\t brave"
self.modal:bind("","escape", function() fsm:toIdle() end)
self.modal:bind({"cmd"}, "space", nil, function() fsm:toMain() end)
for key, app in pairs({
i = "iTerm2",
g = "Google Chrome",
b = "Brave",
e = "Emacs",
m = multimedia.musicApp}) do
self.modal:bind("", key, function() windows.activateApp(app); fsm:toIdle() end)
end

slack.bind(self.modal, fsm)
self.modal:enter()
end
},
media = {
init = function(self, fsm)
self.modal = hs.hotkey.modal.new()
displayModalText "h \t previous track\nl \t next track\nk \t volume up\nj \t volume down\ns \t play/pause\na \t launch player"

self.modal:bind("","escape", function() fsm:toIdle() end)
self.modal:bind({"cmd"}, "space", nil, function() fsm:toMain() end)

multimedia.bind(self.modal, fsm)
self.modal:enter()
end
}
}

local initModal = function(state, fsm)
local m = modals[state]
m.init(m, fsm)
end

exitAllModals = function()
utils.each(modals, function(m)
if m.modal then
m.modal:exit()
end
end)
end

local fsm = machine.create({
initial = "idle",
events = {
{ name = "toIdle", from = "*", to = "idle" },
{ name = "toMain", from = '*', to = "main" },
{ name = "toWindows", from = {'main','idle'}, to = "windows" },
{ name = "toApps", from = {'main', 'idle'}, to = "apps" },
{ name = "toMedia", from = {'main', 'idle'}, to = "media" }
},
callbacks = {
onidle = function(self, event, from, to)
hs.alert.closeAll()
exitAllModals()
end,
onmain = function(self, event, from, to)
-- modals[from].modal:exit()
initModal(to, self)
end,
onwindows = function(self, event, from, to)
-- modals[from].modal:exit()
initModal(to, self)
end,
onapps = function(self, event, from, to)
-- modals[from].modal:exit()
initModal(to, self)
end,
onmedia = function(self, event, from, to)
initModal(to, self)
end,
}
})
require("windows").addState(modal)
require("apps").addState(modal)
require("multimedia").addState(modal)
require("emacs").addState(modal)

fsm:toMain()
local stateMachine = modal.createMachine()
stateMachine:toMain()

hs.alert.show("Config Loaded")
2 changes: 1 addition & 1 deletion keybindings.lua
Expand Up @@ -110,7 +110,7 @@ module.appSpecific = {
--- setting conflicting Cmd+L (jump to address bar) keybinding to Cmd+Shift+L
local cmdSL = hs.hotkey.new({'cmd', 'shift'}, 'l', function()
local app = hs.window.focusedWindow():application()
app:selectMenuItem({'File', 'Open Location…'})
app:selectMenuItem({"File", "Open Location…"})
end)
module.activateAppKey("Google Chrome", cmdSL)

Expand Down
93 changes: 93 additions & 0 deletions modal.lua
@@ -0,0 +1,93 @@
local modal = {}
local stateMachine = require "statemachine"
local utils = require "utils"
local windows = require "windows"

-- local log = hs.logger.new('modal-module','debug')

modal.displayModalText = function(txt)
hs.alert.closeAll()
alert(txt, 999999)
end

modal.exitAllModals = function()
hs.fnutils.each(modal.states, function(s)
if s.hotkeyModal then s.hotkeyModal:exit() end
end)
end

modal.addState = function(name,state)
modal.states[name] = state
end

local filterAllowedApps = function(w)
local allowedApps = {"Emacs", "iTerm2"}
if (not w:isStandard()) and (not hs.fnutils.contains(allowedApps, w:application():name())) then
return false;
end
return true;
end

modal.states = {
idle = {
from = "*", to = "idle",
callback = function(self, event, from, to)
hs.alert.closeAll()
modal.exitAllModals()
end
},
main = {
from = "*", to = "main",
init = function(self, fsm)
if self.hotkeyModal then
self.hotkeyModal:enter()
else
self.hotkeyModal = hs.hotkey.modal.new({"cmd"}, "space")
end
self.hotkeyModal:bind("","space", nil, function() fsm:toIdle(); windows.activateApp("Alfred 3") end)
self.hotkeyModal:bind("","w", nil, function() fsm:toWindows() end)
self.hotkeyModal:bind("","a", nil, function() fsm:toApps() end)
self.hotkeyModal:bind("", "m", nil, function() fsm:toMedia() end)
self.hotkeyModal:bind("", "x", nil, function() fsm:toEmacs() end)
self.hotkeyModal:bind("","j", nil, function()
local wns = hs.fnutils.filter(hs.window.allWindows(), filterAllowedApps)
hs.hints.windowHints(wns, nil, true)
fsm:toIdle() end)
self.hotkeyModal:bind("","escape", function() fsm:toIdle() end)
function self.hotkeyModal:entered()
modal.displayModalText "w \t- windows\na \t- apps\n j \t- jump\nm - media\nx\t- emacs"
end
end
}
}

-- -- each modal has: name, init function
modal.createMachine = function()
-- build events based on modals
local events = {}
local params = function(fsm)
local callbacks = {}
for k, s in pairs (modal.states) do
table.insert(events, { name = "to" .. utils.capitalize(k),
from = s.from or {"main", "idle"},
to = s.to or k})
if s.callback then
cFn = s.callback
else
cFn = function(self, event, from, to)
local st = modal.states[to]
st.init(st, self)
end
end
callbacks["on" .. k] = cFn
end

return callbacks
end

return stateMachine.create({ initial = "idle",
events = events,
callbacks = params(self)})
end

return modal
28 changes: 21 additions & 7 deletions multimedia.lua
Expand Up @@ -10,19 +10,33 @@ multimedia.mKey = function (key)
end
end

multimedia.bind = function(modal, fsm)
modal:bind("", "a", function()
local bind = function(hotkeyModal, fsm)
hotkeyModal:bind("", "a", function()
hs.application.launchOrFocus(multimedia.musicApp)
fsm:toIdle()
end)
modal:bind("", "h", function() multimedia.mKey("previous")(); fsm:toIdle() end)
modal:bind("", "l", function() multimedia.mKey("next")(); fsm:toIdle() end)
hotkeyModal:bind("", "h", function() multimedia.mKey("previous")(); fsm:toIdle() end)
hotkeyModal:bind("", "l", function() multimedia.mKey("next")(); fsm:toIdle() end)
local sUp = multimedia.mKey("sound_up")
modal:bind("", "k", sUp, nil, sUp)
hotkeyModal:bind("", "k", sUp, nil, sUp)
local sDn = multimedia.mKey("sound_down")
modal:bind("", "j", sDn, nil, sDn)
hotkeyModal:bind("", "j", sDn, nil, sDn)
local pl = function() multimedia.mKey("play")(); fsm:toIdle() end
modal:bind("", "s", pl)
hotkeyModal:bind("", "s", pl)
end

multimedia.addState = function(modal)
modal.addState("media", {
init = function(self, fsm)
self.hotkeyModal = hs.hotkey.modal.new()
modal.displayModalText "h \t previous track\nl \t next track\nk \t volume up\nj \t volume down\ns \t play/pause\na \t launch player"

self.hotkeyModal:bind("","escape", function() fsm:toIdle() end)
self.hotkeyModal:bind({"cmd"}, "space", nil, function() fsm:toMain() end)

bind(self.hotkeyModal, fsm)
self.hotkeyModal:enter()
end})
end

return multimedia

0 comments on commit e64e4c0

Please sign in to comment.