From 6015c1ab91d85f0cb22790c09d6d60507ef57287 Mon Sep 17 00:00:00 2001 From: Long Chen Date: Tue, 21 Apr 2026 15:08:55 +0800 Subject: [PATCH 1/2] fix: add macOS compatibility for GUI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace Windows-only `os.startfile()` with cross-platform folder opening using `open` (macOS) / `xdg-open` (Linux) - Gracefully degrade when tkdnd library is unavailable (Tcl/Tk 9.x incompatibility on macOS) — drag-and-drop is disabled but Browse buttons work normally Co-Authored-By: Claude Opus 4.6 --- gui.py | 57 ++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/gui.py b/gui.py index c2c470d..5b21ca0 100644 --- a/gui.py +++ b/gui.py @@ -6,8 +6,16 @@ import os import shutil import webbrowser +import subprocess +import platform import locale -from tkinterdnd2 import DND_FILES, TkinterDnD +try: + from tkinterdnd2 import DND_FILES, TkinterDnD +except Exception: + DND_FILES = None + TkinterDnD = None + +_HAS_DND = False # resolved at App.__init__ time from converter.generator import convert_mineru_to_ppt, TEXT_CLEANUP_MARGIN_RATIO from converter.ocr_merge import OCR_FONT_DISTANCE_THRESHOLD, OCR_FONT_DISTANCE_THRESHOLD_LITE @@ -185,15 +193,17 @@ def _create_widgets(self): tk.Label(frame, text=self.i18n['input_file_label']).grid(row=0, column=0, sticky="w", pady=5) input_entry = tk.Entry(frame, textvariable=self.input_path) input_entry.grid(row=0, column=1, sticky="ew", padx=5) - input_entry.drop_target_register(DND_FILES) - input_entry.dnd_bind('<>', lambda e: self._on_drop(e, self.input_path)) + if _HAS_DND: + input_entry.drop_target_register(DND_FILES) + input_entry.dnd_bind('<>', lambda e: self._on_drop(e, self.input_path)) tk.Button(frame, text=self.i18n['browse_button'], command=self._browse_input).grid(row=0, column=2, padx=5) tk.Label(frame, text=self.i18n['json_file_label']).grid(row=1, column=0, sticky="w", pady=5) json_entry = tk.Entry(frame, textvariable=self.json_path) json_entry.grid(row=1, column=1, sticky="ew", padx=5) - json_entry.drop_target_register(DND_FILES) - json_entry.dnd_bind('<>', lambda e: self._on_drop(e, self.json_path)) + if _HAS_DND: + json_entry.drop_target_register(DND_FILES) + json_entry.dnd_bind('<>', lambda e: self._on_drop(e, self.json_path)) tk.Button(frame, text=self.i18n['browse_button'], command=self._browse_json).grid(row=1, column=2, padx=5) tk.Label(frame, text=self.i18n['output_file_label']).grid(row=2, column=0, sticky="w", pady=5) @@ -338,9 +348,20 @@ def _toggle_ocr_advanced(self): else: frame.grid_remove() -class App(TkinterDnD.Tk): +class App(tk.Tk): def __init__(self): - super().__init__() + global _HAS_DND + if TkinterDnD is not None: + try: + # Try to initialize with drag-and-drop support + TkinterDnD.Tk.__init__(self) + _HAS_DND = True + except Exception: + tk.Tk.__init__(self) + _HAS_DND = False + else: + tk.Tk.__init__(self) + _HAS_DND = False self.i18n = TRANSLATIONS[get_language()] self.title(self.i18n['app_title']) self.geometry("700x600") @@ -385,13 +406,15 @@ def _create_widgets(self): tk.Label(self.single_mode_frame, text=self.i18n['input_file_label']).grid(row=0, column=0, sticky="w", pady=2) input_entry = tk.Entry(self.single_mode_frame, textvariable=self.input_path, state="readonly") input_entry.grid(row=0, column=1, sticky="ew", padx=5) - input_entry.drop_target_register(DND_FILES); input_entry.dnd_bind('<>', lambda e: self._on_drop(e, self.input_path)) + if _HAS_DND: + input_entry.drop_target_register(DND_FILES); input_entry.dnd_bind('<>', lambda e: self._on_drop(e, self.input_path)) tk.Button(self.single_mode_frame, text=self.i18n['browse_button'], command=self._browse_input).grid(row=0, column=2, sticky="w") tk.Label(self.single_mode_frame, text=self.i18n['json_file_label']).grid(row=1, column=0, sticky="w", pady=2) json_entry = tk.Entry(self.single_mode_frame, textvariable=self.json_path, state="readonly") json_entry.grid(row=1, column=1, sticky="ew", padx=5) - json_entry.drop_target_register(DND_FILES); json_entry.dnd_bind('<>', lambda e: self._on_drop(e, self.json_path)) + if _HAS_DND: + json_entry.drop_target_register(DND_FILES); json_entry.dnd_bind('<>', lambda e: self._on_drop(e, self.json_path)) json_buttons_frame = tk.Frame(self.single_mode_frame) json_buttons_frame.grid(row=1, column=2, sticky="w") tk.Button(json_buttons_frame, text=self.i18n['browse_button'], command=self._browse_json).pack(side=tk.LEFT) @@ -600,11 +623,23 @@ def _open_output_folder(self): if not output_file: messagebox.showinfo(self.i18n['info_title'], self.i18n['info_no_output']); return output_dir = os.path.dirname(output_file) - if os.path.exists(output_dir): os.startfile(output_dir) + if os.path.exists(output_dir): + if platform.system() == "Darwin": + subprocess.Popen(["open", output_dir]) + elif platform.system() == "Windows": + os.startfile(output_dir) + else: + subprocess.Popen(["xdg-open", output_dir]) else: messagebox.showerror(self.i18n['error_title'], self.i18n['error_dir_not_found'].format(output_dir)) def _open_debug_folder(self): - if os.path.exists(self.debug_folder_path): os.startfile(self.debug_folder_path) + if os.path.exists(self.debug_folder_path): + if platform.system() == "Darwin": + subprocess.Popen(["open", self.debug_folder_path]) + elif platform.system() == "Windows": + os.startfile(self.debug_folder_path) + else: + subprocess.Popen(["xdg-open", self.debug_folder_path]) else: messagebox.showinfo(self.i18n['info_title'], self.i18n['info_debug_not_found']) def _set_default_output_path(self, in_path): From 049c281c3d8c4f4886e53bd03304c8d8db5ac6db Mon Sep 17 00:00:00 2001 From: Long Chen Date: Tue, 21 Apr 2026 15:13:17 +0800 Subject: [PATCH 2/2] docs: add macOS setup instructions to README --- README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/README.md b/README.md index 643ec06..6e09e10 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,40 @@ The application also supports converting multiple files at once in Batch Mode. 3. **Manage Tasks**: You can add multiple tasks to the list. If you need to remove a task, select it from the list and click "Delete Task". 4. **Start Batch Conversion**: Once all your tasks are added, click "Start Batch Conversion". The application will process each task sequentially. A log will show the progress for each file. +## For macOS Users: Running from Source + +Since there is no pre-built macOS release, you can run the application from source. Python 3.10–3.12 is required (PaddlePaddle does not yet support 3.13+). + +1. **Install Python 3.12 and tkinter** (via [Homebrew](https://brew.sh)): + ```bash + brew install python@3.12 python-tk@3.12 + ``` + +2. **Clone the repository**: + ```bash + git clone https://github.com/JuniverseCoder/MinerU2PPT.git + cd MinerU2PPT + ``` + +3. **Create a virtual environment and install dependencies**: + ```bash + /opt/homebrew/bin/python3.12 -m venv venv + source venv/bin/activate + pip install -r requirements-cpu.txt + ``` + +4. **Run the application**: + - **GUI**: + ```bash + python gui.py + ``` + - **CLI**: + ```bash + python main.py --json --input --output + ``` + +> **Note:** Drag-and-drop may not work on macOS with Tcl/Tk 9.x (the default from Homebrew). Use the "Browse..." buttons instead. + ## For Developers This section provides instructions for running the application from source and packaging it for distribution.