Compare commits

..

3 Commits

Author SHA1 Message Date
koreafood fd17d81cb9 feat: add PicsumRandom wallpaper Spoon with hotkey
Implement a Hammerspoon Spoon that sets random Lorem Picsum wallpapers on a 3-hour automatic refresh interval. Add Ctrl+Alt+Cmd+W hotkey for manual immediate refresh with system conflict detection. Update main init.lua to load the Spoon, handle missing installation errors, and bind the hotkey. Set up cache directory management for downloaded wallpaper images and replace the prior Unsplash-based wallpaper setup.
2026-06-26 10:07:16 +09:00
koreafood de5f09da95 수정 2026-06-26 08:58:20 +09:00
koreafood e970b7b04b feat: add cmd+b hotkey to auto-type stripped clipboard text
Add a new global Cmd+B hotkey that reads clipboard content, removes all hyphens,
and sequentially types the cleaned text into the currently focused application. Include
prerequisite checks for accessibility permissions and secure input state, error handling for
clipboard access and typing failures, and system hotkey conflict detection to avoid overriding
existing macOS shortcuts. Also add the project's git commit message guidelines in
.trae/rules/git-commit-message.md.
2026-06-26 08:56:18 +09:00
9 changed files with 260 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
---
alwaysApply: true
scene: git_message
---
한글로 작성
맨위에 한줄요약으로 시작
그 아래에 메시지 작성
+113
View File
@@ -0,0 +1,113 @@
--- === PicsumRandom === -- Lorem Picsum 기반으로 랜덤 배경화면을 적용하는 Spoon 이름을 선언합니다.
--- -- Spoon 설명 헤더 구분선입니다.
--- Automatically sets a random Lorem Picsum image as your wallpaper periodically. -- Spoon 동작 설명을 문서 헤더에 남깁니다.
--- -- Spoon 설명 헤더 구분선입니다.
--- Local custom Spoon for Hammerspoon. -- 로컬 사용자 정의 Spoon임을 문서 헤더에 남깁니다.
local obj = {} -- Spoon 객체 테이블을 생성합니다.
obj.__index = obj -- 메서드 조회를 위해 자기 자신을 메타테이블 인덱스로 지정합니다.
obj.name = "PicsumRandom" -- Spoon 이름을 정의합니다.
obj.version = "1.0" -- Spoon 버전을 정의합니다.
obj.author = "OpenAI" -- 현재 로컬 Spoon 작성자를 기록합니다.
obj.homepage = "https://picsum.photos/" -- 사용 중인 오픈소스 이미지 서비스 홈페이지를 기록합니다.
obj.license = "MIT" -- 로컬 Spoon 라이선스 표기를 남깁니다.
obj.maxPixelDimension = 4096 -- Picsum이 안정적으로 처리할 수 있도록 요청 최대 한 변 길이를 제한합니다.
obj.refreshIntervalSeconds = 3 * 60 * 60 -- 배경화면 자동 갱신 주기를 3시간으로 설정합니다.
obj.firstRefreshDelaySeconds = 5 -- 설정 로드 후 첫 갱신까지의 지연 시간을 5초로 설정합니다.
obj.cacheRootDirectory = hs.configdir .. "/cache" -- 다운로드 캐시의 상위 디렉터리 경로를 정의합니다.
obj.cacheDirectory = hs.configdir .. "/cache/PicsumRandom" -- 다운로드 이미지를 저장할 캐시 디렉터리 경로를 정의합니다.
obj.currentDownloadPath = nil -- 현재 다운로드 대상 파일 경로를 저장할 변수를 초기화합니다.
obj.task = nil -- 현재 실행 중인 다운로드 태스크를 저장할 변수를 초기화합니다.
obj.timer = nil -- 주기 실행 타이머 객체를 저장할 변수를 초기화합니다.
local function ensureCacheDirectory() -- 캐시 디렉터리가 없으면 생성하는 보조 함수를 정의합니다.
if hs.fs.attributes(obj.cacheRootDirectory) == nil then -- 상위 cache 디렉터리 존재 여부를 먼저 확인합니다.
hs.fs.mkdir(obj.cacheRootDirectory) -- 상위 cache 디렉터리가 없을 때 먼저 생성합니다.
end -- 상위 cache 디렉터리 생성 분기를 마칩니다.
if hs.fs.attributes(obj.cacheDirectory) == nil then -- 캐시 디렉터리 존재 여부를 먼저 확인합니다.
hs.fs.mkdir(obj.cacheDirectory) -- 캐시 디렉터리가 없을 때 생성합니다.
end -- 캐시 디렉터리 생성 분기를 마칩니다.
end -- 캐시 디렉터리 보장 함수 정의를 마칩니다.
local function buildImageRequest() -- 현재 메인 화면 크기에 맞는 Picsum 요청 정보를 생성하는 함수를 정의합니다.
local screenObject = hs.screen.mainScreen() -- 현재 메인 화면 객체를 가져옵니다.
local screenMode = screenObject:currentMode() -- 메인 화면의 현재 해상도와 배율 정보를 읽습니다.
local rawPixelWidth = math.floor(screenMode.w * screenMode.scale) -- 실제 픽셀 기준 원본 가로 길이를 계산합니다.
local rawPixelHeight = math.floor(screenMode.h * screenMode.scale) -- 실제 픽셀 기준 원본 세로 길이를 계산합니다.
local maxDimension = math.max(rawPixelWidth, rawPixelHeight) -- 원본 해상도에서 가장 긴 한 변 길이를 계산합니다.
local resizeRatio = 1 -- 기본적으로는 축소 없이 원본 비율을 유지하도록 배율을 초기화합니다.
if maxDimension > obj.maxPixelDimension then -- 요청 크기가 허용 상한을 넘는 경우를 처리합니다.
resizeRatio = obj.maxPixelDimension / maxDimension -- 가장 긴 변이 상한에 맞도록 축소 배율을 계산합니다.
end -- 요청 크기 상한 초과 여부 분기를 마칩니다.
local pixelWidth = math.max(1, math.floor(rawPixelWidth * resizeRatio)) -- 축소 배율을 반영한 최종 가로 길이를 계산합니다.
local pixelHeight = math.max(1, math.floor(rawPixelHeight * resizeRatio)) -- 축소 배율을 반영한 최종 세로 길이를 계산합니다.
local seedValue = tostring(hs.timer.absoluteTime()) -- 매번 다른 이미지를 받기 위해 현재 시간을 문자열 시드로 변환합니다.
local imageUrl = "https://picsum.photos/seed/" .. seedValue .. "/" .. tostring(pixelWidth) .. "/" .. tostring(pixelHeight) .. ".jpg" -- 화면 크기와 시드를 반영한 Picsum 이미지 URL을 생성합니다.
local filePath = obj.cacheDirectory .. "/wallpaper-" .. seedValue .. ".jpg" -- macOS 캐시를 피하기 위해 매 요청마다 고유한 저장 경로를 생성합니다.
return screenObject, imageUrl, filePath -- 화면 객체와 이미지 URL과 저장 경로를 함께 반환합니다.
end -- 이미지 요청 정보 생성 함수 정의를 마칩니다.
local function applyDownloadedWallpaper() -- 다운로드된 이미지를 배경화면으로 적용하는 보조 함수를 정의합니다.
if obj.currentDownloadPath == nil then -- 현재 다운로드 파일 경로가 없는 비정상 상태를 먼저 처리합니다.
hs.printf("PicsumRandom: download path is missing") -- 디버깅을 위해 경로 누락 상태를 콘솔에 기록합니다.
return -- 적용할 파일이 없으므로 함수를 종료합니다.
end -- 다운로드 경로 존재 여부 검사 분기를 마칩니다.
if hs.fs.attributes(obj.currentDownloadPath) == nil then -- 파일이 실제로 존재하는지 다시 확인합니다.
hs.printf("PicsumRandom: downloaded file not found at %s", obj.currentDownloadPath) -- 누락된 파일 경로를 콘솔에 기록합니다.
return -- 파일이 없으면 배경화면 적용을 중단합니다.
end -- 파일 존재 여부 검사 분기를 마칩니다.
hs.screen.mainScreen():desktopImageURL("file://" .. obj.currentDownloadPath) -- 다운로드된 이미지를 현재 메인 화면의 배경화면으로 적용합니다.
end -- 배경화면 적용 함수 정의를 마칩니다.
local function downloadCallback(exitCode, standardOutput, standardError) -- 다운로드 태스크 완료 후 호출될 콜백 함수를 정의합니다.
obj.task = nil -- 다운로드 완료 시 실행 중 태스크 참조를 먼저 정리합니다.
if exitCode == 0 then -- curl 실행이 성공한 경우를 처리합니다.
applyDownloadedWallpaper() -- 성공적으로 저장된 이미지를 배경화면으로 적용합니다.
else -- curl 실행이 실패한 경우를 처리합니다.
hs.printf("PicsumRandom download failed: %s %s", tostring(standardOutput), tostring(standardError)) -- 실패 원인을 콘솔에 기록합니다.
end -- 다운로드 성공 여부 분기를 마칩니다.
end -- 다운로드 완료 콜백 함수 정의를 마칩니다.
function obj:refresh() -- 새 랜덤 이미지를 즉시 받아 적용하는 공개 메서드를 정의합니다.
ensureCacheDirectory() -- 다운로드 전에 캐시 디렉터리가 준비되도록 보장합니다.
if self.task ~= nil then -- 기존 다운로드 태스크가 아직 실행 중인 경우를 처리합니다.
self.task:terminate() -- 중복 다운로드를 막기 위해 이전 태스크를 종료합니다.
self.task = nil -- 종료한 이전 태스크 참조를 정리합니다.
end -- 중복 다운로드 방지 분기를 마칩니다.
local _, imageUrl, filePath = buildImageRequest() -- 현재 화면에 맞는 이미지 URL과 저장 경로를 계산합니다.
self.currentDownloadPath = filePath -- 이번 다운로드의 저장 경로를 객체 상태에 보관합니다.
self.task = hs.task.new("/usr/bin/curl", downloadCallback, { "-L", "-f", "-sS", imageUrl, "-o", filePath }) -- 실패를 감지할 수 있도록 curl 다운로드 태스크를 생성합니다.
if self.task == nil then -- 태스크 생성 자체가 실패한 경우를 처리합니다.
hs.printf("PicsumRandom: failed to create curl task") -- 태스크 생성 실패를 콘솔에 기록합니다.
return -- 더 진행할 수 없으므로 함수를 종료합니다.
end -- 태스크 생성 성공 여부 분기를 마칩니다.
self.task:start() -- 생성된 다운로드 태스크를 즉시 시작합니다.
end -- 수동 새로고침 메서드 정의를 마칩니다.
function obj:start() -- 주기적 랜덤 배경화면 갱신을 시작하는 메서드를 정의합니다.
ensureCacheDirectory() -- 시작 전에 캐시 디렉터리가 존재하도록 보장합니다.
if self.timer == nil then -- 아직 타이머가 만들어지지 않은 첫 시작 상황을 처리합니다.
self.timer = hs.timer.doEvery(self.refreshIntervalSeconds, function() self:refresh() end) -- 설정한 주기마다 새 배경화면을 받는 타이머를 생성합니다.
else -- 이미 타이머가 존재하는 경우를 처리합니다.
self.timer:start() -- 기존 타이머를 다시 시작합니다.
end -- 타이머 생성 여부 분기를 마칩니다.
self.timer:setNextTrigger(self.firstRefreshDelaySeconds) -- 첫 배경화면 갱신 시점을 짧게 앞당겨 설정합니다.
end -- 자동 갱신 시작 메서드 정의를 마칩니다.
function obj:stop() -- 자동 갱신을 중단하는 메서드를 정의합니다.
if self.timer ~= nil then -- 생성된 타이머가 있는 경우만 처리합니다.
self.timer:stop() -- 주기 실행 타이머를 중지합니다.
end -- 타이머 중지 분기를 마칩니다.
if self.task ~= nil then -- 실행 중인 다운로드 태스크가 있는 경우를 처리합니다.
self.task:terminate() -- 진행 중 다운로드를 안전하게 중단합니다.
self.task = nil -- 종료한 태스크 참조를 정리합니다.
end -- 다운로드 태스크 정리 분기를 마칩니다.
end -- 자동 갱신 중지 메서드 정의를 마칩니다.
function obj:init() -- Spoon 로드 직후 초기화 메서드를 정의합니다.
self:start() -- Spoon이 로드되면 자동 갱신을 바로 시작합니다.
end -- Spoon 초기화 메서드 정의를 마칩니다.
return obj -- 완성된 Spoon 객체를 반환합니다.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 936 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 KiB

