; === 윈도우 탐색기 보완 (AutoHotkey v2) === ; 1) Win+E -> 바탕화면 (분류 방법 = 이름, 이름 오름차순) ; 2) Win+W -> 바탕화면 (분류 방법 = 수정한 날짜, 최신이 위) ; 3) 같은 폴더에 붙여넣기 -> "- 복사본" 대신 타임스탬프 접두어 ; 문서.hwp -> 260915-문서.hwp -> 260915-문서(2).hwp -> (3) ... ; 다른 폴더 붙여넣기 / 잘라내기(Ctrl+X) 는 윈도우 기본 동작 그대로. ; 4) Ctrl+Shift+N (새 폴더) -> 이름 맨 앞에 타임스탬프 접두어 자동 삽입 ; 새 폴더 -> 260915-새 폴더 (이름 바꾸기 입력은 그대로 이어서 가능) ; 5) 타임스탬프 형식은 트레이 아이콘 우클릭 → "타임스탬프 형식"에서 선택 ; (선택한 형식은 explorer_tweaks_config.ini 에 저장되어 계속 유지됩니다) #Requires AutoHotkey v2.0 #SingleInstance Force ; 바탕화면 대신 다른 폴더를 열려면 이 줄만 고치세요. ; 시스템 아이콘(내 PC/휴지통)까지 나오는 옛날식 바탕화면은 자동 보기 지정이 안 됩니다. global DESKTOP_PATH := A_Desktop ; --- 타임스탬프 형식 (트레이 메뉴에서 선택 → 계속 저장됨) --- global CONFIG_PATH := A_ScriptDir "\explorer_tweaks_config.ini" global STAMP_FORMATS := Map( "date6", "yyMMdd", ; 260915 "date6time", "yyMMddHHmm", ; 2609151728 "date8time", "yyyyMMddHHmm", ; 202609151728 "notime8", "MMddHHmm") ; 09151728 (연도 없음) global STAMP_ORDER := ["date6", "date6time", "date8time", "notime8"] global STAMP_DESC := Map( "date6", "날짜만", "date6time", "날짜+시간", "date8time", "연도4자리+시간", "notime8", "월일+시간(연도없음)") global currentStampKey := LoadStampFormat() ; 메뉴 항목 문구는 한 번 계산해서 고정 캐시에 저장 (매번 다시 계산하면 ; 현재 시각이 바뀌어 문구가 달라지고, AHK는 항목을 "문구"로 찾기 때문에 ; Check/Uncheck 이 엉뚱한(옛) 문구를 찾다가 조용히 실패하게 됨) global STAMP_LABEL_CACHE := Map() for stampKey_, stampPattern_ in STAMP_FORMATS STAMP_LABEL_CACHE[stampKey_] := STAMP_DESC[stampKey_] " (" FormatTime(A_Now, stampPattern_) ")" LoadStampFormat() { global CONFIG_PATH, STAMP_FORMATS v := IniRead(CONFIG_PATH, "Stamp", "Format", "date6") if !STAMP_FORMATS.Has(v) v := "date6" return v } CurrentStampPattern() { global currentStampKey, STAMP_FORMATS return STAMP_FORMATS[currentStampKey] } SetStampFormat(key, *) { global currentStampKey, CONFIG_PATH, STAMP_LABEL_CACHE currentStampKey := key IniWrite(key, CONFIG_PATH, "Stamp", "Format") RefreshStampMenuChecks() ToolTip("타임스탬프 형식 → " STAMP_LABEL_CACHE[key]) SetTimer(() => ToolTip(), -1500) } RefreshStampMenuChecks() { global stampMenu, currentStampKey, STAMP_ORDER, STAMP_LABEL_CACHE for key in STAMP_ORDER { if (key = currentStampKey) stampMenu.Check(STAMP_LABEL_CACHE[key]) else stampMenu.Uncheck(STAMP_LABEL_CACHE[key]) } } ; 트레이 메뉴에 "타임스탬프 형식" 하위 메뉴 추가 global stampMenu := Menu() for key in STAMP_ORDER stampMenu.Add(STAMP_LABEL_CACHE[key], SetStampFormat.Bind(key)) RefreshStampMenuChecks() A_TrayMenu.Add() ; 구분선 A_TrayMenu.Add("타임스탬프 형식", stampMenu) ; --- 1) Win+E = 바탕화면 (이름으로 분류) --- #e::OpenDesktop(false) ; --- 2) Win+W = 바탕화면 (수정한 날짜로 분류) --- ; 윈도우에는 "이렇게 열기" 설정이 없고, 보기 설정은 폴더마다 하나만 기억됩니다. ; 그래서 창을 연 뒤 IFolderView2 로 분류 방법을 매번 직접 지정합니다. #w::OpenDesktop(true) OpenDesktop(groupByDate) { ; 새로 열릴 창을 "창 핸들"로 붙잡는다. ; 경로로 찾으면 늦게 열린 다른 창에 잘못 적용될 수 있음. before := Map() for h in WinGetList("ahk_class CabinetWClass") before[h] := 1 Run('explorer.exe "' DESKTOP_PATH '"') hwnd := 0 loop 100 { ; 새 창 대기 (최대 10초) Sleep(100) for h in WinGetList("ahk_class CabinetWClass") { if !before.Has(h) { hwnd := h break } } if hwnd break } if !hwnd return ; 창은 열렸으니 보기 지정만 생략 loop 100 { ; 그 창이 준비되면 보기 적용 (최대 10초) Sleep(100) if !WinExist("ahk_id " hwnd) return try { for w in ComObject("Shell.Application").Windows { try { if (w.HWND != hwnd) continue } catch { continue } try { ApplyView(w, groupByDate) return } } } } } ; 열린 탐색기 창의 분류/정렬을 직접 지정 ApplyView(shellWindow, groupByDate) { static SID_TopLevelBrowser := "{4C96BE40-915C-11CF-99D3-00AA004AE837}" static IID_IShellBrowser := "{000214E2-0000-0000-C000-000000000046}" static IID_IFolderView2 := "{1af3a467-214f-4298-908e-06b03e0b39f9}" static PKEY_GUID := "{B725F130-47EF-101A-A5F1-02608C9EEBAC}" psb := ComObjQuery(shellWindow, SID_TopLevelBrowser, IID_IShellBrowser) ComCall(15, psb, "ptr*", &pv := 0) ; QueryActiveShellView fv := ComObjQuery(ComValue(13, pv), IID_IFolderView2) if (groupByDate) { ; 수정한 날짜로 분류, 내림차순(오늘이 맨 위). 정렬 기준은 건드리지 않음. ComCall(17, fv, "ptr", PropKey(PKEY_GUID, 14), "int", 0) ; 그룹 안에서도 최신 파일이 위로 오게 하려면 아래 3줄의 ; 를 지우세요. ; sc2 := Buffer(24, 0) ; DllCall("ole32\CLSIDFromString", "wstr", PKEY_GUID, "ptr", sc2), NumPut("uint", 14, sc2, 16), NumPut("int", -1, sc2, 20) ; ComCall(27, fv, "ptr", sc2, "int", 1) } else { ; 이름으로 분류, 오름차순 (분류를 아예 끄려면 아래 줄을 Buffer(20, 0) 으로 교체) ComCall(17, fv, "ptr", PropKey(PKEY_GUID, 10), "int", 1) sc := Buffer(24, 0) ; SORTCOLUMN DllCall("ole32\CLSIDFromString", "wstr", PKEY_GUID, "ptr", sc) NumPut("uint", 10, sc, 16) ; 이름 NumPut("int", 1, sc, 20) ; 오름차순 ComCall(27, fv, "ptr", sc, "int", 1) ; SetSortColumns } } PropKey(guid, pid) { b := Buffer(20, 0) DllCall("ole32\CLSIDFromString", "wstr", guid, "ptr", b) NumPut("uint", pid, b, 16) return b } #HotIf ExplorerListFocused() ^v::PasteWithDateName() ^+n::NewFolderWithDate() #HotIf ExplorerListFocused() { if !(WinActive("ahk_class CabinetWClass") || WinActive("ahk_class ExploreWClass")) return false try { cls := ControlGetClassNN(ControlGetFocus("A")) } catch { return false } ; 파일 목록에 포커스가 있을 때만. (주소창/검색창/이름바꾸기 중이면 기본 동작) return (InStr(cls, "DirectUIHWND") = 1) || (InStr(cls, "SysListView32") = 1) } PasteWithDateName() { ; 판단 단계에서 무슨 일이 생기든 윈도우 기본 붙여넣기로 안전하게 넘어감 try { files := ClipboardFiles() if (files.Length = 0 || IsCutOperation()) { Send("^v") return } cur := RTrim(ExplorerPath(WinExist("A")), "\") if (cur = "") { Send("^v") return } ; 원본이 전부 현재 폴더에 있을 때만 = "같은 공간에 붙여넣기" for f in files { SplitPath(f, , &dir) if (StrLower(RTrim(dir, "\")) != StrLower(cur)) { Send("^v") return } } } catch { Send("^v") return } stamp := FormatTime(A_Now, CurrentStampPattern()) made := 0, failed := 0 for f in files { SplitPath(f, , &dir, &ext, &base) target := BuildTarget(RTrim(dir, "\"), StripStamp(base), ext, stamp) try { if DirExist(f) DirCopy(f, target, false) else FileCopy(f, target, false) made++ } catch { failed++ } } Send("{F5}") msg := made " 개 복사 → " stamp "-" if (failed) msg .= " (실패 " failed ")" ToolTip(msg) SetTimer(() => ToolTip(), -1500) } ; 이미 숫자 타임스탬프(6~12자리)로 시작하면 떼어내고 다시 붙임 ; 형식을 나중에 바꿔도(예: 날짜만 -> 날짜+시간) 예전 접두어가 그대로 남지 않도록 ; 가능한 모든 자릿수(6/8/10/12)를 한 번에 인식함 StripStamp(base) { if RegExMatch(base, "^\d{6,12}-(.+?)(\(\d+\))?$", &m) return m.1 return base } BuildTarget(dir, base, ext, stamp) { tail := (ext != "") ? "." ext : "" cand := dir "\" stamp "-" base tail if !FileExist(cand) return cand n := 2 loop { cand := dir "\" stamp "-" base "(" n ")" tail if !FileExist(cand) return cand n++ if (n > 9999) return dir "\" stamp "-" base "-" A_TickCount tail } } ; --- 4) Ctrl+Shift+N = 새 폴더 이름 맨 앞에 오늘 날짜 자동 삽입 --- NewFolderWithDate() { hwnd := WinExist("A") Send("^+n") ; 실제 폴더 생성 + 이름 바꾸기 진입은 윈도우 기본 동작 loop 30 { ; 이름 바꾸기 입력창이 뜰 때까지 대기 (최대 3초) Sleep(100) if !WinExist("ahk_id " hwnd) return try { cls := ControlGetClassNN(ControlGetFocus("ahk_id " hwnd)) } catch { continue } if (InStr(cls, "Edit") = 1) { Send("{Home}") ; 기본 이름 전체 선택을 풀고 맨 앞으로 Send(FormatTime(A_Now, CurrentStampPattern()) "-") ; 타임스탬프를 이름 맨 앞에 삽입 return } } } ClipboardFiles() { out := [] if !DllCall("IsClipboardFormatAvailable", "uint", 15) ; CF_HDROP return out for line in StrSplit(A_Clipboard, "`n", "`r") { line := Trim(line) if (line != "") out.Push(line) } return out } ; Ctrl+X(잘라내기) 로 담긴 클립보드인지 확인 IsCutOperation() { fmt := DllCall("RegisterClipboardFormat", "str", "Preferred DropEffect", "uint") if (!fmt || !DllCall("IsClipboardFormatAvailable", "uint", fmt)) return false eff := 0 if !DllCall("OpenClipboard", "ptr", 0) return false h := DllCall("GetClipboardData", "uint", fmt, "ptr") if (h) { p := DllCall("GlobalLock", "ptr", h, "ptr") if (p) { eff := NumGet(p, 0, "uint") DllCall("GlobalUnlock", "ptr", h) } } DllCall("CloseClipboard") return (eff & 2) ? true : false ; DROPEFFECT_MOVE } ExplorerPath(hwnd) { try { for w in ComObject("Shell.Application").Windows { try { if (w.HWND = hwnd) return w.Document.Folder.Self.Path } } } return "" }