
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 }