GithubHelp home page GithubHelp logo

knu / hs-knu Goto Github PK

View Code? Open in Web Editor NEW
51.0 4.0 2.0 112 KB

A Spoon package of my Hammerspoon modules, mainly for keyboard customization

License: BSD 2-Clause "Simplified" License

Lua 98.86% Makefile 1.14%
hammerspoon keyboard lua macos

hs-knu's Introduction

Knu.spoon for Hammerspoon

This is a Hammerspoon Spoon that contains useful modules written by Akinori Musha a.k.a. @knu.

Requirements

  • Hammerspoon 0.9.53 or later

Usage

Install

Put the following snippet in your ~/.hammerspoon/init.lua:

if hs.fs.attributes("Spoons/Knu.spoon") == nil then
  hs.execute("mkdir -p Spoons; curl -L https://github.com/knu/Knu.Spoon/raw/release/Spoons/Knu.spoon.zip | tar xf - -C Spoons/")
end

Alternatively, you can use SpoonInstall like so:

if hs.fs.attributes("Spoons/SpoonInstall.spoon") == nil then
  hs.execute("mkdir -p Spoons; curl -L https://github.com/Hammerspoon/Spoons/raw/master/Spoons/SpoonInstall.spoon.zip | tar xf - -C Spoons/")
end

hs.loadSpoon("SpoonInstall")

spoon.SpoonInstall.repos.Knu = {
  url = "https://github.com/knu/Knu.spoon",
  desc = "Knu.spoon repository",
  branch = "release",
}
spoon.SpoonInstall.use_syncinstall = true
spoon.SpoonInstall:andUse("Knu", { repo = "Knu" })

After that, you can load Knu.spoon.

knu = hs.loadSpoon("Knu")

-- Enable auto-restart when any of the *.lua files under ~/.hammerspoon/ is modified
knu.runtime.autorestart(true)

Example: pseudo hotkeys

knu.photkey.bind({"fn"}, "l", function ()
    -- Wait a second to skip an inevitable key-up event
    hs.timer.doAfter(1.0, function () hs.application.launchOrFocus("ScreenSaverEngine") end)
end)
knu.photkey.bind({"leftshift", "rightshift"}, "l", function ()
    -- This is invoked with both shift keys down + L
    hs.eventtap.keyStrokes("LGTM!\n")
end)
knu.photkey.bind({"rightcmd", "shift"}, "q", function ()
    -- This is invoked with right command + shift (left or right) + Q
    hs.execute(("kill %d"):format(hs.application.frontmostApplication():pid()))
end)

Example: emoji input and key chord

function inputEmoji()
  local window = hs.window.focusedWindow()
  knu.emoji.chooser(function (chars)
      window:focus()
      if chars then
        local appId = hs.application.frontmostApplication():bundleID()
        if appId == "com.twitter.TweetDeck" then
          -- TweetDeck does not restore focus on text field, so just copy and notify user
          hs.pasteboard.setContents(chars)
          hs.alert.show("Copied! " .. chars)
        else
          -- knu.keyboard.send uses an appropriate method for the frontmost application to send a text
          -- knu.keyboard.paste pastes a string to the frontmost application, which is specified as a fallback function here
          knu.keyboard.send(chars, knu.keyboard.paste)
        end
      end
  end):show()
end

-- Speed up the first invocation
knu.emoji.preload()

-- Function to guard a given object from GC
guard = knu.runtime.guard

--- z+x+c opens the emoji chooser to input an emoji to the frontmost window
guard(knu.chord.bind({}, {"z", "x", "c"}, inputEmoji))

Example: application specific keymap

function withRepeat(fn)
  return fn, nil, fn
end

-- Define some bindings for specific applications
local keymapForQt = knu.keymap.new(
    hs.hotkey.new({"ctrl"}, "h", withRepeat(function ()
          hs.eventtap.keyStroke({}, "delete", 0)
    end)),
    hs.hotkey.new({"ctrl"}, "k", withRepeat(function ()
          hs.eventtap.keyStroke({"ctrl", "shift"}, "e", 0)
          hs.eventtap.keyStroke({"cmd"}, "x", 0)
    end)),
    hs.hotkey.new({"ctrl"}, "n", withRepeat(function ()
          hs.eventtap.keyStroke({}, "down", 0)
    end)),
    hs.hotkey.new({"ctrl"}, "p", withRepeat(function ()
          hs.eventtap.keyStroke({}, "up", 0)
    end))
)
knu.keymap.register("org.keepassx.keepassxc", keymapForQt)
knu.keymap.register("jp.naver.line.mac", keymapForQt)

