Skip to content

Instantly share code, notes, and snippets.

@caiwan
Last active January 10, 2020 19:04
Show Gist options
  • Save caiwan/a713dc51b3baf366fd58e61111a5790e to your computer and use it in GitHub Desktop.
Save caiwan/a713dc51b3baf366fd58e61111a5790e to your computer and use it in GitHub Desktop.
Caiwan's LMC macro framework
-- ------------------------------------------------------------------------------------
-- * Caiwan's LMC macro framework
-- * Usage:
-- * Place this file to <LuaMacros>/lua/caiwan_macro_framework.lua
-- ------------------------------------------------------------------------------------
-- Helpers
function bit(p) return bit32.lshift(1, p) end
function hasbit(x, p) return bit32.band(x, p) ~=0 end
function setbit(x, p) return bit32.bor(x, p) end
function clearbit(x, p) return bit32.band(x, bit32.bnot(p)) end
-- Simulates JS-like Dynamic scoping
-- e.g. injectin object into function
function bind(t, k) return function(...) return t[k](t, ...) end end
-- Modifier keys
SHIFT_KEY = 16
CTRL_KEY = 17
ALT_KEY = 18
MODIFIER_KEY_BITS = {
[SHIFT_KEY] = bit(0),
[CTRL_KEY] = bit(1),
[ALT_KEY] = bit(2)
}
MODIFIER_OFFSET = bit(3)
-- ------------------------------------------------------------------------------------
MacroDispatcher = {}
MacroDispatcher.__index = MacroDispatcher
setmetatable(MacroDispatcher, {
__call = function (cls, ...)
local self = setmetatable({}, cls)
self:_init(...)
return self
end,
})
-- Ctor
function MacroDispatcher:_init(name, identifier)
setmetatable({}, self)
self.name = name
self.identifier = identifier or '0000AAA'
self._modifier_keys = 0
self._keypress_map = {}
self._bindings = {}
self.ignore_unbound_keys = false
return self
end
-- Binds a functiuon to be executed
function MacroDispatcher:bind_fn(fn, key, ...)
local args = table.pack(...)
local modifiers = 0
for i=1, args.n do
local key = args[i]
if (MODIFIER_KEY_BITS[key] ~= nil) then modifiers = setbit(modifiers, MODIFIER_KEY_BITS[key]) end
end
local id = MODIFIER_OFFSET * key + modifiers
self._bindings[id] = fn
end
-- assign logical name to macro keyboard
-- then sets up callback handlers
function MacroDispatcher:register_keyboard()
if self.identifier == '0000AAA' then
lmc_assign_keyboard(self.name);
else
lmc_device_set_name(self.name, self.identifier)
end
lmc_set_handler(self.name, bind(self, '_handler'))
end
-- Callback
function MacroDispatcher:_handler(button, direction)
-- 0 = up 1 = down
if (self._keypress_map[button] == 1 and direction == 1) then return end -- ignore repeat
self._keypress_map[button] = direction
-- print('beep')
if (MODIFIER_KEY_BITS[button] ~= nil) then
if direction == 1 then
self._modifier_keys = setbit(self._modifier_keys, MODIFIER_KEY_BITS[button])
else
self._modifier_keys = clearbit(self._modifier_keys, MODIFIER_KEY_BITS[button])
end
end
local id = MODIFIER_OFFSET * button + self._modifier_keys
if (self._bindings[id] ~= nil ) then
-- release all modifiers first
if direction == 1 then -- only invoke when pushing button down; there's no other support atm
if (not self.ignore_unbound_keys) then if self:_set_modifiers(0) then lmc_sleep(25) end end
-- Invoke callback
local fn = self._bindings[id]
fn(self, button, direction)
-- press all modifiers agaion
if (not self.ignore_unbound_keys) then
lmc_sleep(25)
self:_set_modifiers(1)
end
end
else
-- Pass through otherwise
if (not self.ignore_unbound_keys) then self:send_input(button, direction) end
end
print( self.name .. ' : ' .. button .. ', ' .. direction .. ' '
.. 'Modifiers: ' .. self._modifier_keys .. ' '
.. 'ID: ' .. id .. ' '
.. (self._bindings[id] ~= nil and '*fn' or '')
)
end
function MacroDispatcher:_set_modifiers(direction)
local is_set = false
for k, v in pairs(MODIFIER_KEY_BITS) do if hasbit(self._modifier_keys, v) then
is_set = true
self:send_input(k, direction)
end end
return is_set
end
function MacroDispatcher:send_input(button, direction)
-- -- 0 = up 1 = down
-- -- https://docs.microsoft.com/en-us/windows/desktop/api/winuser/ns-winuser-tagkeybdinput
local a = 0
if direction == 0 then a = 2 end
-- TODO: There is a logger inside LMC for this
print('Send input: ' .. button .. ', ' .. direction)
lmc_send_input(button, 0, a)
end
function MacroDispatcher:send_keys(stroke)
-- TODO: There is a logger inside LMC for this
print('Keys: ' .. stroke)
lmc_send_keys(stroke)
end
-- Bootstrap
-- TODO: Set up loggers, etc. here
function main (fn) fn() end
--
-- Photoshop shortcuts
--
clear()
require 'caiwan_macro_framework'
-- photoshop function keystroke definitions
ps = {
increase_brush_size = function(m) m:send_keys('[') end,
decrease_brush_size = function(m) m:send_keys(']') end,
increase_brush_hardness = function(m) m:send_keys('+[') end,
decrease_brush_hardness = function(m) m:send_keys('+]') end,
zoom_in = function(m) m:send_keys('^{NUMPLUS}') end,
zoom_out = function(m) m:send_keys('^{NUMMINUS}') end,
zoom_reset = function(m) m:send_keys('^0') end,
undo = function(m) m:send_keys('^z') end,
redo = function(m) m:send_keys('^+z') end,
history_step_back = function(m) m:send_keys('^%z') end,
is_brush_selected = false,
toggle_brush = function(m)
ps.is_brush_selected = not ps.is_brush_selected
if (ps.is_brush_selected) then m:send_keys('b')
else m:send_keys('e') end
end,
delete = function(m) m:send_keys('{DELETE}') end,
lasso_tool = function(m) m:send_keys('l') end,
deselect = function(m) m:send_keys('^d') end,
invert_selection = function(m) m:send_keys('^+i') end,
marquee_tool = function(m) m:send_keys('m') end,
move_tool = function(m) m:send_keys('v') end,
-- rotate = function() m:send_keys('r') end
is_rotation_selected = false,
rotate = function(m)
ps.is_rotation_selected = not ps.is_rotation_selected
if (ps.is_rotation_selected) then m:send_keys('r')
else if (ps.is_brush_selected) then m:send_keys('b')
else m:send_keys('e') end
end
end,
transformation_tool = function(m) m:send_keys('^t') end,
swap_colors = function(m) m:send_keys('x') end,
flip_canvas = function(m) m:send_keys('^+=') end,
switch_document_next = function(m) m:send_keys('^{TAB}') end,
switch_document_prev = function(m) m:send_keys('+^{TAB}') end,
new_layer = function(m) m:send_keys('^+n~') end,
merge_layer_down = function(m) m:send_keys('^e') end,
select_all = function(m) m:send_keys('^a') end,
cut = function(m) m:send_keys('^x') end, -- TODO Does not work when boudn to `xcv` and `s`
copy = function(m) m:send_keys('^c') end,
paste = function(m) m:send_keys('^v') end,
save = function(m) m:send_keys('^s') end,
save_as = function(m) m:send_keys('^+s') end,
open = function(m) m:send_keys('^o') end,
magic_wand = function(m) m:send_keys('w') end,
gradient_fill = function(m) m:send_keys('g') end,
hue_saturation = function(m) m:send_keys('^u') end,
desaturate = function(m) m:send_keys('+^u') end,
levels = function(m) m:send_keys('^l') end,
curves = function(m) m:send_keys('^m') end,
group = function(m) m:send_keys('^g') end,
toggle_clipping_mask = function(m) m:send_keys('^%g') end,
-- TODO: This gets ignored, switches to hand tool instead of zoom out-and-in
hand_tool = function(m, b, d) m:send_input(72, d) end, -- 72 = `H`
action_create_base = function(m) m:send_keys('^+{F3}') end,
action_extend_selection = function(m) m:send_keys('^+{F4}') end,
-- TODO This does not work bacause fw cannot unbind ctrl key and opens quickfind instead (`ctlr+f` instead of `f`)
toggle_fullscreen = function(m) m:send_keys('f') end
}
function key_passthrough(m, b, d) m:send_input(b, d) end
main(function()
clear()
print(_VERSION)
local macros = MacroDispatcher('MACROS')
macros.ignore_unbound_keys = false
-- esc
macros:bind_fn(key_passthrough, 27)
-- function key line
macros:bind_fn(ps.action_create_base, 112) -- G1
macros:bind_fn(ps.action_extend_selection, 113) -- G2
macros:bind_fn(ps.curves, 114) -- G3
macros:bind_fn(ps.levels, 115) -- G4
macros:bind_fn(ps.hue_saturation, 116) -- G5
macros:bind_fn(ps.desaturate, 116, CTRL_KEY) -- CTRL + G5
macros:bind_fn(ps.toggle_fullscreen, 116, CTRL_KEY, ALT_KEY) -- CTRL + ALT + G5
-- `~123456` line
macros:bind_fn(ps.save, 223) -- ~
macros:bind_fn(ps.save_as, 223, SHIFT_KEY) -- SHIFT + ~
macros:bind_fn(ps.open, 223, CTRL_KEY) -- CTRL + ~
macros:bind_fn(ps.new_layer, 49) -- 1
macros:bind_fn(ps.group, 49, CTRL_KEY) -- CTRL + 1
macros:bind_fn(ps.merge_down, 50) -- 2
macros:bind_fn(ps.toggle_clipping_mask, 51) -- 3
-- macros:bind_fn(52) -- 4
-- macros:bind_fn(53) -- 5
-- macros:bind_fn(54) -- 6
-- `qwert` line
macros:bind_fn(ps.lasso_tool, 81) -- Q
macros:bind_fn(ps.magic_wand, 81, CTRL_KEY) -- CTRL + Q
macros:bind_fn(ps.marquee_tool, 81, ALT_KEY) -- ALT + Q
macros:bind_fn(ps.increase_brush_size, 87) -- W
macros:bind_fn(ps.increase_brush_hardness, 87, CTRL_KEY) -- CTRL + W
macros:bind_fn(ps.decrease_brush_size, 69) -- E
macros:bind_fn(ps.decrease_brush_hardness, 69, CTRL_KEY) -- CTRL + E
macros:bind_fn(ps.toggle_brush, 82) -- R
macros:bind_fn(ps.undo, 84) -- T
macros:bind_fn(ps.redo, 84, SHIFT_KEY) -- SHIFT + T
macros:bind_fn(ps.delete, 84, CTRL_KEY) -- CTRL + T
-- `asdfg` line
-- TODO: `asd` are unuded (65, 83, 68)
-- macros:bind_fn(65] = ctrl_switch(on_up(select_all), on_down(new_layer)) -- A
-- macros:bind_fn(83] = ctrl_switch(on_up(save), on_down(merge_layer_down)) -- S
-- macros:bind_fn(ps.move_tool, 68) -- D
macros:bind_fn(ps.deselect, 70) -- F
macros:bind_fn(ps.invert_selection, 70, CTRL_KEY) -- CTRL + F
macros:bind_fn(ps.zoom_in, 71) -- G
-- `zxcvb` line
macros:bind_fn(ps.rotate, 90) -- Z
macros:bind_fn(ps.swap_colors, 88) -- X
-- macros:bind_fn(ps.cut, 88, CTRL_KEY) -- CTRL + X
macros:bind_fn(ps.move_tool, 67) -- C
-- macros:bind_fn(ps.copy 67, CTRL_KEY) -- CTRL + C
macros:bind_fn(ps.transformation_tool, 86) -- V
-- macros:bind_fn(ps.paste, 86, CTRL_KEY) -- CTRL + V
macros:bind_fn(ps.zoom_out, 66) -- B
-- tab
macros:bind_fn(ps.flip_canvas, 9)
macros:bind_fn(ps.switch_document_next, 9, CTRL_KEY)
macros:bind_fn(ps.switch_document_prev, 9, CTRL_KEY, SHIFT_KEY)
-- space
macros:bind_fn(key_passthrough, 32) -- TODO: supposed to be the hand tool
macros:bind_fn(ps.zoom_reset, 32, CTRL_KEY)
macros:register_keyboard()
end)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment