Hummerspoon 实现剪切板功能

查看 16|回复 1
作者:mysline   
看到有人分享剪切板工具,我之前用的 maccy,现在直接用 hummerspood 替换掉了, 感觉不错,几乎不占资源.


image.png (144.54 KB, 下载次数: 0)
下载附件
界面
2025-10-14 10:45 上传



image.png (113.73 KB, 下载次数: 0)
下载附件
2025-10-14 10:46 上传



image.png (67.77 KB, 下载次数: 0)
下载附件
搜索
2025-10-14 10:47 上传

使用方法,复制到你的 hummerspood 目录,再 init.lua 里面加载上就行了..
脚本会把剪切板的数据保存在这两个目录下
[Lua] 纯文本查看 复制代码local history_file = os.getenv("HOME") .. "/.hammerspoon/config/clipboard_history/clipboard_history.json"
local images_dir = os.getenv("HOME") .. "/.hammerspoon/config/clipboard_history/images/"
以下是全部源码,需要的朋友,纳之
[Lua] 纯文本查看 复制代码-- clipboard_history.lua
local pasteboard = require("hs.pasteboard")
local chooser = require("hs.chooser")
local hotkey = require("hs.hotkey")
local image = require("hs.image")
local eventtap = require("hs.eventtap")
local timer = require("hs.timer")
local json = require("hs.json")
-- Configuration
local history_size = 200
local hotkey_mod = {"cmd", "shift"}
local hotkey_key = "v"
local chooser_width = 60  -- 减小宽度,更紧凑
local chooser_rows = 12   -- 减少行数,避免过高
local history_file = os.getenv("HOME") .. "/.hammerspoon/config/clipboard_history/clipboard_history.json"
local images_dir = os.getenv("HOME") .. "/.hammerspoon/config/clipboard_history/images/"
-- Store clipboard history
local clipboard_history = {}
-- Forward declarations
local save_history
local load_history
-- Function to ensure images directory exists
local function ensure_images_dir()
    local cmd = "mkdir -p '" .. images_dir .. "'"
    os.execute(cmd)
end
-- Function to save history to file
save_history = function()
    ensure_images_dir()
   
    local serializable_history = {}
    for i, entry in ipairs(clipboard_history) do
        if entry.type == "string" then
            table.insert(serializable_history, {
                type = entry.type,
                content = entry.content,
                text = entry.text
            })
        elseif entry.type == "image" then
            -- Save image to file and store file path
            local timestamp = os.time()
            local filename = "image_" .. timestamp .. "_" .. i .. ".png"
            local filepath = images_dir .. filename
            
            -- Save image as PNG file
            local png_data = entry.content:encodeAsURLString()
            if png_data then
                -- Decode the data URL and save as file
                local base64_data = png_data:match("data:image/png;base64,(.+)")
                if base64_data then
                    local cmd = "echo '" .. base64_data .. "' | base64 -d > '" .. filepath .. "'"
                    os.execute(cmd)
                    
                    table.insert(serializable_history, {
                        type = entry.type,
                        content = filepath,  -- Store file path instead of image data
                        text = entry.text,
                        subText = entry.subText
                    })
                end
            end
        end
    end
   
    local success, json_string = pcall(json.encode, serializable_history)
    if success then
        local file = io.open(history_file, "w")
        if file then
            file:write(json_string)
            file:close()
        end
    end
end
-- Function to load history from file
load_history = function()
    local file = io.open(history_file, "r")
    if not file then return end
   
    local content = file:read("*all")
    file:close()
   
    if not content or content == "" then return end
   
    local success, data = pcall(json.decode, content)
    if not success or not data then return end
   
    clipboard_history = {}
    for i, entry in ipairs(data) do
        if entry.type == "string" then
            table.insert(clipboard_history, {
                type = entry.type,
                content = entry.content,
                text = entry.text
            })
        elseif entry.type == "image" then
            -- Load image from file path
            local img = image.imageFromPath(entry.content)
            if img then
                local size = img:size()
                local max_dimension = 32  -- 与add_to_history保持一致的缩略图尺寸
                local scale_w = max_dimension / size.w
                local scale_h = max_dimension / size.h
                local scale = math.min(scale_w, scale_h)
               
                -- Ensure minimum scale to avoid too small thumbnails
                if scale  80 then
            display_text = string.sub(content, 1, 77) .. "..."
        end
        entry.text = display_text
        entry.subText = "点击粘贴"
        entry.image = nil
    end
    -- Avoid duplicates at the top
    if clipboard_history[1] then
        if clipboard_history[1].type == type then
            if type == "string" and clipboard_history[1].content == content then
                return
            elseif type == "image" then
                -- For images, we'll just check if it's the same size (simple comparison)
                local prev_size = clipboard_history[1].content:size()
                local curr_size = content:size()
                if prev_size.w == curr_size.w and prev_size.h == curr_size.h then
                    return
                end
            end
        end
    end
    -- Add to the top of the history
    table.insert(clipboard_history, 1, entry)
    -- Trim history to history_size and clean up removed image files
    while #clipboard_history > history_size do
        local removed_entry = table.remove(clipboard_history, #clipboard_history)
        -- If removed entry is an image with filepath, delete the file
        if removed_entry.type == "image" and removed_entry.filepath then
            os.remove(removed_entry.filepath)
        end
    end
    -- Update chooser
    history_chooser:choices(clipboard_history)
   
    -- Save to file
    save_history()
   
    -- Periodic cleanup of orphaned files
    if #clipboard_history % 10 == 0 then
        cleanup_old_images()
    end
end
-- Monitor clipboard changes
local last_change_count = pasteboard.changeCount()
local clipboard_timer = timer.new(1, function()
    if pasteboard.changeCount() ~= last_change_count then
        last_change_count = pasteboard.changeCount()
        
        -- Check for image first
        local img = pasteboard.readImage()
        if img then
            add_to_history(img, "image")
        else
            -- Check for text
            local str = pasteboard.readString()
            if str and str ~= "" then
                add_to_history(str, "string")
            end
        end
    end
end)
clipboard_timer:start()
-- Hotkey to show the chooser
local clipboard_hotkey = hotkey.bind(hotkey_mod, hotkey_key, function()
    history_chooser:show()
end)
-- 模块清理函数
function cleanup()
    if clipboard_timer then
        clipboard_timer:stop()
        clipboard_timer = nil
    end
    if clipboard_hotkey then
        clipboard_hotkey:delete()
        clipboard_hotkey = nil
    end
    if history_chooser then
        history_chooser:hide()
        history_chooser = nil
    end
    print("✓ 剪切板历史模块已清理")
end
-- Load history from file on startup
load_history()
-- Update chooser with loaded history after creation
if #clipboard_history > 0 then
    history_chooser:choices(clipboard_history)
end
-- Initial population (only if no history was loaded)
if #clipboard_history == 0 then
    local img = pasteboard.readImage()
    if img then
        add_to_history(img, "image")
    else
        local str = pasteboard.readString()
        if str and str ~= "" then
            add_to_history(str, "string")
        end
    end
end
-- 返回清理函数供模块管理器调用
return { cleanup = cleanup }

剪切板, 下载次数

mryouan   

这是在mac电脑上用的吗?
您需要登录后才可以回帖 登录 | 立即注册

返回顶部