Example: Keyboard layout watcher and helper functions

-- F18 toggles IM between Japanese <-> Roman
do
  local eisu = hs.hotkey.new({}, "f18", function ()
      hs.eventtap.keyStroke({}, "eisu", 0)
  end)
  local kana = hs.hotkey.new({}, "f18", function ()
      hs.eventtap.keyStroke({}, "kana", 0)
  end)

  knu.keyboard.onChange(function ()
      knu.keyboard.showCurrentInputMode()
      if knu.keyboard.isJapaneseMode() then
        kana:disable()
        eisu:enable()
      else
        kana:enable()
        eisu:disable()
      end
  end)
end

Example: Shell escape

-- knu.utils.shelljoin() is a method to build a command line from a list of arguments
-- with shell meta characters properly escaped

local extra_options = { "--exclude", ".*" }

hs.execute(knu.utils.shelljoin(
    "rsync",
    "-a",
    extra_options, -- no need for table.unpack()
    src,
    dest
))

Example: USB watcher

-- Switch the Karabiner-Elements profile when an external keyboard is attached or detached
knu.usb.onChange(function (device)
    local name = device.productName
    if name and (
        name:find("PS2") or -- PS/2-USB converter
          (not device.vendorName:find("^Apple") and name:find("Keyboard"))
      ) then
      if device.eventType == "added" then
        knu.keyboard.switchKarabinerProfile("External")
      else
        knu.keyboard.switchKarabinerProfile("Default")
      end
    end
end)

Example: Enhanced application watcher

-- Enable hotkey for launching an app only while not running
function launchByHotkeyWhileNotRunning(mods, key, bundleID)
  local hotkey = hs.hotkey.new(mods, key, function ()
      hs.application.open(bundleID)
  end)

  -- Writing an application watcher for a specific application made
  -- easy by knu.application.onChange()
  knu.application.onChange(bundleID, function (name, type, app)
      if type == hs.application.watcher.launched then
        hotkey:disable()
      elseif type == hs.application.watcher.terminated then
        hotkey:enable()
      end
  end, true)
end

-- Suppose you have set up system-wide hotkeys for activating apps in
-- their preferences, but also want to launch them if they are not
-- running.
launchByHotkeyWhileNotRunning({"alt", "ctrl"}, "/", "com.kapeli.dashdoc")
launchByHotkeyWhileNotRunning({"alt", "ctrl"}, "t", "com.googlecode.iterm2")

Example: Enhanced application launcher function

-- Refresh calendars every 15 minutes
hs.timer.doEvery(15 * 60, function ()
    knu.application.launchInBackground("com.apple.iCal", function (app)
        if app ~= nil then
          app:selectMenuItem({"View", "Refresh Calendars"})
          -- Depending on your language settings...
          -- app:selectMenuItem({"表示", "カレンダーを更新"})
        end
    end, 10, true)
end)

Example: Enable the hold-to-scroll mode with the middle button

-- Scroll by moving the mouse while holding the middle button
knu.mouse.holdToScrollButton():enable()

Example: URL unshortener

url, error = knu.http.unshortenUrl(originalURL)
if error ~= nil then
  logger.e("Error in unshortening a URL " .. originalURL .. ": " .. error)
end

-- Use url

Modules

  • application.lua: application change watcher and advanced application launcher functions

  • chord.lua: key chord implementation (SimultaneousKeyPress in Karabiner)

  • emoji.lua: emoji database and chooser

  • http.lua: functions to manipulate URLs

  • keyboard.lua: functions to handle input source switching

  • keymap.lua: application/window based keymap switching

  • mouse.lua: functions to handle mouse events

  • photkey.lua: pseudo hotkeys with extended modifiers support

  • runtime.lua: functions like restart() and guard() (from GC)

  • usb.lua: wrapper for hs.usb.watcher

  • utils.lua: common utility functions

License

Copyright (c) 2017-2023 Akinori MUSHA

Licensed under the 2-clause BSD license. See LICENSE for details.

Visit GitHub Repository for the latest information.

hs-knu's People

Contributors

knu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

Forkers

jwhitley zhangaz1

hs-knu's Issues

Keymap mapping to other keymap

I have a keymap below, when pressing cmd + p it triggers the cmd + t hotkey. Is there any way around this?