+139
View File
@@ -188,6 +188,122 @@ hs.keycodes.inputSourceChanged(function() -- 입력기 전환이 일어날 때
showInputSourceHud() -- 한글과 영문 전환 결과에 맞는 중앙 표시를 즉시 띄웁니다.
end) -- 입력기 변경 콜백 등록을 마칩니다.
-- `cmd`+`b`로 클립보드 문자열에서 하이픈을 제거한 뒤 연속 자동 입력하는 기능 설정을 시작합니다.
local clipboardTypeHotkeyModifiers = { "cmd" } -- 자동 입력 기능에 사용할 수정 키 조합을 정의합니다.
local clipboardTypeHotkeyKey = "b" -- 자동 입력 기능에 사용할 기본 키를 정의합니다.
local clipboardTypeInterval = 0.12 -- 각 문자 입력 사이에 둘 짧은 지연 시간을 초 단위로 정의합니다.
local clipboardTypingInProgress = false -- 자동 입력이 이미 진행 중인지 추적해 중복 실행을 막습니다.
local clipboardTypeHotkey = nil -- 충돌 검사 후 실제로 바인딩된 단축키 객체를 저장합니다.
-- 문자열의 각 문자를 순서대로 담은 큐 테이블을 만드는 함수를 정의합니다.
local function buildCharacterQueue(text) -- 하이픈 제거 후 남은 모든 문자를 순차 전송하기 위한 준비 함수입니다.
local queue = {} -- 순서대로 전송할 문자 목록을 빈 테이블로 초기화합니다.
for _, codepoint in utf8.codes(text) do -- UTF-8 문자열을 코드포인트 단위로 안전하게 순회합니다.
queue[#queue + 1] = utf8.char(codepoint) -- 읽어온 문자를 그대로 큐 뒤에 추가합니다.
end -- 문자열 전체를 큐로 변환하는 반복을 마칩니다.
return queue -- 완성된 문자 큐를 호출한 곳에 반환합니다.
end -- 문자 큐 생성 함수 정의를 마칩니다.
-- 현재 환경에서 자동 키 입력을 수행할 수 있는지 선검사하는 함수를 정의합니다.
local function ensureClipboardTypingPrerequisites() -- 권한과 보안 입력 상태를 미리 확인해 실패를 줄입니다.
if not hs.accessibilityState() then -- 손쉬운 사용 권한이 없으면 키 이벤트 전송이 불가능하므로 먼저 처리합니다.
hs.accessibilityState(true) -- macOS 권한 허용 안내를 띄워 사용자가 바로 승인할 수 있게 돕습니다.
hs.alert.show("Hammerspoon 손쉬운 사용 권한이 필요합니다") -- 권한 필요 안내를 화면에 표시합니다.
return false -- 아직 자동 입력을 진행할 수 없으므로 실패를 반환합니다.
end -- 손쉬운 사용 권한 검사 분기 종료입니다.
if hs.eventtap.isSecureInputEnabled() then -- 보안 입력이 활성화된 앱에서는 키 이벤트 주입이 막힐 수 있으므로 점검합니다.
hs.alert.show("보안 입력이 켜져 있어 자동 입력을 실행할 수 없습니다") -- 현재 상태를 사용자에게 즉시 알립니다.
return false -- 안전하지 않은 상태에서는 자동 입력을 중단합니다.
end -- 보안 입력 상태 검사 분기 종료입니다.
return true -- 필요한 선행 조건이 모두 충족됐음을 반환합니다.
end -- 자동 입력 선행 조건 검사 함수 정의를 마칩니다.
-- 한 글자를 현재 포커스된 앱으로 전송하는 함수를 정의합니다.
local function postClipboardCharacter(character) -- 남아 있는 문자를 현재 포커스된 앱에 한 글자씩 안전하게 전송합니다.
hs.eventtap.keyStrokes(character, 0) -- 현재 포커스된 앱에 해당 문자를 한 글자씩 전달합니다.
end -- 단일 문자 전송 함수 정의를 마칩니다.
-- 문자 큐를 타이머 기반으로 순차 입력하는 함수를 정의합니다.
local function typeClipboardQueue(queue, index) -- 긴 입력 중에도 설정 전체가 멈추지 않도록 비동기식으로 처리합니다.
if not clipboardTypingInProgress then -- 외부 오류나 상태 변경으로 이미 종료됐다면 즉시 빠져나옵니다.
return -- 종료된 작업을 더 진행하지 않도록 여기서 함수를 마칩니다.
end -- 실행 상태 검사 분기 종료입니다.
if index > #queue then -- 마지막 문자까지 모두 전송했다면 정상 완료 처리를 진행합니다.
clipboardTypingInProgress = false -- 다음 실행을 위해 진행 중 상태를 해제합니다.
return -- 더 이상 전송할 문자가 없으므로 함수를 종료합니다.
end -- 입력 완료 여부 검사 분기 종료입니다.
local ok, errorMessage = pcall(postClipboardCharacter, queue[index]) -- 현재 문자를 안전하게 전송하고 예외를 가로챕니다.
if not ok then -- 문자 전송 중 런타임 오류가 발생한 경우를 처리합니다.
clipboardTypingInProgress = false -- 오류 발생 시에도 다음 실행을 위해 상태를 정상적으로 복구합니다.
hs.alert.show("자동 입력 중 오류가 발생했습니다") -- 사용자에게 실패 사실을 화면으로 알려줍니다.
hs.printf("Clipboard typing failed: %s", tostring(errorMessage)) -- 디버깅을 위해 콘솔에 구체적인 오류를 기록합니다.
return -- 오류 이후 추가 입력이 이어지지 않도록 즉시 종료합니다.
end -- 문자 전송 예외 처리 분기 종료입니다.
hs.timer.doAfter(clipboardTypeInterval, function() -- 다음 문자 입력을 짧은 지연 뒤 예약해 앱이 입력을 안정적으로 받게 합니다.
typeClipboardQueue(queue, index + 1) -- 다음 위치의 문자를 계속 순차적으로 전송합니다.
end) -- 다음 문자 예약 콜백 정의를 마칩니다.
end -- 문자 큐 순차 입력 함수 정의를 마칩니다.
-- 클립보드 내용을 읽어 하이픈 제거 후 연속 자동 입력 작업을 시작하는 함수를 정의합니다.
local function typeClipboardAsContinuousSequence() -- 실제 단축키가 눌렸을 때 호출될 메인 작업 함수입니다.
if clipboardTypingInProgress then -- 이전 자동 입력이 아직 끝나지 않았다면 중복 실행을 막습니다.
hs.alert.show("자동 입력이 이미 진행 중입니다") -- 재진입이 거부된 이유를 사용자에게 알려줍니다.
return -- 중복 실행은 허용하지 않고 함수를 종료합니다.
end -- 재진입 방지 분기 종료입니다.
if not ensureClipboardTypingPrerequisites() then -- 권한이나 보안 입력 상태가 적절한지 먼저 확인합니다.
return -- 선행 조건이 맞지 않으면 작업을 시작하지 않습니다.
end -- 선행 조건 검사 분기 종료입니다.
local clipboardReadOk, clipboardText = pcall(hs.pasteboard.getContents) -- 클립보드 문자열 읽기를 예외 안전하게 시도합니다.
if not clipboardReadOk then -- 클립보드 접근 자체에서 오류가 발생한 경우를 처리합니다.
hs.alert.show("클립보드 내용을 읽는 중 오류가 발생했습니다") -- 읽기 실패를 사용자에게 알립니다.
hs.printf("Clipboard read failed: %s", tostring(clipboardText)) -- 콘솔에 구체적인 오류 정보를 남깁니다.
return -- 읽기에 실패했으므로 자동 입력을 시작하지 않습니다.
end -- 클립보드 읽기 예외 처리 분기 종료입니다.
if type(clipboardText) ~= "string" or clipboardText == "" then -- 빈 문자열이거나 문자열이 아닌 경우는 유효한 입력 원본이 아닙니다.
hs.alert.show("클립보드에 입력할 텍스트가 없습니다") -- 사용자가 먼저 텍스트를 복사하도록 안내합니다.
return -- 유효한 내용이 없으므로 여기서 종료합니다.
end -- 클립보드 내용 유효성 검사 분기 종료입니다.
local transformedText = clipboardText:gsub("%-", "") -- 자동 포커스 이동을 활용할 수 있도록 모든 하이픈을 제거합니다.
local queue = buildCharacterQueue(transformedText) -- 하이픈이 제거된 문자열을 문자 단위 큐로 만들어 순차 입력 준비를 합니다.
if #queue == 0 then -- 하이픈 제거 후 결과가 실질적으로 비어 있으면 입력할 것이 없으므로 막습니다.
hs.alert.show("하이픈 제거 후 입력할 문자가 없습니다") -- 의미 없는 실행이 되지 않도록 사용자에게 알려줍니다.
return -- 입력 큐가 비어 있으므로 종료합니다.
end -- 하이픈 제거 결과 유효성 검사 분기 종료입니다.
clipboardTypingInProgress = true -- 실제 입력을 시작하기 전에 진행 중 상태를 활성화합니다.
local typingStartOk, typingStartError = pcall(typeClipboardQueue, queue, 1) -- 순차 입력 시작 자체도 예외 안전하게 감쌉니다.
if not typingStartOk then -- 시작 단계에서 예상치 못한 오류가 발생한 경우를 처리합니다.
clipboardTypingInProgress = false -- 시작 실패 시 진행 상태를 즉시 되돌립니다.
hs.alert.show("자동 입력 시작에 실패했습니다") -- 사용자에게 시작 실패를 알립니다.
hs.printf("Clipboard typing start failed: %s", tostring(typingStartError)) -- 콘솔에 실패 원인을 기록합니다.
end -- 자동 입력 시작 예외 처리 분기 종료입니다.
end -- 클립보드 연속 자동 입력 메인 함수 정의를 마칩니다.
-- 시스템 레벨에 이미 예약된 단축키와 충돌하는지 검사합니다.
local clipboardTypeSystemConflict = hs.hotkey.systemAssigned(clipboardTypeHotkeyModifiers, clipboardTypeHotkeyKey) -- macOS가 점유한 조합이면 바인딩을 피하기 위해 조회합니다.
if clipboardTypeSystemConflict then -- 시스템이 이미 이 단축키를 사용 중인 경우를 처리합니다.
hs.alert.show("cmd-b 단축키가 시스템에 이미 할당되어 있어 등록하지 않았습니다") -- 충돌 사실을 화면에 알립니다.
hs.printf("Skipped binding cmd-b due to system conflict: %s", hs.inspect(clipboardTypeSystemConflict)) -- 콘솔에 충돌 세부 정보를 남깁니다.
else -- 시스템 충돌이 없을 때만 안전하게 단축키를 등록합니다.
clipboardTypeHotkey = hs.hotkey.bind(clipboardTypeHotkeyModifiers, clipboardTypeHotkeyKey, function() -- 전역 단축키를 눌렀을 때 메인 작업을 실행합니다.
local hotkeyOk, hotkeyError = pcall(typeClipboardAsContinuousSequence) -- 단축키 콜백 전체를 예외 안전하게 감싸 강제 종료를 막습니다.
if not hotkeyOk then -- 콜백 바깥쪽에서 예상치 못한 오류가 터진 경우를 처리합니다.
clipboardTypingInProgress = false -- 오류 뒤 다음 실행이 가능하도록 상태를 정리합니다.
hs.alert.show("cmd-b 자동 입력 실행에 실패했습니다") -- 사용자에게 실행 실패를 알려줍니다.
hs.printf("cmd-b clipboard hotkey failed: %s", tostring(hotkeyError)) -- 콘솔에 실제 오류 메시지를 기록합니다.
end -- 단축키 콜백 예외 처리 분기 종료입니다.
end) -- 전역 단축키 바인딩 정의를 마칩니다.
end -- 시스템 단축키 충돌 여부에 따른 등록 분기를 마칩니다.
overrideOtherMouseDown:start() -- 보조 버튼 눌림 감시를 시작합니다.
overrideOtherMouseUp:start() -- 보조 버튼 떼기 감시를 시작합니다.
dragOtherToScroll:start() -- 보조 버튼 드래그 스크롤 감시를 시작합니다.
@@ -210,3 +326,26 @@ else -- Caffeine Spoon 로드에 실패한 경우 사용자에게 원인을 알
hs.alert.show("Caffeine.spoon 설치가 필요합니다") -- 누락된 Spoon 설치 필요 안내를 표시합니다.
hs.printf("Failed to load Caffeine spoon: %s", tostring(caffeineError)) -- 콘솔에 로드 실패 원인을 기록합니다.
end -- Caffeine Spoon 로드 예외 처리 분기를 마칩니다.
-- 공식 UnsplashRandom 대신 PicsumRandom Spoon을 로드해 다른 오픈소스 이미지 소스로 배경화면을 갱신합니다.
local picsumRandomRefreshHotkeyModifiers = { "ctrl", "alt", "cmd" } -- 즉시 새 배경화면을 받는 단축키의 수정 키 조합을 정의합니다.
local picsumRandomRefreshHotkeyKey = "w" -- 즉시 새 배경화면을 받는 단축키의 기본 키를 정의합니다.
local picsumRandomRefreshHotkey = nil -- 실제로 등록된 즉시 새로고침 단축키 객체를 저장할 변수를 초기화합니다.
local picsumRandomLoaded, picsumRandomError = pcall(hs.loadSpoon, "PicsumRandom") -- PicsumRandom Spoon 로드와 초기화를 예외 안전하게 시도합니다.
if picsumRandomLoaded then -- PicsumRandom Spoon 로드에 성공한 경우를 처리합니다.
local picsumRandomRefreshSystemConflict = hs.hotkey.systemAssigned(picsumRandomRefreshHotkeyModifiers, picsumRandomRefreshHotkeyKey) -- 시스템이 같은 단축키를 이미 점유하고 있는지 먼저 확인합니다.
if picsumRandomRefreshSystemConflict then -- 시스템 단축키 충돌이 있는 경우를 처리합니다.
hs.alert.show("ctrl-alt-cmd-w 단축키가 시스템에 이미 할당되어 등록하지 않았습니다") -- 단축키 충돌 사실을 사용자에게 화면으로 알립니다.
hs.printf("Skipped binding PicsumRandom refresh hotkey due to system conflict: %s", hs.inspect(picsumRandomRefreshSystemConflict)) -- 충돌 세부 정보를 콘솔에 남겨 원인 파악을 돕습니다.
else -- 시스템 단축키 충돌이 없는 경우를 처리합니다.
picsumRandomRefreshHotkey = hs.hotkey.bind(picsumRandomRefreshHotkeyModifiers, picsumRandomRefreshHotkeyKey, function() -- 전역 단축키를 눌렀을 때 즉시 새 배경화면 갱신을 실행합니다.
hs.alert.show("새 배경화면을 가져오는 중입니다") -- 단축키가 정상 인식됐음을 사용자에게 즉시 알려 줍니다.
spoon.PicsumRandom:refresh() -- 현재 화면 크기에 맞는 새 랜덤 배경화면 다운로드를 바로 시작합니다.
end) -- 즉시 새 배경화면 갱신 단축키 바인딩 정의를 마칩니다.
end -- 시스템 단축키 충돌 여부에 따른 등록 분기를 마칩니다.
hs.printf("PicsumRandom spoon loaded successfully") -- 콘솔에 성공 로그를 남겨 정상 로드를 확인할 수 있게 합니다.
else -- PicsumRandom Spoon 로드에 실패한 경우 사용자에게 원인을 알립니다.
hs.alert.show("PicsumRandom.spoon 설치를 확인해 주세요") -- 누락되었거나 손상된 Spoon 설치 상태를 사용자에게 안내합니다.
hs.printf("Failed to load PicsumRandom spoon: %s", tostring(picsumRandomError)) -- 콘솔에 로드 실패 원인을 기록합니다.
end -- PicsumRandom Spoon 로드 예외 처리 분기를 마칩니다.