diff --git a/build/scripts/build_windows.py b/build/scripts/build_windows.py
index 42d68fa..67b960e 100644
--- a/build/scripts/build_windows.py
+++ b/build/scripts/build_windows.py
@@ -243,10 +243,6 @@ class WindowsBuilder:
if not self._create_wix_source():
return False
- # Ensure toolbar icons are present in bundled resources before harvesting.
- if not self._ensure_toolbar_icons_in_bundle():
- return False
-
# Harvest application files using Heat
print(f" Harvesting application files...")
dist_folder = self.dist_dir / "WebDropBridge"
@@ -356,36 +352,6 @@ class WindowsBuilder:
return True
- def _ensure_toolbar_icons_in_bundle(self) -> bool:
- """Ensure toolbar icon files exist in the bundled app folder.
-
- This guarantees WiX Heat harvest includes these icons in the MSI,
- even if a previous PyInstaller run omitted them.
- """
- src_icons_dir = self.project_root / "resources" / "icons"
- bundle_icons_dir = self.dist_dir / "WebDropBridge" / "_internal" / "resources" / "icons"
- required_icons = ["home.ico", "reload.ico", "open.ico", "openwith.ico"]
-
- try:
- bundle_icons_dir.mkdir(parents=True, exist_ok=True)
-
- for icon_name in required_icons:
- src = src_icons_dir / icon_name
- dst = bundle_icons_dir / icon_name
-
- if not src.exists():
- print(f"❌ Required icon not found: {src}")
- return False
-
- if not dst.exists() or src.stat().st_mtime > dst.stat().st_mtime:
- shutil.copy2(src, dst)
- print(f" ✓ Ensured toolbar icon in bundle: {icon_name}")
-
- return True
- except Exception as e:
- print(f"❌ Failed to ensure toolbar icons in bundle: {e}")
- return False
-
def _create_wix_source(self) -> bool:
"""Create WiX source file for MSI generation.
diff --git a/resources/icons/home.ico b/resources/icons/home.ico
deleted file mode 100644
index e080469..0000000
Binary files a/resources/icons/home.ico and /dev/null differ
diff --git a/resources/icons/open.ico b/resources/icons/open.ico
deleted file mode 100644
index 1b6fed8..0000000
Binary files a/resources/icons/open.ico and /dev/null differ
diff --git a/resources/icons/openwith.ico b/resources/icons/openwith.ico
deleted file mode 100644
index 3facabc..0000000
Binary files a/resources/icons/openwith.ico and /dev/null differ
diff --git a/resources/icons/reload.ico b/resources/icons/reload.ico
deleted file mode 100644
index 1f1023d..0000000
Binary files a/resources/icons/reload.ico and /dev/null differ
diff --git a/src/webdrop_bridge/ui/main_window.py b/src/webdrop_bridge/ui/main_window.py
index 2bfc78e..c6db37e 100644
--- a/src/webdrop_bridge/ui/main_window.py
+++ b/src/webdrop_bridge/ui/main_window.py
@@ -4,7 +4,6 @@ import asyncio
import json
import logging
import re
-import subprocess
import sys
from datetime import datetime
from pathlib import Path
@@ -251,12 +250,6 @@ class OpenDropZone(QWidget):
self.setMinimumSize(QSize(44, 44))
self.setMaximumSize(QSize(48, 48))
- def set_icon(self, icon: QIcon) -> None:
- """Set the displayed icon for the drop zone widget."""
- if icon.isNull():
- return
- self._icon_label.setPixmap(icon.pixmap(QSize(32, 32)))
-
# ------------------------------------------------------------------
# Drop handling
# ------------------------------------------------------------------
@@ -302,46 +295,6 @@ class OpenDropZone(QWidget):
logger.debug(f"OpenDropZone: skipping non-local URL {url.toString()}")
-class OpenWithDropZone(OpenDropZone):
- """Drop target widget that opens files via an app chooser dialog.
-
- When a file is dropped, this widget emits ``file_open_with_requested`` for
- each local file so the main window can invoke platform-specific
- "Open With" behavior.
-
- Signals:
- file_open_with_requested (str): Emitted with the local file path.
- """
-
- file_open_with_requested = Signal(str)
-
- def __init__(self, parent: Optional[QWidget] = None) -> None:
- """Initialize the OpenWithDropZone widget.
-
- Args:
- parent: Parent widget.
- """
- super().__init__(parent)
- self._icon_label.setToolTip("Drop a file here to choose which app should open it")
-
- def dropEvent(self, event) -> None: # type: ignore[override]
- """Emit dropped local files for app-chooser handling."""
- self._icon_label.setStyleSheet(self._NORMAL_STYLE)
- mime = event.mimeData()
- if not mime.hasUrls():
- event.ignore()
- return
-
- event.acceptProposedAction()
- for url in mime.urls():
- if url.isLocalFile():
- file_path = url.toLocalFile()
- logger.info(f"OpenWithDropZone: request app chooser for '{file_path}'")
- self.file_open_with_requested.emit(file_path)
- else:
- logger.debug(f"OpenWithDropZone: skipping non-local URL {url.toString()}")
-
-
class _DragBridge(QObject):
"""JavaScript bridge for drag operations via QWebChannel.
@@ -1118,78 +1071,6 @@ class MainWindow(QMainWindow):
f"File: {file_path}\nError: {error}",
)
- def _on_file_open_with_requested(self, file_path: str) -> None:
- """Handle a file dropped on the OpenWithDropZone.
-
- Args:
- file_path: Local file path to open using an app chooser.
- """
- if self._open_with_app_chooser(file_path):
- self.statusBar().showMessage(f"Choose app for: {Path(file_path).name}", 4000)
- logger.info(f"Opened app chooser for '{file_path}'")
- return
-
- logger.warning(f"Could not open app chooser for '{file_path}'")
- QMessageBox.warning(
- self,
- "Open With Error",
- "Could not open an application chooser for this file on your platform.",
- )
-
- def _open_with_app_chooser(self, file_path: str) -> bool:
- """Open OS-specific app chooser for a local file.
-
- Args:
- file_path: Local file path.
-
- Returns:
- True if the chooser command was started successfully, False otherwise.
- """
- try:
- normalized_path = str(Path(file_path))
- if not Path(normalized_path).exists():
- logger.warning(f"Open-with target does not exist: {normalized_path}")
- return False
-
- if sys.platform.startswith("win"):
- # First try the native shell "openas" verb.
- import ctypes
-
- result = ctypes.windll.shell32.ShellExecuteW(
- None, "openas", normalized_path, None, None, 1
- )
- if result > 32:
- return True
-
- logger.warning(f"ShellExecuteW(openas) failed with code {result}; trying fallback")
-
- # Fallback for systems where openas verb is not available/reliable.
- subprocess.Popen(["rundll32.exe", "shell32.dll,OpenAs_RunDLL", normalized_path])
- return True
-
- if sys.platform == "darwin":
- # Prompt for an app and open the file with the selected app.
- script = (
- "on run argv\n"
- "set targetFile to POSIX file (item 1 of argv)\n"
- "set chosenApp to choose application\n"
- 'tell application "Finder" to open targetFile using chosenApp\n'
- "end run"
- )
- result = subprocess.run(
- ["osascript", "-e", script, file_path],
- check=False,
- capture_output=True,
- text=True,
- )
- return result.returncode == 0
-
- logger.warning(f"Open-with chooser not implemented for platform: {sys.platform}")
- return False
- except Exception as e:
- logger.warning(f"Failed to open app chooser for '{file_path}': {e}")
- return False
-
def _on_download_requested(self, download: QWebEngineDownloadRequest) -> None:
"""Handle download requests from the embedded web view.
@@ -1382,52 +1263,25 @@ class MainWindow(QMainWindow):
# Separator
toolbar.addSeparator()
- if hasattr(sys, "_MEIPASS"):
- icons_dir = Path(sys._MEIPASS) / "resources" / "icons" # type: ignore[attr-defined]
- else:
- icons_dir = Path(__file__).parent.parent.parent.parent / "resources" / "icons"
-
# Home button
- home_icon_path = icons_dir / "home.ico"
- home_icon = (
- QIcon(str(home_icon_path))
- if home_icon_path.exists()
- else self.style().standardIcon(self.style().StandardPixmap.SP_DirHomeIcon)
+ home_action = toolbar.addAction(
+ self.style().standardIcon(self.style().StandardPixmap.SP_DirHomeIcon), ""
)
- home_action = toolbar.addAction(home_icon, "")
home_action.setToolTip("Home")
home_action.triggered.connect(self._navigate_home)
# Refresh button
refresh_action = self.web_view.pageAction(self.web_view.page().WebAction.Reload)
- reload_icon_path = icons_dir / "reload.ico"
- if reload_icon_path.exists():
- refresh_action.setIcon(QIcon(str(reload_icon_path)))
toolbar.addAction(refresh_action)
# Open-with-default-app drop zone (right of Reload)
self._open_drop_zone = OpenDropZone()
- open_icon_path = icons_dir / "open.ico"
- if open_icon_path.exists():
- self._open_drop_zone.set_icon(QIcon(str(open_icon_path)))
self._open_drop_zone.file_opened.connect(self._on_file_opened_via_drop)
self._open_drop_zone.file_open_failed.connect(self._on_file_open_failed_via_drop)
open_drop_action = QWidgetAction(toolbar)
open_drop_action.setDefaultWidget(self._open_drop_zone)
toolbar.addAction(open_drop_action)
- # Open-with chooser drop zone (right of Open-with-default-app)
- self._open_with_drop_zone = OpenWithDropZone()
- open_with_icon_path = icons_dir / "openwith.ico"
- if open_with_icon_path.exists():
- self._open_with_drop_zone.set_icon(QIcon(str(open_with_icon_path)))
- self._open_with_drop_zone.file_open_with_requested.connect(
- self._on_file_open_with_requested
- )
- open_with_drop_action = QWidgetAction(toolbar)
- open_with_drop_action.setDefaultWidget(self._open_with_drop_zone)
- toolbar.addAction(open_with_drop_action)
-
# Add stretch spacer to push help buttons to the right
spacer = QWidget()
spacer.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
@@ -1613,10 +1467,6 @@ class MainWindow(QMainWindow):
f"Bridges web-based drag-and-drop workflows with native file operations "
f"for professional desktop applications.
"
f"
"
- f"Toolbar Drop Zones:
"
- f"Open icon: Opens dropped files with the system default app.
"
- f"Open-with icon: Shows an app chooser for dropped files.
"
- f"
"
f"Product of:
"
f"hörl Information Management GmbH
"
f"Silberburgstraße 126
"
diff --git a/tests/unit/test_main_window.py b/tests/unit/test_main_window.py
index 161be8a..01eaa5a 100644
--- a/tests/unit/test_main_window.py
+++ b/tests/unit/test_main_window.py
@@ -149,74 +149,3 @@ class TestMainWindowURLWhitelist:
# web_view should have allowed_urls configured
assert window.web_view.allowed_urls == sample_config.allowed_urls
-
-
-class TestMainWindowOpenWith:
- """Test Open With chooser behavior."""
-
- def test_open_with_app_chooser_windows(self, qtbot, sample_config):
- """Windows should use ShellExecuteW with the openas verb."""
- window = MainWindow(sample_config)
- qtbot.addWidget(window)
-
- test_file = sample_config.allowed_roots[0] / "open_with_test.txt"
- test_file.write_text("test")
-
- with patch("webdrop_bridge.ui.main_window.sys.platform", "win32"):
- with patch("ctypes.windll.shell32.ShellExecuteW", return_value=33) as mock_shell:
- assert window._open_with_app_chooser(str(test_file)) is True
- mock_shell.assert_called_once_with(
- None,
- "openas",
- str(test_file),
- None,
- None,
- 1,
- )
-
- def test_open_with_app_chooser_windows_shellexecute_failure(self, qtbot, sample_config):
- """Windows should fall back to OpenAs_RunDLL when ShellExecuteW fails."""
- window = MainWindow(sample_config)
- qtbot.addWidget(window)
-
- test_file = sample_config.allowed_roots[0] / "open_with_fallback.txt"
- test_file.write_text("test")
-
- with patch("webdrop_bridge.ui.main_window.sys.platform", "win32"):
- with patch("ctypes.windll.shell32.ShellExecuteW", return_value=31):
- with patch("webdrop_bridge.ui.main_window.subprocess.Popen") as mock_popen:
- assert window._open_with_app_chooser(str(test_file)) is True
- mock_popen.assert_called_once_with(
- ["rundll32.exe", "shell32.dll,OpenAs_RunDLL", str(test_file)]
- )
-
- def test_open_with_app_chooser_missing_file(self, qtbot, sample_config):
- """Missing files should fail before platform-specific invocation."""
- window = MainWindow(sample_config)
- qtbot.addWidget(window)
-
- with patch("webdrop_bridge.ui.main_window.sys.platform", "win32"):
- assert window._open_with_app_chooser("C:/tmp/does_not_exist.txt") is False
-
- def test_open_with_app_chooser_macos_success(self, qtbot, sample_config):
- """macOS should return True when osascript exits successfully."""
- window = MainWindow(sample_config)
- qtbot.addWidget(window)
-
- test_file = sample_config.allowed_roots[0] / "open_with_macos.txt"
- test_file.write_text("test")
-
- class _Result:
- returncode = 0
-
- with patch("webdrop_bridge.ui.main_window.sys.platform", "darwin"):
- with patch("webdrop_bridge.ui.main_window.subprocess.run", return_value=_Result()):
- assert window._open_with_app_chooser(str(test_file)) is True
-
- def test_open_with_app_chooser_unsupported_platform(self, qtbot, sample_config):
- """Unsupported platforms should return False."""
- window = MainWindow(sample_config)
- qtbot.addWidget(window)
-
- with patch("webdrop_bridge.ui.main_window.sys.platform", "linux"):
- assert window._open_with_app_chooser("/tmp/test.txt") is False