knu.keymap.register(
  "company.thebrowser.Browser",
  knu.keymap.new(
    -- Quick Open
    hs.hotkey.bind({ "cmd" }, "p", function ()
      hs.eventtap.keyStroke({ "cmd" }, "t", 1000)
    end),
    -- Forward
    hs.hotkey.new({ "cmd" }, "t", function ()
      hs.eventtap.keyStroke({ "cmd" }, "]", 1000)
    end)
  )
)

Cannot detect difference between one modifier and both left and right modifiers

With the following code, the only output will be leftshift+rightshift-a, and never rightshift. Could you fix this?

knu.photkey.bind({"rightshift"}, "a", function ()
    hs.eventtap.keyStrokes("rightshift-a\n")
end)
knu.photkey.bind({"leftshift", "rightshift"}, "a", function ()
    hs.eventtap.keyStrokes("leftshift+rightshift-a\n")
end)

Overlapping set of keychords

I try to adapt Key chords to apply commonly used styles to Inkscape objects on macOS.

The list of key chords are shown in this graphic

default-styles-names2

The author has published his key chord setup on github https://github.com/gillescastel/inkscape-shortcut-manager for linux.

I have already managed to adapt the creation of svg snippets for pasting. The remaining issue is that defining a large overlapping set of key chords using https://github.com/knu/hs-knu/blob/master/chord.lua does not work as expected. Only a subset works.

knu = require("knu")

-- Function to guard a given object from GC
guard = knu.runtime.guard

-- Keychords (48x)
keychords = {}
-- work
keychords[1]  = {"f", "space"}
keychords[2]  = {"w", "space"}
keychords[3]  = {"b", "space"}
keychords[4]  = {"s", "space"}
keychords[5]  = {"d", "space"}
keychords[6]  = {"e", "space"}
-- do not work
keychords[7]  = {"s", "f"}
keychords[8]  = {"d", "f"}
keychords[9]  = {"e", "f"}
keychords[10] = {"s", "w"}
keychords[11] = {"d", "w"}
keychords[12] = {"e", "w"}
keychords[13] = {"s", "g"}
keychords[14] = {"d", "g"}
keychords[15] = {"e", "g"}
keychords[16] = {"s", "f", "g"}
keychords[17] = {"d", "f", "g"}
keychords[18] = {"e", "f", "g"}
keychords[19] = {"s", "w", "g"}
keychords[20] = {"d", "w", "g"}
keychords[21] = {"e", "w", "g"}
keychords[22] = {"s", "v"}
keychords[23] = {"d", "v"}
keychords[24] = {"e", "v"}
keychords[25] = {"s", "f", "v"}
keychords[26] = {"d", "f", "v"}
keychords[27] = {"e", "f", "v"}
keychords[28] = {"s", "w", "v"}
keychords[29] = {"d", "w", "v"}
keychords[30] = {"e", "w", "v"}
keychords[31] = {"s", "a"}
keychords[32] = {"d", "a"}
keychords[33] = {"e", "a"}
keychords[34] = {"s", "a", "v"}
keychords[35] = {"d", "a", "v"}
keychords[36] = {"e", "a", "v"}
keychords[37] = {"s", "a", "g"}
keychords[38] = {"d", "a", "g"}
keychords[39] = {"e", "a", "g"}
keychords[40] = {"s", "x"}
keychords[41] = {"d", "x"}
keychords[42] = {"e", "x"}
keychords[43] = {"s", "x", "v"}
keychords[44] = {"d", "x", "v"}
keychords[45] = {"e", "x", "v"}
keychords[46] = {"s", "x", "g"}
keychords[47] = {"d", "x", "g"}
keychords[48] = {"e", "x", "g"}

knuchords = {}
for k, v in pairs(keychords) do
    knuchords[k] = knu.chord.new({}, v, function() print(hs.inspect.inspect(v)); end, 0.5)
end

local InkscapeWF = hs.window.filter.new("Inkscape")

InkscapeWF
    :subscribe(hs.window.filter.windowFocused, function()
        print("starting keychords")
        for k,v in pairs(knuchords) do
            guard(v:start())
        end
    end)
    :subscribe(hs.window.filter.windowUnfocused, function()
        print("stopping keychords")
        for k,v in pairs(knuchords) do
            guard(v:stop())
        end
    end)

@knu Do you know where the issue is located and how one could resolve it?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.