From 4e21b21a722b82d7a61bdaa9836fbc9d45e50e92 Mon Sep 17 00:00:00 2001 From: DemonGatanjieu <207458273+DemonGatanjieu@users.noreply.github.com> Date: Tue, 13 Oct 2026 13:25:33 +0800 Subject: [PATCH] Show in folder brings Explorer to the front Windows keeps a background program (ComfyUI) from raising a window, so the folder opened behind the browser and was easy to miss. After starting Explorer, the route waits up to 3 s for its window (a new one, or the open one Explorer reused, matched by the folder's name in the title) and raises it, borrowing the foreground window's input queue for the moment. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 +- api/model_folder.py | 70 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ca2d4b..1fee77e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### 💬 From a Bilibili comment (来自 B 站评论) - **Esc closes the browser (Esc 关闭浏览器)**: Esc closes what is on top: a dialog or menu first, then the Workflows / Combos workspace, then the browser. The shortcut (Ctrl + Shift + M by default) and the Extensions menu entry now close the browser when it is open. Esc in a text box still belongs to the text box. -- **Show in folder (打开所在文件夹)**: a model's details have **Show in folder** next to **Copy path**: Explorer (or Finder) opens with the file selected. Only on the computer ComfyUI runs on. +- **Show in folder (打开所在文件夹)**: a model's details have **Show in folder** next to **Copy path**: Explorer (or Finder) opens in front of the browser with the file selected. Only on the computer ComfyUI runs on. - **Insert a picture as a node (图片作为节点插入)**: right-click a gallery picture → **Insert as a Load Image node** puts a Load Image node showing that picture in the middle of the canvas. It reads the picture from the output folder; nothing is copied. ### ⌨️ Arrow keys in picture details (查看参数里方向键切图) diff --git a/api/model_folder.py b/api/model_folder.py index db42665..cd32bee 100644 --- a/api/model_folder.py +++ b/api/model_folder.py @@ -5,15 +5,85 @@ computer ask (it is a POST).""" import os import subprocess import sys +import threading +import time from aiohttp import web from .utils import require_filename, resolve_folder_subdir, resolve_within +def _explorer_windows(): + """Handles of the open Explorer folder windows (window class CabinetWClass).""" + import ctypes + from ctypes import wintypes + + found = [] + callback_type = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM) + + def collect(hwnd, _): + name = ctypes.create_unicode_buffer(64) + ctypes.windll.user32.GetClassNameW(hwnd, name, 64) + if name.value == "CabinetWClass": + found.append(hwnd) + return True + + ctypes.windll.user32.EnumWindows(callback_type(collect), 0) + return found + + +def _window_title(hwnd): + import ctypes + + title = ctypes.create_unicode_buffer(512) + ctypes.windll.user32.GetWindowTextW(hwnd, title, 512) + return title.value + + +def _to_front(hwnd): + """Windows keeps a background program (ComfyUI here) from raising a window; borrowing the + foreground window's input queue for a moment lets it.""" + import ctypes + + user32, kernel32 = ctypes.windll.user32, ctypes.windll.kernel32 + foreground = user32.GetForegroundWindow() + theirs = user32.GetWindowThreadProcessId(foreground, None) + mine = kernel32.GetCurrentThreadId() + attached = theirs and theirs != mine and user32.AttachThreadInput(mine, theirs, True) + try: + user32.ShowWindow(hwnd, 9 if user32.IsIconic(hwnd) else 5) # SW_RESTORE / SW_SHOW + user32.BringWindowToTop(hwnd) + user32.SetForegroundWindow(hwnd) + finally: + if attached: + user32.AttachThreadInput(mine, theirs, False) + + +def _raise_explorer(folder, before): + """Waits up to 3 s for the window showing `folder` (a new one, or the open one Explorer + reused) and brings it to the front.""" + names = (os.path.basename(folder).lower(), os.path.normcase(folder).lower()) + + def shows_folder(hwnd): + # The folder's name (or full path), then " - File Explorer" in the system's language. + title = _window_title(hwnd).lower() + return any(title == name or title.startswith(name + " - ") for name in names) + + for _ in range(30): + time.sleep(0.1) + windows = _explorer_windows() + fresh = [hwnd for hwnd in windows if hwnd not in before] + match = fresh or [hwnd for hwnd in windows if shows_folder(hwnd)] + if match: + _to_front(match[0]) + return + + def _reveal(path): if sys.platform == "win32": + before = set(_explorer_windows()) subprocess.Popen(f'explorer /select,"{path}"') + threading.Thread(target=_raise_explorer, args=(os.path.dirname(path), before), daemon=True).start() elif sys.platform == "darwin": subprocess.Popen(["open", "-R", path]) else: