Clipboard Slots

@rebindv0.1.4

Copy the clipboard into one of five numbered slots on demand, then type a slot back out with a chord.

Files

main.luau

-- Clipboard Slots: capture the clipboard into a numbered slot, type it back
-- later. Slots live in memory only and vanish when the script stops.

local SLOTS = 5

local MOD_NAMES = {
  "Ctrl+Alt",
  "Ctrl+Shift",
  "Alt+Shift",
  "Ctrl+Alt+Shift",
  "Cmd/Win+Alt",
  "Cmd/Win+Shift",
}
local MODS = {
  ["Ctrl+Alt"] = { ctrl = true, alt = true },
  ["Ctrl+Shift"] = { ctrl = true, shift = true },
  ["Alt+Shift"] = { alt = true, shift = true },
  ["Ctrl+Alt+Shift"] = { ctrl = true, alt = true, shift = true },
  ["Cmd/Win+Alt"] = { win = true, alt = true },
  ["Cmd/Win+Shift"] = { win = true, shift = true },
}

local schema = {
  captureModifier = UI.Select(
    "Ctrl+Alt",
    MOD_NAMES,
    { label = "Capture: modifier + slot key", tab = "Settings" }
  ),
  insertModifier = UI.Select(
    "Ctrl+Shift",
    MOD_NAMES,
    { label = "Insert: modifier + slot key", tab = "Settings" }
  ),
}
for n = 1, SLOTS do
  schema[`key{n}`] =
    UI.Keybind(tostring(n), { label = `Slot {n} key`, tab = "Settings" })
end
local cfg = UI.Schema(schema)

local slots = {}

local function chordHeld(name)
  local want = MODS[name] or {}
  local have = Input.GetModifiers()
  for _, m in { "ctrl", "alt", "shift", "win" } do
    if (want[m] == true) ~= have[m] then
      return false
    end
  end
  return true
end

local function capture(n)
  local ok, text = pcall(Clipboard.Get)
  if not ok then
    Log.Error(`slot {n}: {text}`)
    UI.Notify("Clipboard could not be read", "error")
    return
  end
  if text == nil or text == "" then
    UI.Notify("Clipboard has no text", "warning")
    return
  end
  slots[n] = text
  UI.Notify(`Slot {n}: {#text} characters`, "success")
end

-- type only after the chord is fully released: a modifier still held turns
-- the inserted characters into shortcuts in the receiving app.
local function insert(n)
  Run(function()
    local waited = 0
    while waited < 1000 do
      local m = Input.GetModifiers()
      if not (m.ctrl or m.alt or m.shift or m.win) then
        break
      end
      Sleep(10)
      waited += 10
    end
    if waited >= 1000 then
      UI.Notify("Release the modifier keys to insert", "warning")
      return
    end
    local text = slots[n]
    if text == nil then
      UI.Notify(`Slot {n} is empty`, "warning")
      return
    end
    local ok, err = pcall(System.TypeText, text)
    if not ok then
      Log.Error(`slot {n}: {err}`)
      UI.Notify(`Slot {n} could not be typed`, "error")
    end
  end)
end

for n = 1, SLOTS do
  local key = cfg[`key{n}`]
  Bind(key, {
    when = function()
      return chordHeld(cfg.captureModifier)
    end,
    action = function()
      capture(n)
      return nil
    end,
  })
  Bind(key, {
    when = function()
      return chordHeld(cfg.insertModifier)
    end,
    action = function()
      return nil
    end,
    release = function()
      insert(n)
    end,
  })
end

function OnStart()
  Log.Info(
    `{cfg.captureModifier} + key captures, {cfg.insertModifier} + key types. Slots clear on stop.`
  )
end

function OnStop()
  slots = {}
end