diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b35c2d..5bf8e46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,221 @@ # Changelog +## 1.6.0.0 Stable - 2026-07-14 + +- Updates the integrated German language resources from the corrected language file so the Options dialog labels line up with their intended controls again. +- Ships the restored native/managed stable baseline with the initial Explorer middle-click fix as the next stable release. + +## 1.5.99.0 Stable - 2026-07-14 + +- Initializes the active Explorer folder view on first attach instead of only after a previous view existed, allowing File/Folder middle-click to resolve items immediately on the initial "This PC" page. + +## 1.5.98.0 Stable - 2026-07-14 + +- Restores synchronous AutoLoader activation for the first Explorer instance and only records ActivationDate after ShowBrowserBar succeeds, preventing a missed timer from leaving the initial window without a real QTTabBar host. + +## 1.5.97.0 Stable - 2026-07-14 + +- Falls back to the full Explorer window when the active ShellTabWindowClass has not exposed its initial SHELLDLL_DefView yet, matching the working upstream ListView discovery path more closely. +- Makes main ListView event registration idempotent during recapture so middle-click handlers cannot stack up or remain stale after view changes. + +## 1.5.96.0 Stable - 2026-07-13 + +- Reads AutoHookWindow directly from the registry during hook startup so the first Explorer instance cannot fall back to background-renderer-only mode before config binding finishes. +- Captures the initial item view from the active ShellTabWindowClass container first, improving File/Folder middle-click handling on the initial "This PC" view. + +## 1.5.95.0 Stable - 2026-07-13 + +- Keeps the full Explorer hook active when AutoHookWindow is enabled, even if the Explorer background renderer is also enabled. +- Prevents the background-renderer-only mode from suppressing ShellBrowser hooks needed by file and folder middle-click handling. + +## 1.5.94.0 Stable - 2026-07-13 + +- Treats access-denied Shell Link target resolution as a recoverable condition, so protected or unreadable links no longer get misclassified as dead folder links. +- Falls back to the original shell item when a link target cannot be resolved instead of creating an empty PIDL wrapper. + +## 1.5.93.0 Stable - 2026-07-13 + +- Reverts the 1.5.92 full-hook/background-mode experiment because it did not improve File/Folder middle-click capture and could add startup side effects. +- Restores the upstream niklas2233/v1.5.7 item-view middle-click dispatch semantics while keeping the folder-tree middle-click guard. + +## 1.5.92.0 Stable - 2026-07-13 + +- Restores the upstream native-tab ListView lookup path so the active Explorer file view is captured immediately after startup. +- Loads the full hook library when AutoHookWindow is enabled instead of staying in background-renderer-only mode. + +## 1.5.91.0 Stable - 2026-07-13 + +- Ports the robust native-tab ListView monitor from niklas2233/qttabbar v1.5.7 so Explorer item-view middle-clicks are handled by the direct view subclass immediately after startup. +- Removes the 1.5.90 startup recapture timer and global FolderView middle-click fallback that could block Explorer. + +## 1.5.90.0 Stable - 2026-07-13 + +- Recaptures Explorer's item view shortly after startup and lazily before FolderView middle-click handling, so File/Folder middle-click no longer depends on first using the folder tree. + +## 1.5.89.0 Stable - 2026-07-13 + +- Prevents stale serialized cross-process actions from blocking Explorer startup or flooding the exception log. +- Updates the internal QTTabBar version constant used by diagnostic logs. + +## 1.5.88.0 Stable - 2026-07-13 + +- Fixes initial File/Folder middle-click handling by routing Explorer FolderView middle clicks before the TreeView fallback and limiting TreeView hit tests to real tree targets. + +## 1.5.87.0 Stable - 2026-07-13 + +- Fixes File/Folder middle-click on DirectUI item views by honoring the configured mouse item action, accepting child-window hit targets, and falling back to Explorer's focused item when the hit test cannot resolve the clicked item. + +## 1.5.86.0 Stable - 2026-07-13 + +- Fixes File/Folder middle-click actions in modern Explorer item views by consuming handled middle clicks and accepting DirectUI child-window hits. +- Reduces the first-start Versatile Bar white-panel flash by passing the fixed vertical bar size to Explorer when the bar is auto-shown. + +## 1.5.85.0 Stable - 2026-07-10 + +- Fixes F2 rename-selection cycling so the tweak is enabled when checked and disabled when unchecked. +- Applies the same F2 rename-selection cycling behavior to the Desktop list view. + +## 1.5.84.0 Stable - 2026-07-10 + +- Restores cursor-loop arrow-key selection when Explorer does not report a focused item yet. +- Splits tab text shadows into active, inactive, and mouseover state toggles. +- Clarifies the automatic tab text color override option in Appearance. +- Fixes the inverted "Cycle selection with F2 while renaming" tweak in the Explorer tab view. + +## 1.5.83.0 Preview - 2026-07-06 + +- Starts Versatile Bar reordering directly from each icon or separator while the left mouse button is held. +- Uses the live Windows mouse-button state so ToolStrip item event routing cannot suppress the drag gesture. + +## 1.5.82.0 Preview - 2026-07-05 + +- Adds drag-and-drop reordering for icons and separators on the vertical Versatile Bar. +- Shows a precise insertion marker while dragging and persists the customized item order. +- Lets externally dropped files and folders be inserted at a chosen position instead of always appending them. + +## 1.5.81.0 Preview - 2026-07-05 + +- Adds configurable custom images and X/Y offsets for the tab close button and locked-tab icon. +- Supports a single close-button image or horizontal/vertical four-state image strips (normal, hover, pressed, alternate). +- Scales oversized images to the available tab height, supports PNG alpha and BMP magenta transparency, and safely falls back to built-in resources. + +## 1.5.80.0 Preview - 2026-07-05 + +- Implements tab-skin content margins for text, folder icons, lock glyphs, and close buttons. +- Implements configurable tab overlap in single-row and multi-row layouts with matching draw-order hit testing. +- Implements alpha-aware hit testing against the rendered nine-slice tab skin and fixes overlap-value validation. + +## 1.5.79.0 Stable - 2026-07-04 + +- Promotes the exact-window `BeginPaint`/`FillRect` Explorer background renderer to production. +- Removes periodic managed diagnostics, native paint counters, obsolete draw exports, and diagnostic build markers. +- Retains strict `DirectUIHWND` validation, safe hook rollback, crash reporting, and operational error logs. + +## 1.5.78.0 Diagnostic - 2026-07-04 + +- Replaces the overwritten `WM_ERASEBKGND` draw with process API detours matching the proven ExplorerBgTool paint order. +- Draws immediately after DirectUI's own `FillRect`, before folders, files, and labels are painted. +- Restricts rendering to HDCs resolved to exact registered `DirectUIHWND` instances and fully rolls back partial hook installation. + +## 1.5.77.0 Diagnostic - 2026-07-04 + +- Moves exact-window background rendering from the post-paint overlay to `WM_ERASEBKGND`. +- Draws after the default background erase and before Explorer paints folders, files, and labels. +- Uses the message-provided HDC, removes the post-paint redraw loop, and keeps all IAT hooks disabled. + +## 1.5.76.0 Diagnostic - 2026-07-04 + +- Fixes exact-window registration for the hook-free after-paint renderer by checking the initialized renderer state rather than the obsolete installed-hook state. +- Preserves the 1.5.75 exact `DirectUIHWND` rendering path with all background IAT hooks disabled. + +## 1.5.75.0 Diagnostic - 2026-07-03 + +- Replaces background-mode IAT interception with exact-window rendering dispatched by QTTabBar's existing `DirectUIHWND` after-paint message. +- Uses the WinSpy-confirmed registered file-list window and disables all scoped background IAT hooks. +- Adds after-paint render telemetry while retaining the WIC image loader and strict window validation. + +## 1.5.74.0 Diagnostic - 2026-07-03 + +- Extends the isolated background renderer to scoped `FillRect` and `CreateCompatibleDC` imports in `shell32` and `ExplorerFrame`. +- Adds per-module paint telemetry plus image/paint overlap and clip-region diagnostics. +- Retains exact registered-window filtering and avoids process-wide API hooks. + +## 1.5.73.0 Diagnostic - 2026-07-03 + +- Replaced the non-windowed `dui70!GetDC(NULL)` diagnostic path with scoped `shell32!BeginPaint` and `shell32!EndPaint` import hooks. +- Mirrors the reference renderer's DirectUI paint-DC association while retaining exact registered-window filtering. + +## 1.5.72.0 Diagnostic - 2026-07-03 + +- Replaced the unrelated `ExplorerFrame!BeginPaint` import hook with scoped `dui70!GetDC` and `dui70!ReleaseDC` import hooks. +- Associates only exact registered `DirectUIHWND` DCs with the background renderer. +- Retains delayed telemetry to verify DC resolution and alpha blending in the target Windows 10 VM. + +## 1.5.71.0 Diagnostic - 2026-07-03 + +- Added read-only native telemetry for the isolated Explorer background renderer. +- Logs delayed snapshots for `BeginPaint`, compatible DC propagation, `FillRect`, and `AlphaBlend` without changing hook targets or rendering behavior. +- Keeps 1.5.70.0 Stable as the rollback baseline. + +## 1.5.70.0 Stable - 2026-07-03 + +- Adds a scoped `ExplorerFrame.dll` `BeginPaint` import patch to capture the real + DirectUI paint DC without installing a process-wide hook +- Maps the captured paint DC to the already registered `DirectUIHWND` +- Keeps `FillRect` and `CreateCompatibleDC` patches limited to `dui70.dll` +- Retains the stable background-only state separation from 1.5.69 + +## 1.5.69.0 Stable - 2026-07-03 + +- Separates isolated background-renderer state from the legacy QTHookLib state +- Skips the invalid ShellBrowser-hook call in background-only mode +- Removes the additional `dui70.dll` `DeleteDC` patch not used by the reference implementation +- Replaces DeleteDC tracking with bounded, overwrite-on-create compatible-DC mapping + +## 1.5.68.0 Stable - 2026-07-03 + +- Removed `std::random_device` from Explorer background renderer construction +- Uses a deterministic renderer seed so startup does not enter platform entropy code +- Added phase diagnostics for singleton creation, configuration, image enumeration, + WIC decode, DIB creation, pixel copy, and state commit +- Logs the exact native renderer phase after both success and guarded failure + +## 1.5.67.0 Stable - 2026-07-03 + +- Replaced the background renderer's GDI+ decoder with Windows Imaging Component +- Removed `GdiplusStartup`, raw `LockBits` access and manual source-stride traversal +- Decodes PNG/BMP/JPEG directly into validated premultiplied `32bppPBGRA` DIB surfaces +- Addresses the renderer-stage access violation observed on Windows 10 22H2 build 19045.5679 +- Retains phased renderer/hook diagnostics and guarded `dui70.dll` activation + +## 1.5.66.0 Stable - 2026-07-03 + +- Hardened `dui70.dll` PE/import parsing for later Windows 10 22H2 servicing builds +- Bounded every descriptor, thunk and import-name read to the loaded module image +- Guarded native IAT reads and writes against structured exceptions +- Split renderer initialization from IAT activation for precise managed diagnostics +- Added native export guards so unsupported layouts return an error instead of terminating Explorer + +## 1.5.65.0 Stable - 2026-07-03 + +- Moved Explorer background ownership into the already loaded `QTTabBarNative.dll` +- Stopped loading the legacy monolithic `QTHookLib` in background-only mode +- Removed the background feature's file dependency on `QTHookLib32/64.dll` +- Preserved the three module-scoped `dui70.dll` import hooks and safe image renderer +- Added an architecture smoke test proving initialization succeeds with no legacy hook DLL + +## 1.5.64.0 Stable - 2026-07-03 + +- Replaced the legacy Explorer background path with an encapsulated renderer +- Removed MinHook entirely from background-only initialization +- Limited interception to three `dui70.dll` import slots and registered `DirectUIHWND` views +- Added validated immutable image snapshots and safe live configuration reloads +- Added correct premultiplied PNG alpha rendering and HDC lifecycle tracking +- Added window-property tokens to prevent reused HWND values from matching stale state +- Added configurable image directories, random selection and path-specific images +- Added ESC startup recovery while keeping legacy Shell hooks disabled by default +- Preserved native HRESULT diagnostics across the managed bridge + ## 1.5.62.0 Stable - 2026-07-02 - Consolidated the native and .NET Framework 4.8 QTTabBar code lines @@ -16,4 +232,3 @@ - Added isolated Explorer background rendering from `config.ini` - Improved Win+E capture, tab focus and toolbar persistence - Removed release debug paths and reduced success-log noise - diff --git a/CREDITS.md b/CREDITS.md index 90e38c0..ff189c7 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -14,10 +14,10 @@ QTTabBar Native Enhanced combines and extends work from several generations of Q - **Akay Devel Coder** - project owner, product direction, integration decisions, visual design and extensive Windows/VM testing - **OpenAI Codex** - major integration, debugging, native/managed implementation and release-engineering assistance under the project owner's direction +- **Maplespe / ExplorerBgTool** - inspiration for configurable and path-specific Explorer background images; this project uses an independently reworked renderer integrated with QTTabBar's targeted window registration The attribution list is intentionally open for correction and expansion as additional upstream history is identified. ## Third-party components This repository contains or references third-party components including MinHook, WiX build definitions and NotifyPropertyWeaver. Their original license and attribution terms continue to apply. - diff --git a/I18N/Lng_QTTabBar_de.xml b/I18N/Lng_QTTabBar_de.xml index ca6b85e..bda1828 100644 --- a/I18N/Lng_QTTabBar_de.xml +++ b/I18N/Lng_QTTabBar_de.xml @@ -2,60 +2,68 @@ -akay.dev +leveltrauma - -de_DE - - -Deutschland +Germany -DevBuild: 12.01.2021 09:17:40 +1.6.0.1 -05.05.2021 +07.14.2026 + +DevBuild: 2022/8/15 19:01:42 (2.0.50727.9164) + + + +1.0 + + + +08/29/2022 + + Trennzeichen -Zurück -Vorwärts +Zurückgehen +Vorwärtsgehen Gruppen -Zuletzt geschlossen -Programmstarter +Kürzlich geschlossen +Anwendungen Neues Fenster -Dupliziere aktive Registerkarte +Dies klonen Sperren -Kopier-Werkzeug -Immer im Vordergrund -Tabulator schließen -Alle schließen, außer den aktuellen Tab +Kopierwerkzeug +Oberste +Schließen +Alles außer dem aktuellen schließen Fenster schließen -Linken Tab schließen -Rechten Tab schließen -Aufwärts -Aktualisieren +Links schließen +Rechts schließen +Eine Ebene höher +Neu laden Suchen Deckkraft -Suchbox +Suchfeld Optionen Pfad Name -Aktueller Ordnerp -Aktueller Ordnern -MD5-Prüfsumme - Artikel -Pfade aller Tabs +Aktueller Ordnerpfad +Aktueller Ordnername +MD5 + Elemente +Pfade aller geöffneten Registerkarten @@ -68,10 +76,10 @@ Standard OK Abbrechen -&Anwenden +&Übernehmen &Standard -&Seite Zurücksetzen -&Alle zurücksetzen +Zurücksetzen &Seite +&Alles Zurücksetzen Zurücksetzen Hinzufügen Hinzufügen... @@ -91,21 +99,20 @@ Verknüpfung erstellen in: -Operation failed.\r\nBitte stellen Sie sicher, dass die Datei oder das Ziel der Verknüpfung existiert:\r\n\r\n{0} +Operation fehlgeschlagen.\r\nBitte stellen Sie sicher, dass die Datei oder das Ziel der Verknüpfung existiert:\r\n\r\n{0} Fehler - QTTabBar -Warnung: QTTabBar konnte nicht automatisch aktiviert werden. Um sie manuell zu aktivieren, drücken Sie die Alt-Taste und wählen Sie dann QTTabBar aus dem Menü Ansicht/Symbolleisten. -Konnte nicht aktivieren +Warnung: Die QTTabBar konnte nicht automatisch aktiviert werden. Um sie manuell zu aktivieren, drücken Sie die Alt-Taste, lassen Sie sie los und wählen Sie QTTabBar aus dem Menü Ansicht/Symbolleisten. +Konnte nicht aktiviert werden Fehler -Die QTTabBar-Hook-Bibliothek kann nicht geladen werden. -Die QTTabBar-Hook-Bibliothek konnte nicht initialisiert werden. -Einige Funktionen sind möglicherweise nicht funktionsfähig. Um dies zu beheben, müssen Sie die Anwendung möglicherweise neu installieren. -Hooking des Desktops fehlgeschlagen. Bitte aktivieren Sie das Werkzeug QT Tab Desktop erneut. -Der Vorgang ist fehlgeschlagen.\r\nBitte stellen Sie sicher, dass der Ordner existiert oder Sie -Zugriffsberechtigung für den Ordner haben:\r\n\r\n\t{0} +QTTabBar-Hook-Bibliothek konnte nicht geladen werden. +QTTabBar-Hook-Bibliothek konnte nicht initialisiert werden. +Einige Funktionen sind möglicherweise nicht funktionsfähig. Um dies zu beheben, müssen Sie möglicherweise die Anwendung neu installieren. +Hooking des Desktops fehlgeschlagen. Bitte aktivieren Sie das QT Tab Desktop-Tool erneut. +Der Vorgang ist fehlgeschlagen.\r\nBitte stellen Sie sicher, dass der Ordner existiert oder dass Sie die Berechtigung haben, darauf zuzugreifen:\r\n\r\n\t{0} -Bild-Dateien +Bilddateien Sprachdateien Plugin-Dateien @@ -114,28 +121,28 @@ Plugin-Dateien Pfad Hash Schließen -Klar +Löschen Aktualisieren -Immer an der Spitze +Immer im Vordergrund Ergebnis anzeigen Löschen beim Schließen Vollständiger Pfad -(Jetzt wird gerechnet...) +(Jetzt berechnen...) Alle Links -Top +Oben Rechts Unten -Desktop-Suche +Desktop Suche Bilddatei ist ungültig.\r\n\r\nBitte geben Sie eine 432 x 40 px große Bilddatei an. Suchergebnisse in -Abkürzung +Verknüpfung Wählen Sie Ihre Datei Wählen Sie Ihren Ordner Experimentell @@ -143,196 +150,199 @@ Dummy -Ändern Sie das Verhalten von Explorer-Fenstern. -Einzelfenster-Modus +Ändern Sie das Verhalten von Explorer-Fenstern +Einzelfenstermodus Erfassen neuer Explorer-Fenster als neue Registerkarten Neue Explorer-Fenster nicht erfassen Standardspeicherort für neue Fenster und Registerkarten -Speichern der Sitzung +Sitzungsspeicherung Starten Sie jedes Mal eine neue Sitzung -Alle Registerkarten der vorherigen Sitzung wiederherstellen -Nur die gesperrten Registerkarten der vorherigen Sitzung wiederherstellen -Wenn die Schaltfläche "Schließen" angeklickt wird +Wiederherstellen aller Registerkarten aus der vorherigen Sitzung +Wiederherstellen nur der gesperrten Registerkarten aus der vorherigen Sitzung +Wenn die Schließen-Schaltfläche angeklickt wird Schließen Sie das Fenster -Schließen der aktuellen Registerkarte -Alle nicht gesperrten Registerkarten schließen -Senden Sie das Fenster in die Taskleiste auf +Schließen Sie die aktuelle Registerkarte +Schließen Sie alle nicht gesperrten Registerkarten +Senden Sie das Fenster in den Systemtray Schließen Minimieren -Blaue Plus-Taste +Plus-Schaltfläche -Ändern Sie die Optionen in Bezug auf die Registerkarten. +Ändern Sie die Optionen für die Registerkarten. Neue Registerkarten öffnen: -Wenn die aktuelle Registerkarte geschlossen ist, wechseln Sie in die: -Sofortiges Umschalten auf neue Registerkarten -Vorhandene Registerkarten wiederverwenden -Mehrdeutige Registerkarten umbenennen -Wenn Sie mit dem Mauszeiger über eine Registerkarte ziehen, -Wechseln Sie dazu -Anzeigen des Menüs Unterordner-Tipp -Registerkarten-Symbole -Ordnersymbole auf Registerkarten anzeigen -Das Menü "Unterordner-Tipp" anzeigen, wenn auf das Symbol geklickt wird -Den Laufwerksbuchstaben auf dem Symbol anzeigen -Schließen-Taste -Schließen-Schaltfläche auf Registerkarten anzeigen +Wenn die aktuelle Registerkarte geschlossen wird, wechseln Sie zu der Registerkarte: +Sofort zu neuen Tabs wechseln +Vorhandene Tabs wiederverwenden +Mehrdeutige Tabs umbenennen +Beim Ziehen über einen Tab, +zu diesem wechseln +Menü Unterordner-Inhalt anzeigen +Tab-Symbole +Ordnersymbole auf Tabs anzeigen +Menü Unterordner-Inhalt anzeigen, wenn auf das Symbol geklickt wird +Laufwerksbuchstabe auf dem Symbol anzeigen +Schließen-Schaltfläche +Schließen-Schaltfläche auf Tabs anzeigen Nur bei gedrückter Alt-Taste -Nur bei Mausverschiebung -Registerkarte Bar -Navigationsschaltflächen in der Registerkartenleiste anzeigen -Auf der linken Seite -Auf der rechten Seite +Nur bei Mausbewegung +Registerkartenleiste +Navigationsschaltflächen auf der Registerkartenleiste anzeigen +Links +Rechts Mehrere Reihen von Registerkarten zulassen -Legen Sie die Zeile mit der aktiven Registerkarte immer unten an +Die Reihe mit der aktiven Registerkarte immer unten anordnen Links von der aktuellen Registerkarte Rechts von der aktuellen Registerkarte -In der Position ganz links -In der Position ganz rechts -Reiter nach links -Reiter nach rechts -Registerkarte ganz links -Registerkarte ganz rechts +Ganz links +Ganz rechts +Registerkarte nach links +Registerkarte nach rechts +Ganz links +Ganz rechts Zuletzt aktivierte Registerkarte Verhalten -Aktivieren oder Deaktivieren von Anpassungen der Listenansicht des Explorers. +Aktivieren oder Deaktivieren von Tweaks für die Listenansicht des Explorers. Tweaks, die mit beiden Listenansichten verwendet werden können Anzeige von Spaltenüberschriften in allen Ansichtsmodi (Windows Vista/7) -Verwenden Sie die Standard-Dateisystemansicht für Bibliotheksordner (Windows 7) +Verwendung der Standard-Dateisystemansicht für Bibliotheksordner (Windows 7) Abwählen der Dateierweiterung beim Umbenennen von Dateien (Windows XP) -Zyklusauswahl mit F2 beim Umbenennen -Beim Verschieben der Auswahl mit den Pfeiltasten umbrechen +Auswahl mit F2 beim Umbenennen umschalten +Umdrehen beim Verschieben der Auswahl mit den Pfeiltasten Rücktaste geht eine Ebene nach oben (Windows Vista/7) Scrollen mit Mausrad bei gedrückter Umschalttaste scrollt horizontal Tweaks, die nur mit der älteren Listenansicht verwendet werden können -Aktivieren Sie das ältere Steuerelement für die Listenansicht (Windows 7) -Vollständige Zeilenauswahl in der Details-Ansicht -Gitterlinien in der Ansicht "Details" anzeigen -Abwechselnde Zeilenfarben in der Details-Ansicht +Aktivieren der älteren Listenansicht-Steuerung (Windows 7) +Vollständige Zeilenauswahl in der Detailansicht +Gitterlinien in der Detailansicht anzeigen +Alternative Zeilenfarben in der Detailansicht Vordergrundfarbe: Hintergrundfarbe: -Ändern Sie das Verhalten der Tooltips für Unterordner und Vorschau. -Tipps für Unterordner -Tipps für Unterordner einschalten +Ändern Sie das Verhalten von Unterordner- und Vorschau-Tooltips +Unterordner-Inhalte +Unterordner-Inhalte aktivieren Nur bei gedrückter Umschalttaste Dateien anzeigen -Vorschau-Tooltips beim Überfahren von Menüpunkten mit der Maus anzeigen +Vorschau-Tooltips anzeigen, wenn Sie mit der Maus über Menüpunkte fahren Vorschau-Tooltips -Vorschau-Tooltips einschalten +Vorschau-Tooltips aktivieren Nur bei gedrückter Umschalttaste Dateiinformationen unter Bildern anzeigen Maximale Breite x Höhe: Schriftart: -Text-Dateitypen +Textdateitypen Bild- und Videodateitypen -Allgemeine Optionen für das Verhalten von QTTabBar. -Integration der Taskleiste -Anzeigen von Registerkartenvorschauen in der Taskleiste -Geschichte -Verlauf geschlossener Registerkarten beibehalten +Allgemeine Einstellungen für das Verhalten von QTTabBar +Taskleisten-Integration +Registerkartenvorschau in der Taskleiste anzeigen +Verlauf +Verlauf geschlossener Registerkarten behalten Jetzt löschen -Historie der geöffneten Dateien aufbewahren +Verlauf geöffneter Dateien behalten Netzwerk -Netzwerk-Timeout (Geben Sie 0 für die Systemvorgabe ein): +Netzwerk-Timeout (geben Sie 0 für die Systemvorgabe ein): Regelmäßig nach Updates suchen Jetzt prüfen Exportieren Einstellungen exportieren... Dateiverlauf löschen? Registerkartenverlauf löschen? +Sound +Enable Sound +Activar o som -Ändern Sie das Aussehen von Registerkarten oder der Registerkartenleiste. -Registerkarte Bild -Benutzerdefiniertes Registerkartenbild verwenden: -Auslegungsspielräume: -Überlappungsbereich: -Inhalt Ränder: -Hit-Test transparente Bereiche -Registerkartengröße -Höhe: -Min. Breite: -Max. Breite: -Auslegungsmodus: +Ändern Sie das Aussehen der Registerkarten und der Registerkartenleiste +Tab Bild +Benutzerdefiniertes Bild: +Dimensionierung von Rändern: +Überlappungsbereich: +Inhaltsränder: +Hit-Test transparenter Bereich +Tab Größe +Höhe: +Minimale Breite: +Maximale Breite: +Skalierungsmodus: Volle Breite Gleiche Größe -Registerkarte Text -Textfarben: -Schattenfarben: +Tab Text +Text Farben: +Schatten Farben: Aktive Farbe Inaktive Farbe -Mouseover-Farbe -Text Schriftart: -Ausrichtung: +Hover-Farbe +Schriftart: +Ausrichtung: Links -Zentrum -Symbolleisten-Hintergrund -Bild -Einfarbig -Jeden Balken einzeln abbilden -Streckmodus: -Auslegungsspielräume: -Skin-Datei +Zentriert +Hintergrund der Symbolleiste +Benutzerdefiniertes Bild +Vollfarbe +Die Bilder werden von jedem Symbolleistenbereich ausgefüllt +Speicherort des Bildes: +Dimensionierung von Rändern: +Skin-Dateien Skin importieren... Skin exportieren... -Strecken +Dehnen Echte Größe -Fliese +Kacheln -Weisen Sie den verschiedenen Mausaktionen Befehle zu. -Um eine Mausbindung hinzuzufügen, führen Sie eine Mausaktion auf dem Plus-Symbol links neben dem entsprechenden Zieltyp aus. Um eine Bindung zu entfernen, wählen Sie sie in der Liste aus und drücken Sie die Entf-Taste. -Andere Mausoptionen -Mausrad scrollt das Fenster unter der Maus -Maus-Akkord +Weisen Sie verschiedenen Mausaktionen Befehle zu +Um eine Mausbindung hinzuzufügen, führen Sie eine Mausaktion auf dem Plus-Symbol links neben dem entsprechenden Zieltyp aus. Um eine Bindung zu entfernen, markieren Sie sie in der Liste und drücken Sie die Entf-Taste. +Weitere Mausoptionen +Mausrad rollt das Fenster unter der Maus +Mausakkord Aktion -Irgendwo -Registerkarte -Hintergrund der Registerkartenleiste -Ordner Link +Überall +Tabulator +Tab-Leisten-Hintergrund +Ordner-Link Datei oder Ordner -Explorer Hintergrund +Explorer-Hintergrund Linksklick Rechtsklick -Mittlerer Klick +Mittelklick Doppelklick -X1 Klick -X2 Klick +X1-Klick +X2-Klick Diese Maustaste kann für dieses Ziel nicht verwendet werden. Die gültigen Maustasten für dieses Ziel sind: Ungültige Taste -Tastaturkürzel ändern. -Wählen Sie eine Aktion aus der Liste aus und drücken Sie die Tastengeste, die Sie daran binden möchten. Drücken Sie Escape, um die Bindung aufzuheben. +Tastenkombinationen ändern +Wählen Sie eine Aktion aus der Liste aus und drücken Sie die Tastenkombination, die Sie damit verbinden möchten. Drücken Sie Escape, um die Bindung zu löschen. Andere Tastaturoptionen -Das Fenster "Tab Switcher" anzeigen, wenn Strg + Tab gedrückt wird +Tabulatorwechsel-Fenster anzeigen, wenn Strg + Tab gedrückt wird Aktion -Tastenanschlag -Diese Taste ist bereits zugewiesen: +Tastendruck +Diese Taste ist bereits zugewiesen an: Soll sie neu zugewiesen werden? Tastenkonflikt -Erstellen und verwalten Sie Registerkartengruppen. -Um einer Gruppe eine Schnelltaste zuzuweisen, wählen Sie sie aus und drücken Sie die entsprechende Tastengeste. Drücken Sie Escape, um die Bindung zu löschen. +Erstellen und Verwalten von Registerkartengruppen +Um einer Gruppe ein Tastenkürzel zuzuweisen, wählen Sie sie aus und drücken Sie die entsprechende Tastengeste. Drücken Sie Escape, um die Bindung zu löschen. Neue Gruppe erstellen Ordner -Inbetriebnahme +Start Tastenkürzel Neue Gruppe Gruppe "{0}" öffnen @@ -340,52 +350,52 @@ Gruppe "{0}" öffnen Erstellen Sie Verknüpfungen zu häufig verwendeten Dateien oder Anwendungen. -Um einer Anwendung eine Schnelltaste zuzuweisen, wählen Sie sie aus und drücken Sie die entsprechende Tastengeste. Drücken Sie Escape, um die Verknüpfung zu löschen. +Um einer Anwendung eine Verknüpfung zuzuweisen, wählen Sie sie aus und drücken Sie die entsprechende Tastenkombination. Drücken Sie Escape, um die Verknüpfung zu löschen. Neuen Ordner erstellen Pfad: Argumente: Arbeitsverzeichnis: Anwendung -Tastenkürzel -Die folgenden Schlüsselwörter werden zur Laufzeit durch Pfadzeichenketten in doppelten Anführungszeichen ersetzt. +Verknüpfungstaste +Die folgenden Schlüsselwörter werden zur Laufzeit durch Pfadangaben in doppelten Anführungszeichen ersetzt. Ausgewählte Dateien. -Ausgewählte Mappen. +Ausgewählte Ordner. Ausgewählte Dateien und Ordner. Aktueller Ordner. Aktueller Ordner, wenn keine Auswahl, sonst ausgewählter Ordner. -Neue Mappe +Neuer Ordner Diesen Ordner und seinen Inhalt entfernen? Anwendung "{0}" starten -Anpassen der Schaltflächenleiste. +Anpassen der Schaltflächenleiste Schaltflächen -Optionen der Schaltflächenleiste +Optionen für die Schaltflächenleiste Große Symbole Beschriftungen auf Schaltflächen anzeigen -Sperren der Reihenfolge von Dropdown-Schaltflächenmenüs -Sperren der Größe des Suchfelds -Benutzerdefinierte Schaltflächenbilder +Reihenfolge der Dropdown-Schaltflächenmenüs festlegen +Größe des Suchfelds festlegen +Benutzerdefinierte Schaltflächen-Icons -Verwalten Sie Plugins von Drittanbietern. -Ein Plugin installieren -(Disabled) +Plugins von Drittanbietern verwalten. +Plugin installieren +(Deaktiviert) von {0} {0} wurde entfernt. Dieses Plugin wird installiert, wenn Sie auf OK oder Übernehmen drücken. Dieses Plugin wird aktiviert, wenn Sie auf OK oder Übernehmen drücken. Dieses Plugin wird deaktiviert, wenn Sie auf OK oder Übernehmen drücken. -Wenn Sie dieses Plugin deinstallieren, werden auch die folgenden Plugins deinstalliert: +Die Deinstallation dieses Plugins deinstalliert auch die folgenden Plugins: Fortfahren? -Aktivieren Sie +Aktivieren Deaktivieren -Ändern Sie die Sprache. +Sprache ändern Hauptsprache Eingebaute Sprache verwenden: Sprachdatei verwenden: @@ -396,25 +406,40 @@ Original Übersetzt Alle Einträge auf ihren ursprünglichen Wert zurücksetzen? Fehler beim Speichern der Datei. -Vereinfachtes Chinesisch +简体中文 +English +German -Abspann und Versionsinformationen anzeigen. -Original-Code von {0} +Credits und Versionsinformationen anzeigen: +Originalcode von {0} Hauptentwickler: Andere Entwickler: -Icons und Grafiken: +Symbole und Grafiken: Wenn Sie QTTabBar nützlich finden, ziehen Sie bitte eine kleine Spende in Betracht. -Vereinfachtes Chinesisch: -Gebaut von: {0} +汉化修改: +Build by: {0} + +Schaltfläche für neuen Tab +Angepasste Grafik verwenden: + + + +Tab-Schaltflächengrafiken +Benutzerdefinierte Grafik für „Tab schließen“: +Versatz der Schließen-Grafik (X, Y): +Benutzerdefinierte Grafik für „Tab gesperrt“: +Versatz der Sperrgrafik (X, Y): + + -QTTabBar Optionen -Alle Optionen auf dieser Seite auf ihre Standardwerte zurücksetzen? -Alle Optionen auf ihre Standardwerte zurücksetzen? Dies hat keine Auswirkungen auf Gruppen, Anwendungen oder Plugins. -Konfirmation +QTTabBar Einstellungen +Alle Einstellungen auf dieser Seite auf ihre Standardwerte zurücksetzen? +Alle Einstellungen auf ihre Standardwerte zurücksetzen? Dies wirkt sich nicht auf Gruppen, Anwendungen oder Plugins aus. +Bestätigung @@ -422,86 +447,87 @@ https://github.com/indiff/qttabbar/blob/master/QTTabBar/Resources/wechat_pay.jpg -Zurückgehen -Vorwärts gehen -Zurück zum Start -Vorwärts bis Ende -Nächste Registerkarte wählen +Zurück +Vorwärts +Zurück zum Anfang +Vorwärts zum Ende +Nächste Registerkarte auswählen Vorherige Registerkarte auswählen Erste Registerkarte auswählen Letzte Registerkarte auswählen -Wechsel zur zuletzt aktivierten Registerkarte +Zur zuletzt aktivierten Registerkarte wechseln Neue Registerkarte öffnen Neues Fenster öffnen Registerkarten aus anderen Fenstern zusammenführen Aktuelle Registerkarte schließen -Alle schließen, außer aktive +Alle außer der aktuellen schließen Links schließen Rechts schließen Fenster schließen Schließen rückgängig machen Aktuelle Registerkarte klonen Aktuellen Ordner in neuem Fenster öffnen -Strom sperren / entsperren -Alle sperren / entsperren +Aktuelle sperren / entsperren + Alle sperren / entsperren Ordner durchsuchen Neue Gruppe erstellen Optionsdialog öffnen Menü der Symbolleiste anzeigen -Aktuelles Registerkartenmenü anzeigen -Gruppenmenü anzeigen (Schaltflächenleiste) -Menü "Letzte Ordner" anzeigen (Schaltflächenleiste) -Menü der Benutzeranwendung anzeigen (Schaltflächenleiste) -Menü "Letzte Dateien" anzeigen (Schaltflächenleiste) +Menü der aktuellen Registerkarte anzeigen +Menü der Gruppe anzeigen (Buttonleiste) +Menü des letzten Ordners anzeigen (Buttonleiste) +Menü der Benutzeranwendung anzeigen (Buttonleiste) +Menü der letzten Dateien anzeigen (Buttonleiste) Neue leere Datei erstellen Neuen Ordner erstellen Pfad der ausgewählten Dateien kopieren Name der ausgewählten Dateien kopieren -Aktuellen Ordnerpfad kopieren -Aktuellen Ordnernamen kopieren +Pfad des aktuellen Ordners kopieren + Aktuellen Ordnernamen kopieren MD5 der ausgewählten Dateien prüfen Fenster ganz nach oben bringen Transparenz + Transparenz - -Fokus-Dateiliste -Fokus-Suchleiste (Explorer) -Fokus-Suchfeld (Schaltflächenleiste) -Unterordner-Tippmenü für ausgewählten Ordner anzeigen +Dateiliste fokussieren +Suchleiste (Explorer) fokussieren +Suchfeld (Buttonleiste) fokussieren +Unterordner-Inhalt-Menü für ausgewählten Ordner anzeigen Fenster an Taskleiste senden -Fokussieren der Registerkartenleiste -Registerkarten nach Namen sortieren -Registerkarten nach Pfad sortieren -Registerkarten nach letzter Aktivierung sortieren +Tab-Leiste fokussieren +Tabs nach Namen sortieren +Tabs nach Pfad sortieren +Tabs nach letzter Aktivierung sortieren Nichts Eine Ebene höher Aktualisieren Einfügen -Maximieren Sie -Minimieren +Maximieren + Minimieren In neuer Registerkarte öffnen -In neuer Registerkarte öffnen, ohne zu aktivieren +In neuer Registerkarte ohne Aktivierung öffnen In neuem Fenster öffnen -Schneiden +Ausschneiden Kopieren Löschen Eigenschaften Pfad kopieren Name kopieren Prüfsumme anzeigen -Register schließen -Registerkarten nach links schließen -Registerkarten nach rechts schließen +Registerkarte schließen + Registerkarte links schließen +Registerkarte rechts schließen Eine Ebene höher -Schloss +Sperren Kontextmenü anzeigen In neuem Fenster öffnen Klonen Pfad kopieren Eigenschaften -Menü "Unterverzeichnis anzeigen" Tipp -Alle schließen, außer diesem +Inhalt-Menü für Unterverzeichnisse anzeigen +Alles außer diesem schließen CMD öffnen -Ausgewählte Ordner in neuer Registerkarte öffnen, ohne sie zu aktivieren +Ausgewählte Ordner in neuer Registerkarte ohne Aktivierung öffnen +Add to group @@ -517,48 +543,48 @@ https://github.com/indiff/qttabbar Schließen Rechts schließen Links schließen -Alle schließen, außer diesem -Zur Gruppe hinzufügen +Alles außer diesem schließen +Zu Gruppe hinzufügen Neue Gruppe erstellen -Schloss -Klonen Sie dies +Sperren +Dies klonen Neues Fenster Pfad kopieren Eigenschaften -Registerkarte Historie +Tab-Historie Gruppen -Rückgängig geschlossen +Geschlossen rückgängig machen Zuletzt aktiviert -Zuletzt geöffnete Dateien +Kürzlich geöffnete Dateien Ordner durchsuchen -Alle schließen, außer Strom +Alles außer dem aktuellen schließen Fenster schließen Optionen -freischalten +Entsperren Zum Anwendungsmenü hinzufügen : -Artikel +Einträge Abbrechen -Zweige -Diesen Punkt aus dem Menü entfernen -&Öffne enthaltender Ordner -Registerkarte "Etikett -Tabulator-Reihenfolge +Entwicklungszweig +Diesen Eintrag aus dem Menü entfernen +O&pen, der den Ordner enthält +Tab-Label +Tab-Reihenfolge Name Pfad Aktiv -Sperren der Symbolleisten -Registerkarten anderer Fenster zusammenführen +Symbolleisten sperren +Tabs anderer Fenster zusammenführen Invertieren CMD öffnen -Es trat ein Fehler auf bei den Versuch \r\n\r\n "{0}" zu öffnen. +QTTabBar konnte nicht geöffnet werden\r\n\r\n "{0}". Neue Gruppe erstellen -Name der Gruppe: +Gruppenname: Alle Registerkarten hinzufügen @@ -568,7 +594,7 @@ Registerkarten Tweaks Tooltips Allgemein -Erscheinungsbild +Aussehen Maus Tasten Gruppen @@ -590,7 +616,7 @@ Tastenkombinationen für Anwendungen aktivieren Gruppen -Rückgängig geschlossen +Geschlossen rückgängig machen Anwendungen Zuletzt geöffnete Dateien @@ -598,11 +624,13 @@ Zuletzt geöffnete Dateien 1 Fenster {0} Fenster +Restore all +Close All -Es wurde eine neue Version von QTTabBar veröffentlicht: -Drücken Sie OK, um die Website aufzurufen. +Eine neue Version von QTTabBar wurde veröffentlicht: +Drücken Sie OK, um zur Website zu gelangen. Ihre Version von QTTabBar ist auf dem neuesten Stand. Neue Entwicklungsversion gefunden: Versionsdatei nicht gefunden (404). diff --git a/Installer/Bundle.wxs b/Installer/Bundle.wxs index 41d306c..53180f0 100644 --- a/Installer/Bundle.wxs +++ b/Installer/Bundle.wxs @@ -3,8 +3,8 @@ xmlns:bal="http://schemas.microsoft.com/wix/BalExtension" xmlns:netfx="http://schemas.microsoft.com/wix/NetFxExtension"> - diff --git a/Installer/Installer.wxs b/Installer/Installer.wxs index 73adb25..b724505 100644 --- a/Installer/Installer.wxs +++ b/Installer/Installer.wxs @@ -23,14 +23,14 @@ - - + + - + - PREVIOUSVERSIONSINSTALLED + UPGRADEFOUND diff --git a/Installer/config.ini b/Installer/config.ini index 6c54449..6db3386 100644 --- a/Installer/config.ini +++ b/Installer/config.ini @@ -4,8 +4,14 @@ enabled=false [image] enabled=true +# Hold ESC while Explorer starts to bypass the background renderer for recovery. # 遍历模式生效 random=false +# Optional directory containing additional PNG/BMP/JPG/JPEG images. +# Relative paths are resolved below C:\ProgramData\QTTabBar. +folder=Image +# Allow a section named after the current Explorer path to select an image. +custom=false # 图片位置 #0 = Left top #1 = Right top @@ -19,3 +25,10 @@ posType=3 imgAlpha= 255 # 如果自定义存在则加载,反之加载目录的图片 *.png *.jpg *.jpeg C:\\ProgramData\\QTTabBar\\Image\\bgImage.png imgPath=%ProgramData%\QTTabBar\libai.png + +# Example for custom=true (the image must exist in the configured folder): +#[C:\Users\Public\Pictures] +#img=example.png +# +#[::{20D04FE0-3AEA-1069-A2D8-08002B30309D}] +#img=this-pc.png diff --git a/MinHook/libMinHook.vcxproj b/MinHook/libMinHook.vcxproj index 7520ea5..e82f04e 100644 --- a/MinHook/libMinHook.vcxproj +++ b/MinHook/libMinHook.vcxproj @@ -83,7 +83,7 @@ WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) true EnableFastChecks - MultiThreadedDebug + MultiThreadedDebugDLL Level3 @@ -102,7 +102,7 @@ WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) true EnableFastChecks - MultiThreadedDebug + MultiThreadedDebugDLL Level3 @@ -117,7 +117,7 @@ MaxSpeed true WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) - MultiThreaded + MultiThreadedDLL true @@ -136,7 +136,7 @@ MaxSpeed true WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) - MultiThreaded + MultiThreadedDLL true diff --git a/QTHookLib/ExplorerBackgroundRenderer.cpp b/QTHookLib/ExplorerBackgroundRenderer.cpp new file mode 100644 index 0000000..73f63df --- /dev/null +++ b/QTHookLib/ExplorerBackgroundRenderer.cpp @@ -0,0 +1,734 @@ +#include "ExplorerBackgroundRenderer.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma comment(lib, "Msimg32.lib") +#pragma comment(lib, "Shlwapi.lib") +#pragma comment(lib, "Windowscodecs.lib") + +namespace qttabbar { +namespace background { +namespace { + +constexpr wchar_t kRegisteredWindowProperty[] = + L"QTTabBar.NativeEnhanced.BackgroundWindow"; + +struct ImageAsset { + ~ImageAsset() { + if(memoryDc != nullptr && previousBitmap != nullptr && previousBitmap != HGDI_ERROR) { + ::SelectObject(memoryDc, previousBitmap); + } + if(bitmap != nullptr) { + ::DeleteObject(bitmap); + } + if(memoryDc != nullptr) { + ::DeleteDC(memoryDc); + } + } + + std::wstring path; + std::wstring fileName; + HDC memoryDc = nullptr; + HBITMAP bitmap = nullptr; + HGDIOBJ previousBitmap = nullptr; + SIZE size{}; + mutable std::mutex drawMutex; +}; + +struct ConfigSnapshot { + std::wstring configPath; + std::wstring imageFolder; + int positionMode = 3; + BYTE alpha = 255; + bool random = false; + bool customByPath = false; + std::vector> images; +}; + +struct WindowState { + SIZE lastSize{}; + std::size_t imageIndex = 0; + std::wstring path; +}; + +std::wstring ToLower(std::wstring value) { + std::transform(value.begin(), value.end(), value.begin(), + [](wchar_t ch) { return static_cast(std::towlower(ch)); }); + return value; +} + +std::wstring FileNameOf(const std::wstring& path) { + const std::wstring::size_type slash = path.find_last_of(L"\\/"); + return slash == std::wstring::npos ? path : path.substr(slash + 1); +} + +bool FileExists(const std::wstring& path) { + const DWORD attributes = ::GetFileAttributesW(path.c_str()); + return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0; +} + +bool DirectoryExists(const std::wstring& path) { + const DWORD attributes = ::GetFileAttributesW(path.c_str()); + return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0; +} + +std::wstring ModuleDirectory(HMODULE module) { + std::vector buffer(MAX_PATH); + for(;;) { + const DWORD length = ::GetModuleFileNameW(module, buffer.data(), static_cast(buffer.size())); + if(length == 0) { + return {}; + } + if(length < buffer.size() - 1) { + std::wstring path(buffer.data(), length); + const std::wstring::size_type slash = path.find_last_of(L"\\/"); + return slash == std::wstring::npos ? std::wstring() : path.substr(0, slash); + } + buffer.resize(buffer.size() * 2); + } +} + +std::wstring ReadIni(const std::wstring& file, const std::wstring& section, + const wchar_t* key, const wchar_t* fallback = L"") { + std::vector buffer(32768, L'\0'); + const DWORD length = ::GetPrivateProfileStringW(section.c_str(), key, fallback, + buffer.data(), static_cast(buffer.size()), file.c_str()); + return std::wstring(buffer.data(), length); +} + +bool ReadBool(const std::wstring& file, const wchar_t* section, const wchar_t* key, + bool fallback) { + const std::wstring value = ToLower(ReadIni(file, section, key, fallback ? L"true" : L"false")); + return value == L"true" || value == L"1" || value == L"yes" || value == L"on"; +} + +int ReadInt(const std::wstring& file, const wchar_t* section, const wchar_t* key, + int fallback, int minimum, int maximum) { + const std::wstring value = ReadIni(file, section, key, L""); + if(value.empty()) { + return fallback; + } + wchar_t* end = nullptr; + const long parsed = std::wcstol(value.c_str(), &end, 10); + if(end == value.c_str() || *end != L'\0') { + return fallback; + } + const long clamped = parsed < minimum ? minimum : (parsed > maximum ? maximum : parsed); + return static_cast(clamped); +} + +std::wstring ExpandPath(const std::wstring& value, const std::wstring& baseDirectory) { + if(value.empty()) { + return {}; + } + const DWORD required = ::ExpandEnvironmentStringsW(value.c_str(), nullptr, 0); + std::wstring expanded = value; + if(required > 0) { + std::vector buffer(required, L'\0'); + if(::ExpandEnvironmentStringsW(value.c_str(), buffer.data(), required) != 0) { + expanded.assign(buffer.data()); + } + } + if(::PathIsRelativeW(expanded.c_str())) { + return baseDirectory + L"\\" + expanded; + } + return expanded; +} + +bool IsSupportedImage(const std::wstring& name) { + const std::wstring lower = ToLower(name); + return lower.size() > 4 && + (lower.compare(lower.size() - 4, 4, L".png") == 0 || + lower.compare(lower.size() - 4, 4, L".bmp") == 0 || + lower.compare(lower.size() - 4, 4, L".jpg") == 0 || + (lower.size() > 5 && lower.compare(lower.size() - 5, 5, L".jpeg") == 0)); +} + +void EnumerateImages(const std::wstring& directory, std::vector& paths) { + if(!DirectoryExists(directory)) { + return; + } + WIN32_FIND_DATAW data{}; + const std::wstring pattern = directory + L"\\*"; + HANDLE find = ::FindFirstFileW(pattern.c_str(), &data); + if(find == INVALID_HANDLE_VALUE) { + return; + } + do { + if((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0 && IsSupportedImage(data.cFileName)) { + paths.push_back(directory + L"\\" + data.cFileName); + } + } while(::FindNextFileW(find, &data)); + ::FindClose(find); + std::sort(paths.begin(), paths.end(), [](const std::wstring& left, const std::wstring& right) { + return ToLower(left) < ToLower(right); + }); +} + +std::shared_ptr LoadImage(const std::wstring& path) { + using Microsoft::WRL::ComPtr; + + ComPtr factory; + HRESULT result = ::CoCreateInstance(CLSID_WICImagingFactory, nullptr, + CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&factory)); + if(FAILED(result)) { + return {}; + } + + ComPtr decoder; + result = factory->CreateDecoderFromFilename(path.c_str(), nullptr, GENERIC_READ, + WICDecodeMetadataCacheOnLoad, &decoder); + if(FAILED(result)) { + return {}; + } + + ComPtr frame; + result = decoder->GetFrame(0, &frame); + if(FAILED(result)) { + return {}; + } + + UINT width = 0; + UINT height = 0; + result = frame->GetSize(&width, &height); + if(FAILED(result) || width == 0 || height == 0 || + width > static_cast((std::numeric_limits::max)()) || + height > static_cast((std::numeric_limits::max)()) || + width > (std::numeric_limits::max)() / 4) { + return {}; + } + + const UINT stride = width * 4; + if(height > (std::numeric_limits::max)() / stride) { + return {}; + } + const UINT bufferSize = stride * height; + + ComPtr converter; + result = factory->CreateFormatConverter(&converter); + if(FAILED(result)) { + return {}; + } + result = converter->Initialize(frame.Get(), GUID_WICPixelFormat32bppPBGRA, + WICBitmapDitherTypeNone, nullptr, 0.0, WICBitmapPaletteTypeCustom); + if(FAILED(result)) { + return {}; + } + + auto image = std::make_shared(); + image->path = path; + image->fileName = FileNameOf(path); + image->size.cx = static_cast(width); + image->size.cy = static_cast(height); + image->memoryDc = ::CreateCompatibleDC(nullptr); + if(image->memoryDc == nullptr) { + return {}; + } + BITMAPINFO bitmapInfo{}; + bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bitmapInfo.bmiHeader.biWidth = image->size.cx; + bitmapInfo.bmiHeader.biHeight = -image->size.cy; + bitmapInfo.bmiHeader.biPlanes = 1; + bitmapInfo.bmiHeader.biBitCount = 32; + bitmapInfo.bmiHeader.biCompression = BI_RGB; + void* destinationBits = nullptr; + image->bitmap = ::CreateDIBSection(image->memoryDc, &bitmapInfo, DIB_RGB_COLORS, + &destinationBits, nullptr, 0); + if(image->bitmap == nullptr || destinationBits == nullptr) { + return {}; + } + + result = converter->CopyPixels(nullptr, stride, bufferSize, + static_cast(destinationBits)); + if(FAILED(result)) { + return {}; + } + + image->previousBitmap = ::SelectObject(image->memoryDc, image->bitmap); + if(image->previousBitmap == nullptr || image->previousBitmap == HGDI_ERROR) { + return {}; + } + return image; +} + +bool SameFileTime(const FILETIME& left, const FILETIME& right) { + return left.dwLowDateTime == right.dwLowDateTime && left.dwHighDateTime == right.dwHighDateTime; +} + +FILETIME LastWriteTime(const std::wstring& path) { + WIN32_FILE_ATTRIBUTE_DATA data{}; + if(::GetFileAttributesExW(path.c_str(), GetFileExInfoStandard, &data)) { + return data.ftLastWriteTime; + } + return {}; +} + +class Renderer { +public: + Renderer() noexcept : random_(0x51545442u) { + } + + HRESULT Initialize(HMODULE module) { + std::lock_guard loadGuard(loadMutex_); + if(active_.load(std::memory_order_acquire)) { + return S_OK; + } + if((::GetAsyncKeyState(VK_ESCAPE) & 0x8000) != 0) { + return HRESULT_FROM_WIN32(ERROR_CANCELLED); + } + moduleDirectory_ = ModuleDirectory(module); + if(moduleDirectory_.empty()) { + return E_FAIL; + } + configPath_ = moduleDirectory_ + L"\\config.ini"; + if(!FileExists(configPath_) || !ReadBool(configPath_, L"image", L"enabled", true)) { + return S_FALSE; + } + + std::shared_ptr snapshot = BuildSnapshot(); + if(!snapshot || snapshot->images.empty()) { + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + { + std::lock_guard stateGuard(stateMutex_); + config_ = std::move(snapshot); + windows_.clear(); + dcWindows_.clear(); + configWriteTime_ = LastWriteTime(configPath_); + } + active_.store(true, std::memory_order_release); + return S_OK; + } + + void Shutdown() noexcept { + std::lock_guard loadGuard(loadMutex_); + active_.store(false, std::memory_order_release); + { + std::lock_guard stateGuard(stateMutex_); + for(const auto& entry : windows_) { + if(::IsWindow(entry.first) && + ::GetPropW(entry.first, kRegisteredWindowProperty) == this) { + ::RemovePropW(entry.first, kRegisteredWindowProperty); + } + } + windows_.clear(); + dcWindows_.clear(); + config_.reset(); + } + } + + bool IsActive() const noexcept { + return active_.load(std::memory_order_acquire); + } + + HRESULT RegisterWindow(HWND window, const wchar_t* path) { + if(!IsActive() || !::IsWindow(window)) { + return E_FAIL; + } + wchar_t className[64]{}; + ::GetClassNameW(window, className, ARRAYSIZE(className)); + if(std::wcscmp(className, L"DirectUIHWND") != 0) { + return E_INVALIDARG; + } + ReloadIfChanged(); + + const std::wstring currentPath = path == nullptr ? std::wstring() : path; + std::lock_guard stateGuard(stateMutex_); + if(!config_ || config_->images.empty()) { + return E_FAIL; + } + if(!::SetPropW(window, kRegisteredWindowProperty, this)) { + const DWORD error = ::GetLastError(); + return HRESULT_FROM_WIN32(error == ERROR_SUCCESS ? ERROR_NOT_ENOUGH_MEMORY : error); + } + WindowState& state = windows_[window]; + state.path = currentPath; + state.imageIndex = SelectImageIndex(*config_, currentPath); + state.lastSize = {}; + ::InvalidateRect(window, nullptr, TRUE); + return S_OK; + } + + HRESULT UpdateWindow(HWND window, const wchar_t* path) { + if(!IsActive() || !::IsWindow(window)) { + return E_FAIL; + } + if(::GetPropW(window, kRegisteredWindowProperty) != this) { + return E_INVALIDARG; + } + ReloadIfChanged(); + const std::wstring currentPath = path == nullptr ? std::wstring() : path; + std::lock_guard stateGuard(stateMutex_); + auto found = windows_.find(window); + if(found == windows_.end() || !config_ || config_->images.empty()) { + return E_FAIL; + } + found->second.path = currentPath; + found->second.imageIndex = SelectImageIndex(*config_, currentPath); + ::InvalidateRect(window, nullptr, TRUE); + return S_OK; + } + + void OnWindowDcAcquired(HWND window, HDC dc) noexcept { + if(!IsActive() || window == nullptr || dc == nullptr) { + return; + } + std::lock_guard guard(stateMutex_); + dcWindows_.erase(dc); + if(windows_.find(window) != windows_.end() && + ::IsWindow(window) && + ::GetPropW(window, kRegisteredWindowProperty) == this) { + dcWindows_[dc] = window; + } + } + + void OnCompatibleDcCreated(HDC sourceDc, HDC compatibleDc) noexcept { + if(!IsActive() || sourceDc == nullptr || compatibleDc == nullptr) { + return; + } + std::lock_guard guard(stateMutex_); + dcWindows_.erase(compatibleDc); + if(dcWindows_.size() > 2048) { + dcWindows_.clear(); + } + const HWND window = ResolveWindowLocked(sourceDc); + if(window != nullptr) { + dcWindows_[compatibleDc] = window; + } + } + + void OnDcDeleted(HDC dc) noexcept { + if(dc == nullptr) { + return; + } + std::lock_guard guard(stateMutex_); + dcWindows_.erase(dc); + } + + void OnFillRect(HDC targetDc, const RECT* paintedRect) noexcept { + if(!IsActive() || targetDc == nullptr || paintedRect == nullptr) { + return; + } + + HWND window = nullptr; + SIZE previousSize{}; + std::shared_ptr config; + std::shared_ptr image; + { + std::lock_guard guard(stateMutex_); + window = ResolveWindowLocked(targetDc); + auto found = windows_.find(window); + if(found != windows_.end()) { + const WindowState& state = found->second; + previousSize = state.lastSize; + config = config_; + if(config && !config->images.empty()) { + const std::size_t index = state.imageIndex < config->images.size() ? state.imageIndex : 0; + image = config->images[index]; + } + } + } + if(window == nullptr || !image || !config || image->memoryDc == nullptr || + image->size.cx <= 0 || image->size.cy <= 0) { + return; + } + + RECT client{}; + if(!::GetClientRect(window, &client)) { + return; + } + const SIZE windowSize{client.right - client.left, client.bottom - client.top}; + if(windowSize.cx <= 0 || windowSize.cy <= 0) { + return; + } + if(config->positionMode != 0 && + (previousSize.cx != windowSize.cx || previousSize.cy != windowSize.cy)) { + ::InvalidateRect(window, nullptr, TRUE); + } + + POINT position{}; + SIZE destination = image->size; + CalculatePlacement(config->positionMode, windowSize, image->size, position, destination); + if(destination.cx <= 0 || destination.cy <= 0) { + return; + } + + const int saved = ::SaveDC(targetDc); + if(saved == 0) { + return; + } + const int clipResult = ::IntersectClipRect(targetDc, paintedRect->left, paintedRect->top, + paintedRect->right, paintedRect->bottom); + if(clipResult == NULLREGION || clipResult == ERROR) { + ::RestoreDC(targetDc, saved); + return; + } + const BLENDFUNCTION blend{AC_SRC_OVER, 0, config->alpha, AC_SRC_ALPHA}; + { + std::lock_guard drawGuard(image->drawMutex); + ::AlphaBlend(targetDc, position.x, position.y, destination.cx, destination.cy, + image->memoryDc, 0, 0, image->size.cx, image->size.cy, blend); + } + ::RestoreDC(targetDc, saved); + + std::lock_guard guard(stateMutex_); + auto found = windows_.find(window); + if(found != windows_.end()) { + found->second.lastSize = windowSize; + } + } + +private: + HWND ResolveWindowLocked(HDC dc) { + const auto mapped = dcWindows_.find(dc); + if(mapped != dcWindows_.end()) { + if(windows_.find(mapped->second) != windows_.end() && ::IsWindow(mapped->second) && + ::GetPropW(mapped->second, kRegisteredWindowProperty) == this) { + return mapped->second; + } + dcWindows_.erase(mapped); + } + + HWND candidate = ::WindowFromDC(dc); + for(int depth = 0; candidate != nullptr && depth < 8; ++depth) { + if(windows_.find(candidate) != windows_.end() && + ::GetPropW(candidate, kRegisteredWindowProperty) == this) { + return candidate; + } + candidate = ::GetParent(candidate); + } + return nullptr; + } + + std::shared_ptr BuildSnapshot() const { + auto snapshot = std::make_shared(); + snapshot->configPath = configPath_; + snapshot->positionMode = ReadInt(configPath_, L"image", L"posType", 3, 0, 6); + snapshot->alpha = static_cast(ReadInt(configPath_, L"image", L"imgAlpha", 255, 0, 255)); + snapshot->random = ReadBool(configPath_, L"image", L"random", false); + snapshot->customByPath = ReadBool(configPath_, L"image", L"custom", false); + + const std::wstring configuredFolder = ExpandPath( + ReadIni(configPath_, L"image", L"folder", L""), moduleDirectory_); + snapshot->imageFolder = configuredFolder.empty() ? moduleDirectory_ + L"\\Image" : configuredFolder; + + std::vector paths; + const std::wstring defaultImage = ExpandPath( + ReadIni(configPath_, L"image", L"imgPath", L""), moduleDirectory_); + if(FileExists(defaultImage) && IsSupportedImage(defaultImage)) { + paths.push_back(defaultImage); + } + EnumerateImages(snapshot->imageFolder, paths); + + std::unordered_set seen; + for(const std::wstring& path : paths) { + const std::wstring key = ToLower(path); + if(!seen.insert(key).second) { + continue; + } + std::shared_ptr image = LoadImage(path); + if(image) { + snapshot->images.push_back(std::move(image)); + } + } + return snapshot; + } + + void ReloadIfChanged() { + const FILETIME currentWriteTime = LastWriteTime(configPath_); + { + std::lock_guard guard(stateMutex_); + if(SameFileTime(currentWriteTime, configWriteTime_)) { + return; + } + } + std::lock_guard loadGuard(loadMutex_); + { + std::lock_guard guard(stateMutex_); + if(SameFileTime(currentWriteTime, configWriteTime_)) { + return; + } + } + std::shared_ptr replacement = BuildSnapshot(); + if(!replacement || replacement->images.empty()) { + return; + } + std::vector invalidate; + { + std::lock_guard guard(stateMutex_); + config_ = std::move(replacement); + configWriteTime_ = currentWriteTime; + for(auto& entry : windows_) { + entry.second.imageIndex = SelectImageIndex(*config_, entry.second.path); + invalidate.push_back(entry.first); + } + } + for(HWND window : invalidate) { + if(::IsWindow(window)) { + ::InvalidateRect(window, nullptr, TRUE); + } + } + } + + std::size_t SelectImageIndex(const ConfigSnapshot& config, const std::wstring& path) { + if(config.customByPath && !path.empty()) { + const std::wstring requested = ToLower(FileNameOf( + ReadIni(config.configPath, path, L"img", L""))); + if(!requested.empty()) { + for(std::size_t i = 0; i < config.images.size(); ++i) { + if(ToLower(config.images[i]->fileName) == requested) { + return i; + } + } + } + } + if(config.random && config.images.size() > 1) { + std::uniform_int_distribution distribution(0, config.images.size() - 1); + return distribution(random_); + } + return 0; + } + + static void CalculatePlacement(int mode, const SIZE& window, const SIZE& image, + POINT& position, SIZE& destination) { + destination = image; + switch(mode) { + case 0: + position = {0, 0}; + break; + case 1: + position = {window.cx - image.cx, 0}; + break; + case 2: + position = {0, window.cy - image.cy}; + break; + case 3: + position = {window.cx - image.cx, window.cy - image.cy}; + break; + case 4: + position = {(window.cx - image.cx) / 2, (window.cy - image.cy) / 2}; + break; + case 5: + position = {0, 0}; + destination = window; + break; + case 6: { + const double scaleX = static_cast(window.cx) / image.cx; + const double scaleY = static_cast(window.cy) / image.cy; + const double scale = scaleX > scaleY ? scaleX : scaleY; + const LONG scaledWidth = static_cast(image.cx * scale + 0.5); + const LONG scaledHeight = static_cast(image.cy * scale + 0.5); + destination.cx = scaledWidth > 0 ? scaledWidth : 1; + destination.cy = scaledHeight > 0 ? scaledHeight : 1; + position.x = (window.cx - destination.cx) / 2; + position.y = (window.cy - destination.cy) / 2; + break; + } + default: + position = {window.cx - image.cx, window.cy - image.cy}; + break; + } + } + + std::atomic active_{false}; + std::mutex stateMutex_; + std::mutex loadMutex_; + std::unordered_map windows_; + std::unordered_map dcWindows_; + std::shared_ptr config_; + std::wstring moduleDirectory_; + std::wstring configPath_; + FILETIME configWriteTime_{}; + std::mt19937 random_; +}; + +Renderer& GetRenderer() { + // Explicit Shutdown releases GDI objects before the DLL is unloaded. The tiny + // coordinator intentionally survives to avoid destructor work under loader lock. + static Renderer* renderer = new Renderer(); + return *renderer; +} + +} // namespace + +HRESULT Initialize(HMODULE module) { + try { + return GetRenderer().Initialize(module); + } + catch(const std::bad_alloc&) { + return E_OUTOFMEMORY; + } + catch(...) { + return E_FAIL; + } +} + +void Shutdown() noexcept { + GetRenderer().Shutdown(); +} + +bool IsActive() noexcept { + return GetRenderer().IsActive(); +} + +HRESULT RegisterWindow(HWND window, const wchar_t* path) { + try { + return GetRenderer().RegisterWindow(window, path); + } + catch(const std::bad_alloc&) { + return E_OUTOFMEMORY; + } + catch(...) { + return E_FAIL; + } +} + +HRESULT UpdateWindow(HWND window, const wchar_t* path) { + try { + return GetRenderer().UpdateWindow(window, path); + } + catch(const std::bad_alloc&) { + return E_OUTOFMEMORY; + } + catch(...) { + return E_FAIL; + } +} + +void OnCompatibleDcCreated(HDC sourceDc, HDC compatibleDc) noexcept { + GetRenderer().OnCompatibleDcCreated(sourceDc, compatibleDc); +} + +void OnWindowDcAcquired(HWND window, HDC dc) noexcept { + GetRenderer().OnWindowDcAcquired(window, dc); +} + +void OnDcDeleted(HDC dc) noexcept { + GetRenderer().OnDcDeleted(dc); +} + +void OnFillRect(HDC targetDc, const RECT* paintedRect) noexcept { + GetRenderer().OnFillRect(targetDc, paintedRect); +} + +} // namespace background +} // namespace qttabbar diff --git a/QTHookLib/ExplorerBackgroundRenderer.h b/QTHookLib/ExplorerBackgroundRenderer.h new file mode 100644 index 0000000..84a15ac --- /dev/null +++ b/QTHookLib/ExplorerBackgroundRenderer.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +namespace qttabbar { +namespace background { + +HRESULT Initialize(HMODULE module); +void Shutdown() noexcept; +bool IsActive() noexcept; + +HRESULT RegisterWindow(HWND window, const wchar_t* path); +HRESULT UpdateWindow(HWND window, const wchar_t* path); + +void OnWindowDcAcquired(HWND window, HDC dc) noexcept; +void OnCompatibleDcCreated(HDC sourceDc, HDC compatibleDc) noexcept; +void OnDcDeleted(HDC dc) noexcept; +void OnFillRect(HDC targetDc, const RECT* paintedRect) noexcept; + +} // namespace background +} // namespace qttabbar diff --git a/QTHookLib/QTHookLib.vcxproj b/QTHookLib/QTHookLib.vcxproj index c4dce08..098a7b8 100644 --- a/QTHookLib/QTHookLib.vcxproj +++ b/QTHookLib/QTHookLib.vcxproj @@ -96,7 +96,7 @@ Level3 Disabled WIN32;_DEBUG;_WINDOWS;_USRDLL;QTHOOKLIB_EXPORTS;%(PreprocessorDefinitions) - MultiThreadedDebug + MultiThreadedDebugDLL ProgramDatabase @@ -111,7 +111,7 @@ Level3 Disabled WIN32;_DEBUG;_WINDOWS;_USRDLL;QTHOOKLIB_EXPORTS;%(PreprocessorDefinitions) - MultiThreadedDebug + MultiThreadedDebugDLL ProgramDatabase @@ -129,7 +129,7 @@ true true WIN32;NDEBUG;_WINDOWS;_USRDLL;QTHOOKLIB_EXPORTS;%(PreprocessorDefinitions) - MultiThreaded + MultiThreadedDLL Windows @@ -149,7 +149,7 @@ true true WIN32;NDEBUG;_WINDOWS;_USRDLL;QTHOOKLIB_EXPORTS;%(PreprocessorDefinitions) - MultiThreaded + MultiThreadedDLL Windows @@ -162,10 +162,14 @@ + + + + diff --git a/QTHookLib/ScopedBackgroundHooks.cpp b/QTHookLib/ScopedBackgroundHooks.cpp new file mode 100644 index 0000000..8e44bf6 --- /dev/null +++ b/QTHookLib/ScopedBackgroundHooks.cpp @@ -0,0 +1,189 @@ +#include "ScopedBackgroundHooks.h" + +#include "ExplorerBackgroundRenderer.h" +#include "../MinHook/MinHook.h" + +#include +#include +#include + +namespace qttabbar { +namespace background { +namespace hooks { +namespace { + +using FillRectFn = int (WINAPI*)(HDC, const RECT*, HBRUSH); +using CreateCompatibleDcFn = HDC (WINAPI*)(HDC); +using DeleteDcFn = BOOL (WINAPI*)(HDC); +using BeginPaintFn = HDC (WINAPI*)(HWND, LPPAINTSTRUCT); +using EndPaintFn = BOOL (WINAPI*)(HWND, const PAINTSTRUCT*); + +std::mutex hookMutex; +std::atomic installed{false}; +bool ownsMinHook = false; + +FillRectFn originalFillRect = nullptr; +CreateCompatibleDcFn originalCreateCompatibleDc = nullptr; +DeleteDcFn originalDeleteDc = nullptr; +BeginPaintFn originalBeginPaint = nullptr; +EndPaintFn originalEndPaint = nullptr; + +const std::array targets{{ + reinterpret_cast(&::BeginPaint), + reinterpret_cast(&::CreateCompatibleDC), + reinterpret_cast(&::FillRect), + reinterpret_cast(&::EndPaint), + reinterpret_cast(&::DeleteDC), +}}; + +HDC WINAPI ScopedBeginPaint(HWND window, LPPAINTSTRUCT paint) { + HDC const dc = originalBeginPaint != nullptr + ? originalBeginPaint(window, paint) + : nullptr; + OnWindowDcAcquired(window, dc); + return dc; +} + +HDC WINAPI ScopedCreateCompatibleDc(HDC sourceDc) { + HDC const compatibleDc = originalCreateCompatibleDc != nullptr + ? originalCreateCompatibleDc(sourceDc) + : nullptr; + OnCompatibleDcCreated(sourceDc, compatibleDc); + return compatibleDc; +} + +int WINAPI ScopedFillRect(HDC dc, const RECT* rect, HBRUSH brush) { + const int result = originalFillRect != nullptr + ? originalFillRect(dc, rect, brush) + : 0; + if(result != 0) { + static thread_local bool dispatching = false; + if(!dispatching) { + dispatching = true; + OnFillRect(dc, rect); + dispatching = false; + } + } + return result; +} + +BOOL WINAPI ScopedEndPaint(HWND window, const PAINTSTRUCT* paint) { + const BOOL result = originalEndPaint != nullptr + ? originalEndPaint(window, paint) + : FALSE; + if(paint != nullptr) { + OnDcDeleted(paint->hdc); + } + return result; +} + +BOOL WINAPI ScopedDeleteDc(HDC dc) { + OnDcDeleted(dc); + return originalDeleteDc != nullptr ? originalDeleteDc(dc) : FALSE; +} + +HRESULT HookFailure(MH_STATUS status) noexcept { + return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, + static_cast(0x300 + static_cast(status))); +} + +void ClearOriginals() noexcept { + originalFillRect = nullptr; + originalCreateCompatibleDc = nullptr; + originalDeleteDc = nullptr; + originalBeginPaint = nullptr; + originalEndPaint = nullptr; +} + +} // namespace + +HRESULT Install() { + std::lock_guard guard(hookMutex); + if(installed.load(std::memory_order_acquire)) { + return S_OK; + } + + MH_STATUS status = MH_Initialize(); + if(status != MH_OK) { + return HookFailure(status); + } + ownsMinHook = true; + + struct HookDefinition { + LPVOID target; + LPVOID detour; + LPVOID* original; + }; + const std::array definitions{{ + {targets[0], reinterpret_cast(&ScopedBeginPaint), + reinterpret_cast(&originalBeginPaint)}, + {targets[1], reinterpret_cast(&ScopedCreateCompatibleDc), + reinterpret_cast(&originalCreateCompatibleDc)}, + {targets[2], reinterpret_cast(&ScopedFillRect), + reinterpret_cast(&originalFillRect)}, + {targets[3], reinterpret_cast(&ScopedEndPaint), + reinterpret_cast(&originalEndPaint)}, + {targets[4], reinterpret_cast(&ScopedDeleteDc), + reinterpret_cast(&originalDeleteDc)}, + }}; + + std::size_t created = 0; + for(; created < definitions.size(); ++created) { + status = MH_CreateHook(definitions[created].target, + definitions[created].detour, definitions[created].original); + if(status != MH_OK) { + break; + } + } + if(created != definitions.size()) { + MH_Uninitialize(); + ownsMinHook = false; + ClearOriginals(); + return HookFailure(status); + } + + std::size_t enabled = 0; + for(; enabled < definitions.size(); ++enabled) { + status = MH_EnableHook(definitions[enabled].target); + if(status != MH_OK) { + break; + } + } + if(enabled != definitions.size()) { + while(enabled > 0) { + --enabled; + MH_DisableHook(definitions[enabled].target); + } + MH_Uninitialize(); + ownsMinHook = false; + ClearOriginals(); + return HookFailure(status); + } + + installed.store(true, std::memory_order_release); + return S_OK; +} + +void Remove() noexcept { + std::lock_guard guard(hookMutex); + if(!installed.exchange(false, std::memory_order_acq_rel)) { + return; + } + + for(std::size_t index = targets.size(); index > 0; --index) { + MH_DisableHook(targets[index - 1]); + } + if(ownsMinHook) { + MH_Uninitialize(); + } + ownsMinHook = false; + ClearOriginals(); +} + +bool IsInstalled() noexcept { + return installed.load(std::memory_order_acquire); +} + +} // namespace hooks +} // namespace background +} // namespace qttabbar diff --git a/QTHookLib/ScopedBackgroundHooks.h b/QTHookLib/ScopedBackgroundHooks.h new file mode 100644 index 0000000..aed1931 --- /dev/null +++ b/QTHookLib/ScopedBackgroundHooks.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace qttabbar { +namespace background { +namespace hooks { + +HRESULT Install(); +void Remove() noexcept; +bool IsInstalled() noexcept; + +} // namespace hooks +} // namespace background +} // namespace qttabbar diff --git a/QTHookLib/main.cpp b/QTHookLib/main.cpp index b1fcb8c..58951bc 100644 --- a/QTHookLib/main.cpp +++ b/QTHookLib/main.cpp @@ -35,6 +35,8 @@ #pragma comment(lib, "GdiPlus.lib") #include "CComPtr.h" +#include "ExplorerBackgroundRenderer.h" +#include "ScopedBackgroundHooks.h" #include "..\MinHook\MinHook.h" @@ -198,6 +200,7 @@ struct CallbackStruct { bool (*fpNewWindow)(LPCITEMIDLIST pIDL); }; CallbackStruct callbacks; +volatile LONG g_backgroundInitializationState = 0; // Other stuff HMODULE hModAutomation = NULL; @@ -535,11 +538,14 @@ extern "C" __declspec(dllexport) int InitializeBackground(CallbackStruct* cb); extern "C" __declspec(dllexport) int Dispose(); extern "C" __declspec(dllexport) int InitShellBrowserHook(IShellBrowser* psb); extern "C" __declspec(dllexport) int RegisterBackgroundWindow(HWND hwnd); +extern "C" __declspec(dllexport) int UpdateBackgroundWindow(HWND hwnd, LPCWSTR path); BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_DETACH: - return Dispose(); + // Hook shutdown is performed explicitly by HookLibraryBridge before + // FreeLibrary. Avoid MinHook/GDI cleanup while the loader lock is held. + break; case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: @@ -670,37 +676,36 @@ int Initialize(CallbackStruct* cb) { int InitializeBackground(CallbackStruct* cb) { - volatile static long initialized; - if (InterlockedIncrement(&initialized) != 1) { - initialized = 1; + const LONG state = InterlockedCompareExchange(&g_backgroundInitializationState, 1, 0); + if(state == 2) { return MH_OK; } + if(state != 0) { + return HRESULT_FROM_WIN32(ERROR_BUSY); + } + if(!cb) { + InterlockedExchange(&g_backgroundInitializationState, 0); + return E_INVALIDARG; + } - if (!cb) return E_INVALIDARG; - - srand((int)time(0)); - MH_STATUS ret = MH_Initialize(); - if (ret != MH_OK && ret != MH_ERROR_ALREADY_INITIALIZED) return ret; - - callbacks = *cb; - if (m_gdiplusToken == 0) { - Gdiplus::GdiplusStartupInput startupInput; - if (Gdiplus::GdiplusStartup(&m_gdiplusToken, &startupInput, nullptr) != Gdiplus::Ok) { - m_gdiplusToken = 0; - return ERROR_DLL_INIT_FAILED; - } + // Build and validate the complete immutable image snapshot before any + // process-wide drawing function is intercepted. + const HRESULT rendererResult = qttabbar::background::Initialize(g_hModule); + if(rendererResult != S_OK) { + InterlockedExchange(&g_backgroundInitializationState, 0); + return rendererResult; } - // Background-only mode intentionally avoids the legacy Shell, window capture, - // navigation and drag/drop hooks. These four hooks are the complete drawing - // path used by a DirectUIHWND registered through RegisterBackgroundWindow. - CREATE_HOOK(&DestroyWindow, DestroyWindow) - CREATE_HOOK(&BeginPaint, BeginPaint) - CREATE_HOOK(&FillRect, FillRect) - CREATE_HOOK(&CreateCompatibleDC, CreateCompatibleDC) - LoadSettings(true); + const HRESULT hookResult = qttabbar::background::hooks::Install(); + if(FAILED(hookResult)) { + qttabbar::background::Shutdown(); + InterlockedExchange(&g_backgroundInitializationState, 0); + return hookResult; + } - return MH_OK; + callbacks = *cb; + InterlockedExchange(&g_backgroundInitializationState, 2); + return S_OK; } @@ -740,6 +745,9 @@ int InitShellBrowserHook(IShellBrowser* psb) { } int RegisterBackgroundWindow(HWND hwnd) { + if(qttabbar::background::IsActive()) { + return qttabbar::background::RegisterWindow(hwnd, nullptr); + } if (!IsWindow(hwnd)) return E_INVALIDARG; wchar_t className[64] = {}; @@ -759,8 +767,20 @@ int RegisterBackgroundWindow(HWND hwnd) { return S_OK; } +int UpdateBackgroundWindow(HWND hwnd, LPCWSTR path) { + if(!qttabbar::background::IsActive()) { + return E_NOTIMPL; + } + return qttabbar::background::UpdateWindow(hwnd, path); +} + int Dispose() { - // Uninitialize MinHook. + if(qttabbar::background::IsActive()) { + qttabbar::background::hooks::Remove(); + qttabbar::background::Shutdown(); + InterlockedExchange(&g_backgroundInitializationState, 0); + } + // Uninitialize MinHook after renderer state is no longer reachable by hooks. MH_Uninitialize(); // Free the Automation library @@ -916,7 +936,6 @@ int WINAPI DetourFillRect(HDC hDC, const RECT* lprc, HBRUSH hbr) { int ret = fpFillRect(hDC, lprc, hbr); - // Box1(L"DetourFillRect in "); auto iter = m_duiList.find(GetCurrentThreadId()); if (iter != m_duiList.end()) { @@ -1084,7 +1103,6 @@ HDC WINAPI DetourCreateCompatibleDC(HDC hDC) return retDC; } - // The purpose of this hook is to intercept the creation of the NameSpaceTreeControl object, and // send a reference to the control to QTTabBar. We can use this reference to hit test the // control, which is how opening new tabs from middle-click works. diff --git a/QTTabBar.Tests/QTTabBar.Tests.csproj b/QTTabBar.Tests/QTTabBar.Tests.csproj index 8115409..198072e 100644 --- a/QTTabBar.Tests/QTTabBar.Tests.csproj +++ b/QTTabBar.Tests/QTTabBar.Tests.csproj @@ -4,6 +4,7 @@ QTTabBar.Tests QTTabBar.Tests false + true @@ -14,5 +15,7 @@ + + diff --git a/QTTabBar.Tests/TabButtonImageGeometryTests.cs b/QTTabBar.Tests/TabButtonImageGeometryTests.cs new file mode 100644 index 0000000..30d5b89 --- /dev/null +++ b/QTTabBar.Tests/TabButtonImageGeometryTests.cs @@ -0,0 +1,45 @@ +using System.Drawing; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace QTTabBarLib.Tests { + [TestClass] + public class TabButtonImageGeometryTests { + [TestMethod] + public void CloseButtonFrames_SplitsHorizontalFourStateStrip() { + Rectangle[] frames = TabButtonImageGeometry.GetCloseButtonFrames(new Size(64, 16)); + + Assert.AreEqual(4, frames.Length); + Assert.AreEqual(new Rectangle(32, 0, 16, 16), frames[2]); + } + + [TestMethod] + public void CloseButtonFrames_SplitsVerticalFourStateStrip() { + Rectangle[] frames = TabButtonImageGeometry.GetCloseButtonFrames(new Size(12, 48)); + + Assert.AreEqual(4, frames.Length); + Assert.AreEqual(new Rectangle(0, 36, 12, 12), frames[3]); + } + + [TestMethod] + public void CloseButtonFrames_KeepsSingleImageIntact() { + Rectangle[] frames = TabButtonImageGeometry.GetCloseButtonFrames(new Size(20, 16)); + + Assert.AreEqual(1, frames.Length); + Assert.AreEqual(new Rectangle(0, 0, 20, 16), frames[0]); + } + + [TestMethod] + public void FitWithin_DownscalesWithoutUpscaling() { + Assert.AreEqual(new Size(20, 10), + TabButtonImageGeometry.FitWithin(new Size(40, 20), new Size(20, 20))); + Assert.AreEqual(new Size(8, 8), + TabButtonImageGeometry.FitWithin(new Size(8, 8), new Size(20, 20))); + } + + [TestMethod] + public void CenterWithin_CentersImageInHitTarget() { + Assert.AreEqual(new Rectangle(14, 24, 12, 12), + TabButtonImageGeometry.CenterWithin(new Rectangle(10, 20, 20, 20), new Size(12, 12))); + } + } +} diff --git a/QTTabBar.Tests/TabSkinGeometryTests.cs b/QTTabBar.Tests/TabSkinGeometryTests.cs new file mode 100644 index 0000000..0c69189 --- /dev/null +++ b/QTTabBar.Tests/TabSkinGeometryTests.cs @@ -0,0 +1,62 @@ +using System.Drawing; +using System.Windows.Forms; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using QTTabBarLib; + +namespace QTTabBar.Tests { + [TestClass] + public class TabSkinGeometryTests { + private static int MapCoordinate(int coordinate, int destinationLength, + int sourceLength, int leadingMargin, int trailingMargin) { + return TabSkinGeometry.MapNineSliceCoordinate( + coordinate, + destinationLength, + sourceLength, + leadingMargin, + trailingMargin); + } + + [TestMethod] + public void TabAdvance_SubtractsOverlapAndKeepsPositiveWidth() { + Assert.AreEqual(100, TabSkinGeometry.GetTabAdvance(120, 20)); + Assert.AreEqual(1, TabSkinGeometry.GetTabAdvance(10, 20)); + } + + [TestMethod] + public void ContentMargins_InsetTheContentRectangle() { + Rectangle result = TabSkinGeometry.ApplyContentMargins( + new Rectangle(0, 0, 120, 40), + new Padding(5, 3, 7, 4)); + Assert.AreEqual(new Rectangle(5, 3, 108, 33), result); + } + + [TestMethod] + public void ContentMargins_KeepAtLeastOneDrawablePixel() { + Rectangle result = TabSkinGeometry.ApplyContentMargins( + new Rectangle(10, 20, 4, 3), + new Padding(99)); + Assert.AreEqual(1, result.Width); + Assert.AreEqual(1, result.Height); + } + + [TestMethod] + public void NineSliceMapping_PreservesFixedEdges() { + Assert.AreEqual(0, MapCoordinate(0, 100, 20, 4, 4)); + Assert.AreEqual(3, MapCoordinate(3, 100, 20, 4, 4)); + Assert.AreEqual(16, MapCoordinate(96, 100, 20, 4, 4)); + Assert.AreEqual(19, MapCoordinate(99, 100, 20, 4, 4)); + } + + [TestMethod] + public void NineSliceMapping_ScalesCenterRegion() { + Assert.AreEqual(10, MapCoordinate(50, 100, 20, 4, 4)); + } + + [TestMethod] + public void NineSliceMapping_ClampsMarginsAndCoordinates() { + Assert.AreEqual(0, MapCoordinate(-10, 5, 3, 99, 99)); + Assert.AreEqual(2, MapCoordinate(20, 5, 3, 99, 99)); + Assert.AreEqual(0, MapCoordinate(0, 0, 0, 0, 0)); + } + } +} diff --git a/QTTabBar/AbstractListView.cs b/QTTabBar/AbstractListView.cs index d877c04..8cded26 100644 --- a/QTTabBar/AbstractListView.cs +++ b/QTTabBar/AbstractListView.cs @@ -41,7 +41,8 @@ public virtual int GetHotItem() { return -1; } - public virtual void HandleF2() { + public virtual bool HandleF2() { + return false; } public virtual void HandleShiftKey() { diff --git a/QTTabBar/AutoLoader.cs b/QTTabBar/AutoLoader.cs index 6b25683..38b3166 100644 --- a/QTTabBar/AutoLoader.cs +++ b/QTTabBar/AutoLoader.cs @@ -28,8 +28,6 @@ namespace QTTabBarLib { [Guid("D2BF470E-ED1C-487F-A777-2BD8835EB6CE"), ComVisible(true), ClassInterface(ClassInterfaceType.None)] public class AutoLoader : IObjectWithSite { private IWebBrowser2 explorer; - private Timer activationTimer; - private int activationAttempts; private static int crashDiagnosticsInstalled; private const string BHOKEYNAME = @"Software\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\"; private const int E_FAIL = unchecked((int)0x80004005); @@ -63,7 +61,7 @@ public int SetSite(object site) { QTUtility2.log("SetSite"); explorer = site as IWebBrowser2; if(explorer == null) { - StopActivationTimer(); + return 0; } // QTUtility2.flog("QTTabBar AutoLoader SetSite "); /*if(explorer == null || Process.GetCurrentProcess().ProcessName == "iexplore") { @@ -111,63 +109,30 @@ public int GetSite(ref Guid guid, out object ppvSite) { } private void ActivateIt() { - string installDateString; - DateTime installDate; - string minDate = DateTime.MinValue.ToString(); - using(RegistryKey key = Registry.LocalMachine.OpenSubKey(RegConst.Root)) { - installDateString = key == null ? minDate : (string)key.GetValue("InstallDate", minDate); - installDate = DateTime.Parse(installDateString); - } - using(RegistryKey key = Registry.CurrentUser.CreateSubKey(RegConst.Root)) { - DateTime lastActivation = DateTime.Parse((string)key.GetValue("ActivationDate", minDate)); - if(installDate.CompareTo(lastActivation) <= 0) return; - - key.SetValue("ActivationDate", installDateString); - QTUtility2.flog("QTTabBar AutoLoader add ActivationDate"); - } - - ScheduleFirstActivation(); - } - - private void ScheduleFirstActivation() { - if(activationTimer != null || explorer == null) return; - - activationAttempts = 0; - activationTimer = new Timer { Interval = 250 }; - activationTimer.Tick += activationTimer_Tick; - activationTimer.Start(); - QTUtility2.flog("QTTabBar AutoLoader first activation scheduled"); - } - - private void activationTimer_Tick(object sender, EventArgs e) { - if(explorer == null || InstanceManager.GetThreadTabBar() != null) { - StopActivationTimer(); - return; - } - try { - activationAttempts++; - object tabBarClsid = typeof(QTTabBarClass).GUID.ToString("B"); - object show = true; - object size = null; - explorer.ShowBrowserBar(ref tabBarClsid, ref show, ref size); - QTUtility2.flog("QTTabBar AutoLoader first activation ShowBrowserBar attempt " + activationAttempts); + string installDateString; + DateTime installDate; + string minDate = DateTime.MinValue.ToString(); + using(RegistryKey key = Registry.LocalMachine.OpenSubKey(RegConst.Root)) { + installDateString = key == null ? minDate : (string)key.GetValue("InstallDate", minDate); + installDate = DateTime.Parse(installDateString); + } + using(RegistryKey key = Registry.CurrentUser.CreateSubKey(RegConst.Root)) { + DateTime lastActivation = DateTime.Parse((string)key.GetValue("ActivationDate", minDate)); + if(installDate.CompareTo(lastActivation) <= 0) return; + + object tabBarClsid = typeof(QTTabBarClass).GUID.ToString("B"); + object show = true; + object size = null; + explorer.ShowBrowserBar(ref tabBarClsid, ref show, ref size); + QTUtility2.flog("QTTabBar AutoLoader ShowBrowserBar succeeded"); + key.SetValue("ActivationDate", installDateString); + QTUtility2.flog("QTTabBar AutoLoader add ActivationDate"); + } } catch(Exception ex) { - QTUtility2.MakeErrorLog(ex, "AutoLoader first activation ShowBrowserBar"); + QTUtility2.MakeErrorLog(ex, "AutoLoader ShowBrowserBar"); } - - if(activationAttempts >= 40) { - StopActivationTimer(); - } - } - - private void StopActivationTimer() { - if(activationTimer == null) return; - activationTimer.Stop(); - activationTimer.Tick -= activationTimer_Tick; - activationTimer.Dispose(); - activationTimer = null; } } } diff --git a/QTTabBar/BreadcrumbBar.cs b/QTTabBar/BreadcrumbBar.cs index fd1bc08..3f396c7 100644 --- a/QTTabBar/BreadcrumbBar.cs +++ b/QTTabBar/BreadcrumbBar.cs @@ -38,6 +38,10 @@ class BreadcrumbBar { private NativeWindowController ParentController; private IntPtr hdpa = IntPtr.Zero; + public IntPtr Handle { + get { return BreadcrumbController.Handle; } + } + public BreadcrumbBar(IntPtr hwnd) { BreadcrumbController = new NativeWindowController(hwnd); BreadcrumbController.MessageCaptured += BreadcrumbController_MessageCaptured; @@ -79,7 +83,7 @@ private bool ParentController_MessageCaptured(ref Message msg) { return true; } else if(msg.Msg == WM.COMMAND) { - if(hdpa != IntPtr.Zero && ItemClicked != null && Control.ModifierKeys != Keys.None) { + if(hdpa != IntPtr.Zero && ItemClicked != null) { int itemId = PInvoke.LoWord(msg.WParam.ToInt32()); int idx = CommandToIndex(itemId); if(idx >= 0 && idx < ButtonCount()) { @@ -95,16 +99,56 @@ private bool ParentController_MessageCaptured(ref Message msg) { // Catch middle clicks private bool BreadcrumbController_MessageCaptured(ref Message msg) { if(msg.Msg == WM.MBUTTONUP) { - if(hdpa != IntPtr.Zero && ItemClicked != null) { - int idx = HitTest(QTUtility2.PointFromLPARAM(msg.LParam)); - if(idx >= 0 && idx <= ButtonCount()) { - DoItemClick(IndexToCommand(idx), Control.ModifierKeys, true); - } + Point pt = QTUtility2.PointFromLPARAM(msg.LParam); + if(TryHandleMiddleClick(pt, Control.ModifierKeys, false)) { + return true; } } return false; } + public bool TryHandleMiddleClick(Point point, Keys modKeys, bool screenCoordinates) { + if(hdpa == IntPtr.Zero || ItemClicked == null) { + return false; + } + int idx; + int count; + int ancestorIndex; + if(!TryGetHitAncestorIndex(point, screenCoordinates, out ancestorIndex, out idx, out count)) { + return false; + } + return DoItemClick(IndexToCommand(idx), modKeys, true); + } + + public bool TryGetHitAncestorIndex(Point point, bool screenCoordinates, out int ancestorIndex) { + int idx; + int count; + return TryGetHitAncestorIndex(point, screenCoordinates, out ancestorIndex, out idx, out count); + } + + public bool TryGetHitAncestorIndex(Point point, bool screenCoordinates, out int ancestorIndex, out int idx, out int count) { + return TryGetHitAncestorIndex(BreadcrumbController.Handle, point, screenCoordinates, out ancestorIndex, out idx, out count); + } + + public static bool TryGetHitAncestorIndex(IntPtr hwnd, Point point, bool screenCoordinates, out int ancestorIndex, out int idx, out int count) { + ancestorIndex = -1; + idx = -1; + count = 0; + if(hwnd == IntPtr.Zero || !PInvoke.IsWindow(hwnd)) { + return false; + } + if(screenCoordinates && !PInvoke.ScreenToClient(hwnd, ref point)) { + return false; + } + count = (int)PInvoke.SendMessage(hwnd, TB_BUTTONCOUNT, IntPtr.Zero, IntPtr.Zero); + idx = (int)PInvoke.SendMessage(hwnd, TB_HITTEST, IntPtr.Zero, ref point); + if(idx < 0 || idx >= count) { + return false; + } + ancestorIndex = count - 1 - idx; + return ancestorIndex >= 0; + } + private bool DoItemClick(int itemId, Keys modKeys, bool middle) { IntPtr lParam = GetButtonLParam(itemId); IntPtr ptr = PInvoke.DPA_GetPtr(hdpa, (int)lParam); // This pointer is NOT AddRef'd. Do not Release it!! diff --git a/QTTabBar/Config.cs b/QTTabBar/Config.cs index 7c6f630..afb2307 100644 --- a/QTTabBar/Config.cs +++ b/QTTabBar/Config.cs @@ -1,8 +1,8 @@ /* File Info * Author: indiff - * CreateTime: 2021/1/51:58:08 + * CreateTime: 2021/1/5����1:58:08 * LastEditor: indiff - * ModifyTime: 2021/8/287:47:22 + * ModifyTime: 2021/8/28����7:47:22 * Description: */ // This file is part of QTTabBar, a shell extension for Microsoft @@ -66,7 +66,7 @@ public static Font ToFont(XmlSerializableFont xmlSerializableFont) { } /* - * @: ǩλ + * @����: ��ǩλ�� */ public enum TabPos { Rightmost, @@ -77,7 +77,7 @@ public enum TabPos { } /* - * @: ģʽ + * @����: ����ģʽ */ public enum StretchMode { Full, @@ -85,8 +85,21 @@ public enum StretchMode { Tile, } + public enum TabDropDefaultEffect { + SystemDefault, + Copy, + Move, + Link, + } + + public enum TabDropHoverAction { + None, + SelectTab, + ShowSubfolderMenu, + } + /* - * @: Ŀ + * @����: ����Ŀ�� */ public enum MouseTarget { Anywhere, @@ -94,11 +107,12 @@ public enum MouseTarget { TabBarBackground, FolderLink, ExplorerItem, - ExplorerBackground + ExplorerBackground, + Breadcrumbs } /* - * @: + * @����: ����� */ [Flags] public enum MouseChord { @@ -115,7 +129,7 @@ public enum MouseChord { } /* - * @: 󶨶 + * @����: �󶨶��� */ // WARNING // reordering these will break existing settings. @@ -146,7 +160,7 @@ public enum BindAction BrowseFolder, CreateNewGroup, /***** add by indiff end *****/ - // AddToGroup, // ǩ һ² + // AddToGroup, // ��������ǩ�� �� ����һ���²��������� ShowOptions, ShowToolbarMenu, ShowTabMenuCurrent, @@ -228,18 +242,19 @@ public enum BindAction [Serializable] public class Config { // Shortcuts to the loaded config, for convenience. - public static _Window Window { get { return ConfigManager.LoadedConfig.window; } } /*Ϊ*/ - public static _Tabs Tabs { get { return ConfigManager.LoadedConfig.tabs; } } /*ǩΪ*/ - public static _Tweaks Tweaks { get { return ConfigManager.LoadedConfig.tweaks; } } /**/ - public static _Tips Tips { get { return ConfigManager.LoadedConfig.tips; } } /*Ԥʾ*/ - public static _Misc Misc { get { return ConfigManager.LoadedConfig.misc; } } /*ѡ*/ - public static _Skin Skin { get { return ConfigManager.LoadedConfig.skin; } } /*ǩ*/ - public static _BBar BBar { get { return ConfigManager.LoadedConfig.bbar; } } /*ťѡ*/ - public static _Mouse Mouse { get { return ConfigManager.LoadedConfig.mouse; } } /**/ - public static _Keys Keys { get { return ConfigManager.LoadedConfig.keys; } } /*ݲ*/ - public static _Plugin Plugin { get { return ConfigManager.LoadedConfig.plugin; } } /**/ - public static _Lang Lang { get { return ConfigManager.LoadedConfig.lang; } } /**/ - public static _Desktop Desktop { get { return ConfigManager.LoadedConfig.desktop; } } /*Ϣ*/ + public static _Window Window { get { return ConfigManager.LoadedConfig.window; } } /*������Ϊ*/ + public static _Tabs Tabs { get { return ConfigManager.LoadedConfig.tabs; } } /*��ǩ��Ϊ*/ + public static _Tweaks Tweaks { get { return ConfigManager.LoadedConfig.tweaks; } } /*��������*/ + public static _Tips Tips { get { return ConfigManager.LoadedConfig.tips; } } /*Ԥ����ʾ*/ + public static _Misc Misc { get { return ConfigManager.LoadedConfig.misc; } } /*����ѡ��*/ + public static _Skin Skin { get { return ConfigManager.LoadedConfig.skin; } } /*��ǩ���*/ + public static _DragDrop DragDrop { get { return ConfigManager.LoadedConfig.dragdrop; } } + public static _BBar BBar { get { return ConfigManager.LoadedConfig.bbar; } } /*��ťѡ��*/ + public static _Mouse Mouse { get { return ConfigManager.LoadedConfig.mouse; } } /*������*/ + public static _Keys Keys { get { return ConfigManager.LoadedConfig.keys; } } /*��ݲ���*/ + public static _Plugin Plugin { get { return ConfigManager.LoadedConfig.plugin; } } /*�������*/ + public static _Lang Lang { get { return ConfigManager.LoadedConfig.lang; } } /*��������*/ + public static _Desktop Desktop { get { return ConfigManager.LoadedConfig.desktop; } } /*������Ϣ*/ public _Window window { get; set; } public _Tabs tabs { get; set; } @@ -247,6 +262,7 @@ public class Config { public _Tips tips { get; set; } public _Misc misc { get; set; } public _Skin skin { get; set; } + public _DragDrop dragdrop { get; set; } public _BBar bbar { get; set; } public _Mouse mouse { get; set; } public _Keys keys { get; set; } @@ -261,6 +277,7 @@ public Config() { tips = new _Tips(); misc = new _Misc(); skin = new _Skin(); + dragdrop = new _DragDrop(); bbar = new _BBar(); mouse = new _Mouse(); keys = new _Keys(); @@ -272,7 +289,7 @@ public Config() { [Serializable] public class _Window { public bool CaptureNewWindows { get; set; } - public bool CaptureWeChatSelection { get; set; } // Ƿ񲶻΢šqqĴļѡ״̬ + public bool CaptureWeChatSelection { get; set; } // �Ƿ񲶻�΢�š�qq�������Ĵ��ļ�ѡ��״̬ public bool RestoreSession { get; set; } public bool RestoreOnlyLocked { get; set; } public bool CloseBtnClosesUnlocked { get; set; } @@ -310,16 +327,16 @@ public _Window() { CloseBtnClosesSingleTab = true; TrayOnClose = false; TrayOnMinimize = false; - // ĬϹرԶhook + // Ĭ�Ϲر��Զ�����hook AutoHookWindow = false; // string idl = Environment.OSVersion.Version >= new Version(6, 1) // ? "::{031E4825-7B94-4DC3-B131-E946B44C8DD5}" // Libraries // : "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}"; // Computer - string idl = "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}"; // ҵĵԣ Ĭϴ + string idl = "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}"; // �ҵĵ��ԣ� Ĭ�ϴ� using(IDLWrapper w = new IDLWrapper(idl)) { DefaultLocation = w.IDL; } - // ʾʧܵʾ + // ����ʾ����ʧ�ܵ���ʾ�� ShowFailNavMsg = false; } } @@ -362,23 +379,40 @@ public _Tabs() { ActiveTabOnBottomRow = true;*/ /* qwop's default's value.*/ - NewTabPosition = TabPos.Rightmost; // ±ǩҲ - NextAfterClosed = TabPos.LastActive; // رձǩʱл - ActivateNewTab = true; // Ƿл±ǩ - NeverOpenSame = true; // ±ǩ - RenameAmbTabs = true; // ģǩ - DragOverTabOpensSDT = false; // קǩIJ - ShowFolderIcon = true; // ʾͼ - ShowSubDirTipOnTab = false; // ļʾӱǩ - ShowDriveLetters = false; // ʾַ - ShowCloseButtons = true; // ʾرհť - CloseBtnsWithAlt = false; // ֻaltסйر - CloseBtnsOnHover = false; // ر - ShowNavButtons = false; // ڱǩʾť ( (Ĭùرհťʾ)ǩҲťܷһѡԿǷȥء#28 - NavButtonsOnRight = true; // ұ - MultipleTabRows = true; // бǩ - ActiveTabOnBottomRow = false; // ʼսǩڵײ - NeedPlusButton = true; // Ƿʾɫǩť + NewTabPosition = TabPos.Rightmost; // �±�ǩ�����Ҳ� + NextAfterClosed = TabPos.LastActive; // �رձ�ǩʱ���л��� + ActivateNewTab = true; // �Ƿ������л����±�ǩ + NeverOpenSame = true; // �����±�ǩ + RenameAmbTabs = true; // ģ����������ǩ + DragOverTabOpensSDT = false; // ��ק��ǩ��IJ��� + ShowFolderIcon = true; // ��ʾ������ͼ�� + ShowSubDirTipOnTab = false; // ���ļ�����ʾ�ӱ�ǩ + ShowDriveLetters = false; // ��ʾ�������ַ� + ShowCloseButtons = true; // ��ʾ�رհ�ť + CloseBtnsWithAlt = false; // ֻ��alt��ס���йر� + CloseBtnsOnHover = false; // ����������ر� + ShowNavButtons = false; // �ڱ�ǩ��ʾ������ť ( (Ĭ�����ùرհ�ť��ʾ)��ǩ���Ҳ��������ť�ܷ�����һ��ѡ���Կ����Ƿ�ȥ���ء�#28�� + NavButtonsOnRight = true; // �����ұ� + MultipleTabRows = true; // �������б�ǩ + ActiveTabOnBottomRow = false; // ʼ�ս����ǩ���ڵײ��� + NeedPlusButton = true; // �Ƿ���ʾ��ɫ������ǩ��ť + } + } + + [Serializable] + public class _DragDrop { + public bool TabDropTargetEnabled { get; set; } + public TabDropDefaultEffect TabDropDefaultEffect { get; set; } + public TabDropHoverAction TabDropHoverAction { get; set; } + public int TabDropHoverTime { get; set; } + public bool TabDropAcceptSameTabBar { get; set; } + + public _DragDrop() { + TabDropTargetEnabled = true; + TabDropDefaultEffect = TabDropDefaultEffect.SystemDefault; + TabDropHoverAction = TabDropHoverAction.ShowSubfolderMenu; + TabDropHoverTime = 700; + TabDropAcceptSameTabBar = true; } } @@ -416,24 +450,24 @@ public _Tweaks() { /* qwop's default value.*/ if (QTUtility.IsWin7) { - AlwaysShowHeaders = true; // ʾб + AlwaysShowHeaders = true; // ��ʾ�б��� } else { - AlwaysShowHeaders = false; // ʾб + AlwaysShowHeaders = false; // ��ʾ�б��� } - RedirectLibraryFolders = false; // ʹÿļ - KillExtWhileRenaming = true; // ʱ򣬲ʹչ - F2Selection = false; // F2ѡ - WrapArrowKeySelection = true; // ʹüͷʱѡļ - BackspaceUpLevel = true; // backupspace صһĿ¼ - HorizontalScroll = true; // ͬʱסshiftˮƽ - ForceSysListView = false; // þɰбͼؼ - ToggleFullRowSelect = QTUtility.IsXP; // ϸͼѡ - DetailsGridLines = false; // - AlternateRowColors = false;// ɫ - AltRowForegroundColor = SystemColors.WindowText; // ǰɫ - AltRowBackgroundColor = QTUtility2.MakeColor(0xfaf5f1); // ɫ + RedirectLibraryFolders = false; // ʹ�ÿ��ļ��� + KillExtWhileRenaming = true; // ������ʱ�򣬲�ʹ����չ�� + F2Selection = false; // ����F2����������ѡ�� + WrapArrowKeySelection = true; // ʹ�ü�ͷ��ʱ����ѡ���ļ��� + BackspaceUpLevel = true; // backupspace ���ص���һ��Ŀ¼ + HorizontalScroll = true; // ͬʱ��סshift����ˮƽ���� + ForceSysListView = false; // ���þɰ��б���ͼ�ؼ� + ToggleFullRowSelect = QTUtility.IsXP; // ��ϸ��ͼѡ������ + DetailsGridLines = false; // ������ + AlternateRowColors = false;// ��������ɫ + AltRowForegroundColor = SystemColors.WindowText; // ǰ��ɫ + AltRowBackgroundColor = QTUtility2.MakeColor(0xfaf5f1); // ����ɫ } } @@ -448,6 +482,9 @@ public class _Tips { public bool ShowPreviewInfo { get; set; } public int PreviewMaxWidth { get; set; } public int PreviewMaxHeight { get; set; } + public int PreviewDelay { get; set; } + public int PreviewOpacity { get; set; } + public int PreviewCacheCapacity { get; set; } public Font PreviewFont { get; set; } public List TextExt { get; set; } public List ImageExt { get; set; } @@ -466,23 +503,26 @@ public _Tips() { TextExt = new List {".txt", ".ini", ".inf" ,".cs", ".log", ".js", ".vbs"}; ImageExt = ThumbnailTooltipForm.MakeDefaultImgExts();*/ - ShowSubDirTips = true; // ǷʾĿ¼ʾ - SubDirTipsPreview = true; // Ŀ¼ʾԤ - SubDirTipsFiles = true; // Ŀ¼ʾļ - SubDirTipsWithShift = false ; // shiftʾĿ¼ + ShowSubDirTips = true; // �Ƿ���ʾ��Ŀ¼��ʾ + SubDirTipsPreview = true; // ��Ŀ¼��ʾԤ�� + SubDirTipsFiles = true; // ��Ŀ¼��ʾ�ļ� + SubDirTipsWithShift = false ; // ����shift��������ʾ��Ŀ¼ ShowTooltipPreviews = true; - ShowPreviewInfo = true; // ļԤ - ShowPreviewsWithShift = true; // shift, ļԤ + ShowPreviewInfo = true; // �����ļ�Ԥ�� + ShowPreviewsWithShift = true; // ����shift������, �����ļ�Ԥ�� - // ԤĿ + // Ԥ���Ŀ��� PreviewMaxWidth = 600; PreviewMaxHeight = 400; + PreviewDelay = 300; + PreviewOpacity = 100; + PreviewCacheCapacity = 128; // PreviewMaxWidth = 512; // PreviewMaxHeight = 256; - // + // �������� PreviewFont = Control.DefaultFont; PreviewFont = new Font(Control.DefaultFont.FontFamily, 9f); - // ıչ yml yaml ļ֧ + // �ı�������չ�� ���� yml yaml �ļ�֧�� TextExt = new List { ".txt",".rtf" ,".ini", ".inf",".properties", ".ruleset", ".settings" @@ -508,7 +548,7 @@ public _Tips() { ,".jsp", ".asp", ".php",".aspx" ,".resx",".xaml", ".config", ".manifest", ".csproj", ".vbproj" }; - // Ĭϵͼչ + // ����Ĭ�ϵ�ͼ����չ�� ImageExt = ThumbnailTooltipForm.MakeDefaultImgExts(); } } @@ -533,9 +573,9 @@ public _Misc() { FileHistoryCount = 15; NetworkTimeout = 0; AutoUpdate = true; - // ĬϹر + // Ĭ�Ϲر��������� SoundBox = false; - // Ĭϲ־ + // Ĭ�ϲ�������־���� EnableLog = false; } } @@ -546,6 +586,14 @@ public class _Skin { public string TabImageFile { get; set; } public bool UsePlusButtonImage { get; set; } public string PlusButtonImageFile { get; set; } + public bool UseCloseButtonImage { get; set; } + public string CloseButtonImageFile { get; set; } + public int CloseButtonImageOffsetX { get; set; } + public int CloseButtonImageOffsetY { get; set; } + public bool UseLockIconImage { get; set; } + public string LockIconImageFile { get; set; } + public int LockIconImageOffsetX { get; set; } + public int LockIconImageOffsetY { get; set; } public Padding TabSizeMargin { get; set; } public Padding TabContentMargin { get; set; } public int OverlapPixels { get; set; } @@ -563,6 +611,9 @@ public class _Skin { public Color TabShadInactiveColor { get; set; } public Color TabShadHotColor { get; set; } public bool TabTitleShadows { get; set; } + public bool TabActiveTitleShadow { get; set; } + public bool TabInactiveTitleShadow { get; set; } + public bool TabHotTitleShadow { get; set; } public bool TabTextCentered { get; set; } public bool UseRebarBGColor { get; set; } public Color RebarColor { get; set; } @@ -602,6 +653,9 @@ public _Skin() { TabShadInactiveColor = Color.White; TabShadHotColor = Color.White; TabTitleShadows = false; + TabActiveTitleShadow = true; + TabInactiveTitleShadow = true; + TabHotTitleShadow = true; TabTextCentered = false; UseRebarBGColor = false; RebarColor = Color.Gray; @@ -613,20 +667,28 @@ public _Skin() { ActiveTabInBold = false;*/ /* qwop's default value. */ - UseTabSkin = false; // ǩ - TabImageFile = ""; // ǩļ + UseTabSkin = false; // ��ǩ���� + TabImageFile = ""; // ��ǩ�����ļ� UsePlusButtonImage = false; PlusButtonImageFile = ""; - TabSizeMargin = Padding.Empty; // ñԵ - TabContentMargin = Padding.Empty; // ݱԵ + UseCloseButtonImage = false; + CloseButtonImageFile = ""; + CloseButtonImageOffsetX = 0; + CloseButtonImageOffsetY = 0; + UseLockIconImage = false; + LockIconImageFile = ""; + LockIconImageOffsetX = 0; + LockIconImageOffsetY = 0; + TabSizeMargin = Padding.Empty; // ���ñ�Ե + TabContentMargin = Padding.Empty; // ���ݱ�Ե OverlapPixels = 0; // HitTestTransparent = false; - TabHeight = 30; // ߶ - TabMinWidth = 100; // С - TabMaxWidth = 200; // + TabHeight = 30; // �߶� + TabMinWidth = 100; // ��С���� + TabMaxWidth = 200; // ������ FixedWidthTabs = false; TabTextFont = new Font(Control.DefaultFont.FontFamily, 9f); - ToolBarTextColor = Color.Black; // ıɫ + ToolBarTextColor = Color.Black; // �������ı���ɫ TabTextActiveColor = Color.Black; TabTextInactiveColor = Color.Black; TabTextHotColor = Color.Black; @@ -636,14 +698,17 @@ public _Skin() { TabShadHotColor = Color.WhiteSmoke; RebarColor = Color.WhiteSmoke; // RebarColor = Color.FromArgb(230,230,230); - // ñǩɫ + // ���ñ�ǩ����ɫ // RebarColor = Color.FromArgb(245, 246, 247); - TabTitleShadows = false; // ǩıӰǷ - TabTextCentered = true; // ǩıǷ - UseRebarBGColor = true; // Ƿñɫ - UseRebarImage = false; // Ƿ񹤾ԶͼƬ,ԶͼƬ - RebarStretchMode = StretchMode.Tile; // ַʽ - RebarImageFile = ""; // ԶͼƬ + TabTitleShadows = false; // ��ǩ�ı���Ӱ�Ƿ����� + TabActiveTitleShadow = true; + TabInactiveTitleShadow = true; + TabHotTitleShadow = true; + TabTextCentered = true; // ��ǩ�ı��Ƿ���� + UseRebarBGColor = true; // �Ƿ��������ñ�����ɫ + UseRebarImage = false; // �Ƿ񹤾����Զ���ͼƬ,�����Զ���ͼƬ + RebarStretchMode = StretchMode.Tile; // ���ַ�ʽ + RebarImageFile = ""; // �������Զ���ͼƬ RebarImageSeperateBars = false; RebarSizeMargin = Padding.Empty; UseFluentExplorerGlass = true; @@ -654,12 +719,12 @@ public _Skin() { FluentGlassApplyBorderColor = true; FluentGlassSuppressQtChildErase = true; ActiveTabInBold = true; - SkinAutoColorChangeClose = false; // ǷرԶɫ + SkinAutoColorChangeClose = false; // �Ƿ�ر��Զ���ɫ�� DrawHorizontalExplorerBarBgColor = false; // DrawVerticalExplorerBarBgColor = false; // } - // лɫģʽʱлɫ + // �л���ɫ����ģʽʱ���л���ɫ internal void SwitchNighMode(bool isNighMode) { if (this.SkinAutoColorChangeClose) @@ -670,10 +735,10 @@ internal void SwitchNighMode(bool isNighMode) if (isNighMode) { QTUtility2.log("change nightMode white skinChanged " + this.SkinAutoColorChangeClose); - // UseTabSkin = false; // ǩ - // TabImageFile = ""; // ǩļ - // TabSizeMargin = Padding.Empty; // ñԵ - // TabContentMargin = Padding.Empty; // ݱԵ + // UseTabSkin = false; // ��ǩ���� + // TabImageFile = ""; // ��ǩ�����ļ� + // TabSizeMargin = Padding.Empty; // ���ñ�Ե + // TabContentMargin = Padding.Empty; // ���ݱ�Ե // OverlapPixels = 0; // // HitTestTransparent = false; // TabHeight = 24; @@ -694,21 +759,21 @@ internal void SwitchNighMode(bool isNighMode) // TabTextCentered = false; // UseRebarBGColor = false; // RebarColor = Color.FromArgb(230,230,230); - // ñǩɫ + // ���ñ�ǩ����ɫ - // UseRebarImage = false; // Ƿ񹤾ԶͼƬ,ԶͼƬ + // UseRebarImage = false; // �Ƿ񹤾����Զ���ͼƬ,�����Զ���ͼƬ // RebarStretchMode = StretchMode.Tile; - // RebarImageFile = ""; // ԶͼƬ + // RebarImageFile = ""; // �������Զ���ͼƬ // RebarImageSeperateBars = false; // RebarSizeMargin = Padding.Empty; // ActiveTabInBold = true; } else { - // UseTabSkin = false; // ǩ - // TabImageFile = ""; // ǩļ - // TabSizeMargin = Padding.Empty; // ñԵ - // TabContentMargin = Padding.Empty; // ݱԵ + // UseTabSkin = false; // ��ǩ���� + // TabImageFile = ""; // ��ǩ�����ļ� + // TabSizeMargin = Padding.Empty; // ���ñ�Ե + // TabContentMargin = Padding.Empty; // ���ݱ�Ե // OverlapPixels = 0; // // HitTestTransparent = false; // TabHeight = 24; @@ -731,10 +796,10 @@ internal void SwitchNighMode(bool isNighMode) // TabTextCentered = false; UseRebarBGColor = true; // // RebarColor = Color.FromArgb(230,230,230); - // // ñǩɫ - // UseRebarImage = false; // Ƿ񹤾ԶͼƬ,ԶͼƬ + // // ���ñ�ǩ����ɫ + // UseRebarImage = false; // �Ƿ񹤾����Զ���ͼƬ,�����Զ���ͼƬ // RebarStretchMode = StretchMode.Tile; - // RebarImageFile = ""; // ԶͼƬ + // RebarImageFile = ""; // �������Զ���ͼƬ // RebarImageSeperateBars = false; // RebarSizeMargin = Padding.Empty; // ActiveTabInBold = true; @@ -769,15 +834,15 @@ public _BBar() { ButtonIndexes = QTUtility.IsXP // ? new int[] { 1, 2, 0, 3, 4, 5, 0, 6, 7, 0, 11, 13, 12, 14, 15, 0, 21, 9, 20 } // : new System.Int32[] { 3, 4, 5, 0, 6, 7, 0, 17, 11, 12, 14, 15, 13, 0, 21, 9, 19, 10 }; - // ȥָ + // ȥ���ָ��� ? new int[] { 1, 2, 3, 4, 5, 6, 7, 11, 13, 12, 14, 15, 21, 9, 20 } : new System.Int32[] { 3, 4, 5, 6, 7, 17, 11, 12, 14, 15, 13, 21, 9, 19, 10 }; ActivePluginIDs = new string[0]; - LargeButtons = true; // Ƿʾť - LockSearchBarWidth = true; // С - LockDropDownButtons = true; // ť˵˳ - ShowButtonLabels = true; // Ƿʾťǩ - ImageStripPath = ""; // ԶͼƬ· + LargeButtons = true; // �Ƿ���ʾ��ť + LockSearchBarWidth = true; // �����������С + LockDropDownButtons = true; // ����������ť�˵�˳�� + ShowButtonLabels = true; // �Ƿ���ʾ��ť��ǩ + ImageStripPath = ""; // �Զ���ͼƬ·�� } } @@ -788,6 +853,7 @@ public class _Mouse { public Dictionary TabActions { get; set; } public Dictionary BarActions { get; set; } public Dictionary LinkActions { get; set; } + public Dictionary BreadcrumbActions { get; set; } public Dictionary ItemActions { get; set; } public Dictionary MarginActions { get; set; } @@ -822,43 +888,46 @@ public _Mouse() { /* qwop's default value. */ MouseScrollsHotWnd = false; - // ȫ궯 + // ȫ����궯�� GlobalMouseActions = new Dictionary { {MouseChord.X1, BindAction.GoBack}, {MouseChord.X2, BindAction.GoForward}, {MouseChord.X1 | MouseChord.Ctrl, BindAction.GoFirst}, {MouseChord.X2 | MouseChord.Ctrl, BindAction.GoLast} }; - // ǩ + // ��ǩ���� TabActions = new Dictionary { {MouseChord.Middle, BindAction.CloseTab}, {MouseChord.Ctrl | MouseChord.Left, BindAction.LockTab}, {MouseChord.Double, BindAction.UpOneLevelTab}, }; - // ǩBar + // ��ǩBar������ BarActions = new Dictionary { {MouseChord.Double, BindAction.NewTab}, {MouseChord.Middle, BindAction.RestoreLastClosed}, {MouseChord.Ctrl | MouseChord.Middle, BindAction.TearOffCurrent} }; - // ļӶ + // �ļ������Ӷ��� LinkActions = new Dictionary { {MouseChord.None, BindAction.ItemsOpenInNewTabNoSel}, {MouseChord.Middle, BindAction.ItemOpenInNewTab}, {MouseChord.Ctrl | MouseChord.Middle, BindAction.ItemOpenInNewWindow} }; - // ԴĿհ״ + BreadcrumbActions = new Dictionary { + {MouseChord.Middle, BindAction.ItemOpenInNewTab} + }; + // ��Դ��������Ŀ�հ״� ItemActions = new Dictionary { {MouseChord.Middle, BindAction.ItemOpenInNewTab}, {MouseChord.Ctrl | MouseChord.Middle, BindAction.ItemOpenInNewTabNoSel} }; - // Դհ״ + // ��Դ�������հ״� MarginActions = new Dictionary { { MouseChord.Double, BindAction.UpOneLevel} // add by qwop // ,{ MouseChord.Middle, BindAction.BrowseFolder} - // ctrl + ˫ ʾ + // ctrl + ˫�� ��������ʾ�� ,{ ( MouseChord) 66, BindAction.OpenCmd } // === {MouseChord.Ctrl | MouseChord.Double, BindAction.OpenCmd} ,{ MouseChord.Ctrl | MouseChord.Middle, BindAction.ItemsOpenInNewTabNoSel} // add by qwop // @@ -873,75 +942,75 @@ public class _Keys { public bool UseTabSwitcher { get; set; } public _Keys() { - // ʼĬϵĿݼֵ + // ��ʼ��Ĭ�ϵĿ�ݼ��ֵ��� var dict = new Dictionary { - // ˲ + // ���˲��� {BindAction.GoBack, Key.Left | Key.Alt}, - // ǰ + // ǰ������ {BindAction.GoForward, Key.Right | Key.Alt}, - // תһ + // ��ת��һ�� {BindAction.GoFirst, Key.Left | Key.Control | Key.Alt}, - // תһ + // ��ת���һ�� {BindAction.GoLast, Key.Right | Key.Control | Key.Alt}, - // һǩ + // ��һ����ǩ {BindAction.NextTab, Key.Tab | Key.Control}, - // һǩ + // ��һ����ǩ {BindAction.PreviousTab, Key.Tab | Key.Control | Key.Shift}, - // ½ǩ + // �½���ǩ {BindAction.NewTab, Key.T | Key.Control}, - // ´ + // �´��� {BindAction.NewWindow, Key.T | Key.Control | Key.Shift}, - // رձǩ + // �رձ�ǩ {BindAction.CloseCurrent, Key.W | Key.Control}, - // رǩ + // �ر�������ǩ {BindAction.CloseAllButCurrent, Key.W | Key.Control | Key.Shift}, - // ָرյıǩ + // �ָ��رյı�ǩ {BindAction.RestoreLastClosed, Key.Z | Key.Control | Key.Shift}, - // ȡ̿ݼ + // ȡ���������̿�ݼ� // {BindAction.LockCurrent, Key.L | Key.Control}, // {BindAction.LockAll, Key.L | Key.Control | Key.Shift}, {BindAction.BrowseFolder, Key.O | Key.Control}, - // ѡ + // ��ѡ�� {BindAction.ShowOptions, Key.O | Key.Alt}, - // ʾ˵ + // ��ʾ�������˵� {BindAction.ShowToolbarMenu, Key.Oemcomma | Key.Alt}, - // ʾǩ˵ + // ��ʾ��ǩ�˵� {BindAction.ShowTabMenuCurrent, Key.OemPeriod | Key.Alt}, - // ʾǩ˵ + // ��ʾ��ǩ��˵� {BindAction.ShowGroupMenu, Key.G | Key.Alt}, - // ʾûӦó˵ + // ��ʾ�û�Ӧ�ó���˵� {BindAction.ShowUserAppsMenu, Key.H | Key.Alt}, - // ʾǩ˵ + // ��ʾ�����ǩ�˵� {BindAction.ShowRecentTabsMenu, Key.U | Key.Alt}, - // ʾļ˵ + // ��ʾ����ļ��˵� {BindAction.ShowRecentFilesMenu,Key.F | Key.Alt}, - // Bug fix ȼͻ by indiff + // Bug fix �ȼ���ͻ�� ���� by indiff // {BindAction.NewFile, Key.N | Key.Control}, {BindAction.NewFile, Key.N | Key.Control | Key.Alt}, // {BindAction.NewFolder, Key.N | Key.Control | Key.Shift}, - // {BindAction.NewFolder, Key.N | Key.Shift }, // ϵͳĬԴ - // ǩ + // {BindAction.NewFolder, Key.N | Key.Shift }, // ϵͳĬ���Դ� + // ������ǩ�� {BindAction.CreateNewGroup, Key.D | Key.Control}, - // ӵǩ + // ���ӵ���ǩ�� // {BindAction.AddToGroup, Key.D | Key.Control }, // {BindAction.AddToGroup, Key.G | Key.Control | Key.Alt }, }; - // ޸Խ by indiff + // �޸�����Խ������ by indiff var keyboardActionCount = (int)BindAction.KEYBOARD_ACTION_COUNT; Shortcuts = new int[keyboardActionCount]; - // ݼ + // �����ݼ� PluginShortcuts = new Dictionary(); foreach(var pair in dict) { var pairKey = (int)pair.Key; - // ޸Խ by indiff + // �޸�����Խ������ by indiff if (pairKey > keyboardActionCount - 1) { continue; } Shortcuts[pairKey] = (int)pair.Value | QTUtility.FLAG_KEYENABLED; } - // ñǩл + // ���ñ�ǩ�л��� UseTabSwitcher = true; } } @@ -973,7 +1042,7 @@ public _Lang() { if (uiCulture.Equals("zh-CN") || lUiCulture.Equals("zh") || lUiCulture.Equals("cn")) { BuiltInLangSelectedIndex = 1; - BuiltInLang = ""; + BuiltInLang = "��������"; } else if (uiCulture.Equals("de_DE") || lUiCulture.Equals("de")) { @@ -1001,7 +1070,7 @@ public _Lang() { BuiltInLang = "Turkish"; } else if (uiCulture.Equals("ru_RU") || lUiCulture.Equals("ru")) - { // Сдж϶˹ + { // Сд�ж϶���˹ BuiltInLangSelectedIndex = 7; BuiltInLang = "Russian"; } @@ -1034,7 +1103,7 @@ public class _Desktop { public bool OneClickMenu { get; set; } public bool EnableAppShortcuts { get; set; } public int Width { get; set; } - public int lstSelectedIndex { get; set; } /*ѡеIJ˵.*/ + public int lstSelectedIndex { get; set; } /*���ѡ�еIJ˵���.*/ public _Desktop() { FirstItem = 0; SecondItem = 1; @@ -1059,7 +1128,7 @@ public _Desktop() { // qwop's default value. Width = 12; - // ѡеIJ˵ĬΪ0. ´δ򿪵ʱԶλ + // ���ѡ�еIJ˵�������Ĭ��Ϊ0. �´δ򿪵�ʱ���Զ���λ�������� lstSelectedIndex = 0; } } @@ -1070,9 +1139,9 @@ public static class ConfigManager { public static void Initialize() { LoadedConfig = new Config(); - QTUtility2.log("ʼϢɹ"); + QTUtility2.log("��ʼ��������Ϣ�ɹ�"); ReadConfig(); - QTUtility2.log("עȡϢɹ"); + QTUtility2.log("ע�����ȡ������Ϣ�ɹ�"); } public static void UpdateConfig(bool fBroadcast = true) { @@ -1173,14 +1242,26 @@ from settingProperty in categoryType.GetProperties() Config.Tips.PreviewFont = Config.Tips.PreviewFont ?? Control.DefaultFont; Config.Tips.PreviewMaxWidth = QTUtility.ValidateMinMax(Config.Tips.PreviewMaxWidth, 128, 1920); Config.Tips.PreviewMaxHeight = QTUtility.ValidateMinMax(Config.Tips.PreviewMaxHeight, 96, 1200); + if(Config.Tips.PreviewDelay <= 0) { + Config.Tips.PreviewDelay = new Config._Tips().PreviewDelay; + } + if(Config.Tips.PreviewOpacity <= 0) { + Config.Tips.PreviewOpacity = new Config._Tips().PreviewOpacity; + } + if(Config.Tips.PreviewCacheCapacity <= 0) { + Config.Tips.PreviewCacheCapacity = new Config._Tips().PreviewCacheCapacity; + } + Config.Tips.PreviewDelay = QTUtility.ValidateMinMax(Config.Tips.PreviewDelay, 50, 5000); + Config.Tips.PreviewOpacity = QTUtility.ValidateMinMax(Config.Tips.PreviewOpacity, 20, 100); + Config.Tips.PreviewCacheCapacity = QTUtility.ValidateMinMax(Config.Tips.PreviewCacheCapacity, 8, 1024); Config.Misc.TabHistoryCount = QTUtility.ValidateMinMax(Config.Misc.TabHistoryCount, 1, 30); Config.Misc.FileHistoryCount = QTUtility.ValidateMinMax(Config.Misc.FileHistoryCount, 1, 30); Config.Misc.NetworkTimeout = QTUtility.ValidateMinMax(Config.Misc.NetworkTimeout, 0, 120); Config.Skin.TabHeight = QTUtility.ValidateMinMax(Config.Skin.TabHeight, 10, 50); - // ǩС + // ������ǩ��С���� Config.Skin.TabMinWidth = QTUtility.ValidateMinMax(Config.Skin.TabMinWidth, 10, 999); Config.Skin.TabMaxWidth = QTUtility.ValidateMinMax(Config.Skin.TabMaxWidth, 50, 999); - Config.Skin.OverlapPixels = QTUtility.ValidateMinMax(Config.Skin.TabHeight, 0, 20); + Config.Skin.OverlapPixels = QTUtility.ValidateMinMax(Config.Skin.OverlapPixels, 0, 20); Config.Skin.TabTextFont = Config.Skin.TabTextFont ?? Control.DefaultFont; Config.Skin.FluentGlassAddressMode = QTUtility.ValidateMinMax(Config.Skin.FluentGlassAddressMode, 0, 2); Config.Skin.FluentGlassAddressExtraPixels = QTUtility.ValidateMinMax(Config.Skin.FluentGlassAddressExtraPixels, 0, 400); @@ -1203,6 +1284,17 @@ from settingProperty in categoryType.GetProperties() if(string.IsNullOrEmpty(Config.Skin.PlusButtonImageFile) || !File.Exists(Config.Skin.PlusButtonImageFile)) { Config.Skin.PlusButtonImageFile = ""; } + if(string.IsNullOrEmpty(Config.Skin.CloseButtonImageFile) || !File.Exists(Config.Skin.CloseButtonImageFile)) { + Config.Skin.CloseButtonImageFile = ""; + } + if(string.IsNullOrEmpty(Config.Skin.LockIconImageFile) || !File.Exists(Config.Skin.LockIconImageFile)) { + Config.Skin.LockIconImageFile = ""; + } + Config.Skin.CloseButtonImageOffsetX = QTUtility.ValidateMinMax(Config.Skin.CloseButtonImageOffsetX, -50, 50); + Config.Skin.CloseButtonImageOffsetY = QTUtility.ValidateMinMax(Config.Skin.CloseButtonImageOffsetY, -50, 50); + Config.Skin.LockIconImageOffsetX = QTUtility.ValidateMinMax(Config.Skin.LockIconImageOffsetX, -50, 50); + Config.Skin.LockIconImageOffsetY = QTUtility.ValidateMinMax(Config.Skin.LockIconImageOffsetY, -50, 50); + Config.DragDrop.TabDropHoverTime = QTUtility.ValidateMinMax(Config.DragDrop.TabDropHoverTime, 100, 5000); using(IDLWrapper wrapper = new IDLWrapper(Config.Skin.RebarImageFile)) { if(!wrapper.Available) Config.Skin.RebarImageFile = ""; } diff --git a/QTTabBar/ExtendedItemsView.cs b/QTTabBar/ExtendedItemsView.cs index 0eb72dd..f40ec9f 100644 --- a/QTTabBar/ExtendedItemsView.cs +++ b/QTTabBar/ExtendedItemsView.cs @@ -145,6 +145,8 @@ private Point GetWindowPos() { protected override bool HandleCursorLoop(Keys key) { int focusedIdx = ShellBrowser.GetFocusedIndex(); int itemCount = ShellBrowser.GetItemCount(); + if(itemCount <= 0) return false; + if(focusedIdx < 0 || focusedIdx >= itemCount) return SelectCursorLoopFallback(key); int selectMe = -1; FVM viewMode = ShellBrowser.ViewMode; switch(viewMode) { @@ -181,7 +183,7 @@ protected override bool HandleCursorLoop(Keys key) { if(elem == null) return -1; return viewMode == FVM.LIST ? elem.GetRowCount() : elem.GetColumnCount(); }); - if(pageCount == -1) return false; + if(pageCount <= 0) return false; int page = focusedIdx % pageCount; if(key == KeyNextItem && (page == pageCount - 1 || focusedIdx == itemCount - 1)) { selectMe = focusedIdx - page; diff --git a/QTTabBar/ExtendedListViewCommon.cs b/QTTabBar/ExtendedListViewCommon.cs index 5931179..001e446 100644 --- a/QTTabBar/ExtendedListViewCommon.cs +++ b/QTTabBar/ExtendedListViewCommon.cs @@ -126,13 +126,13 @@ internal ExtendedListViewCommon(ShellBrowserEx shellBrowser, IntPtr hwndShellVie } // RefreshViewWatermark(true); - // ļ򲻼ر + // ����ļ��������򲻼��ر��� /*if (File.Exists(BG_IMG)) { SetBackgroundImage(true, true, 0, 0); }*/ - // ִвЧ + // ִ�в���Ч // SetBackgroundImage(true, true, 0, 0); // InstallHooks(); } @@ -169,10 +169,10 @@ int MyFillRect(IntPtr hDC, [In] ref RECT lprc, IntPtr hbr) PInvoke.GetWindowRect(Handle, out pRc); Size wndSize = new Size(lprc.right - pRc.left, lprc.bottom - pRc.top); Rectangle rctDw = pRc.ToRectangle(); - //ͼƬλ Calculate picture position + //����ͼƬλ�� Calculate picture position PInvoke.InvalidateRect(Handle, IntPtr.Zero, true); - var bgPng = @"D:\\Release\Release\x64\Image\bgImage1.png"; + var bgPng = @"D:\����\Release\Release\x64\Image\bgImage1.png"; // PInvoke.SaveDC if (rendererDown_Normal == null) @@ -188,7 +188,7 @@ int MyFillRect(IntPtr hDC, [In] ref RECT lprc, IntPtr hbr) // VisualStyleRenderer renderer2; renderer = rendererDown_Normal; // g.DrawImage(QTUtility.ImageListGlobal.Images[base2.ImageKey], rect); - var dToutiaoX1080IntellijIdea3Png = @"D:\\Release\Release\x64\Image\bgImage.png"; + var dToutiaoX1080IntellijIdea3Png = @"D:\����\Release\Release\x64\Image\bgImage.png"; using (FreeBitmap freeBitmap = new FreeBitmap(dToutiaoX1080IntellijIdea3Png)) using (Bitmap bmp = freeBitmap.Clone()) { @@ -264,7 +264,7 @@ public bool SetBackgroundImage2(bool isWatermark, bool isTiled, int xOffset, int lvbkimage.ulFlags = LVBKIF_SOURCE_HBITMAP; result = PInvoke.SendMessageLVBKIMAGE(handle, LVM_SETBKIMAGE, 0, ref lvbkimage); - var dToutiaoX1080IntellijIdea3Png = @"D:\\Release\Release\x64\Image\bgImage1.png"; + var dToutiaoX1080IntellijIdea3Png = @"D:\����\Release\Release\x64\Image\bgImage1.png"; // var dToutiaoX1080IntellijIdea3Png = @"D:\Users\Administrator\Documents\Tencent Files\531299332\Image\Group2\IY\S2\IYS2F)882TXGVT[JIR[`4BY.bmp"; using (FreeBitmap freeBitmap = new FreeBitmap(dToutiaoX1080IntellijIdea3Png)) @@ -390,21 +390,35 @@ public override int GetHotItem() { protected abstract bool HandleCursorLoop(Keys key); - public override void HandleF2() { + public override bool HandleF2() { IntPtr hWnd = GetEditControl(); - if(hWnd == IntPtr.Zero) return; + if(hWnd == IntPtr.Zero) return false; string str; using(SafePtr lParam = new SafePtr(520)) { - if(0 >= ((int)PInvoke.SendMessage(hWnd, 13, (IntPtr)260, lParam))) return; + if(0 >= ((int)PInvoke.SendMessage(hWnd, 13, (IntPtr)260, lParam))) return false; str = Marshal.PtrToStringUni(lParam); } - if(str.Length <= 2) return; + if(str.Length <= 2) return false; int num = str.LastIndexOf("."); - if(num != -1) { - IntPtr ptr3 = PInvoke.SendMessage(hWnd, 0xb0, IntPtr.Zero, IntPtr.Zero); - int start = QTUtility2.GET_X_LPARAM(ptr3); - int length = QTUtility2.GET_Y_LPARAM(ptr3); - if((length - start) >= 0) { + IntPtr ptr3 = PInvoke.SendMessage(hWnd, 0xb0, IntPtr.Zero, IntPtr.Zero); + int start = QTUtility2.GET_X_LPARAM(ptr3); + int length = QTUtility2.GET_Y_LPARAM(ptr3); + if((length - start) >= 0) { + if(num == -1) { + if((start == 0) && (length == str.Length)) { + start = length = str.Length; + } + else if((start == str.Length) && (length == str.Length)) { + start = length = 0; + } + else { + start = 0; + length = -1; + } + PInvoke.SendMessage(hWnd, 0xb1, (IntPtr)start, (IntPtr)length); + return true; + } + else { if((start == 0) && (length == num)) { start = length = num; } @@ -425,8 +439,10 @@ public override void HandleF2() { length = num; } PInvoke.SendMessage(hWnd, 0xb1, (IntPtr)start, (IntPtr)length); + return true; } } + return false; } public override void HandleShiftKey() { @@ -604,7 +620,7 @@ protected virtual bool ListViewController_MessageCaptured(ref Message msg) { PInvoke.InvalidateRect(Handle, IntPtr.Zero, true); - //ü Clip rect + //�ü����� Clip rect // SaveDC(hDC); // IntersectClipRect(hDC, lprc->left, lprc->top, lprc->right, lprc->bottom); @@ -622,7 +638,7 @@ protected virtual bool ListViewController_MessageCaptured(ref Message msg) { // VisualStyleRenderer renderer2; renderer = rendererDown_Normal; // g.DrawImage(QTUtility.ImageListGlobal.Images[base2.ImageKey], rect); - var dToutiaoX1080IntellijIdea3Png = @"D:\\Release\Release\x64\Image\bgImage.png"; + var dToutiaoX1080IntellijIdea3Png = @"D:\����\Release\Release\x64\Image\bgImage.png"; using (FreeBitmap freeBitmap = new FreeBitmap(dToutiaoX1080IntellijIdea3Png)) using (Bitmap bmp = freeBitmap.Clone()) { @@ -732,7 +748,7 @@ protected virtual bool ListViewController_MessageCaptured(ref Message msg) { // PInvoke.InvalidateRect(Handle, IntPtr.Zero, true); - //ü Clip rect + //�ü����� Clip rect // SaveDC(hDC); // IntersectClipRect(hDC, lprc->left, lprc->top, lprc->right, lprc->bottom); @@ -750,7 +766,7 @@ protected virtual bool ListViewController_MessageCaptured(ref Message msg) { // VisualStyleRenderer renderer2; renderer = rendererDown_Normal; // g.DrawImage(QTUtility.ImageListGlobal.Images[base2.ImageKey], rect); - var dToutiaoX1080IntellijIdea3Png = @"D:\\Release\Release\x64\Image\bgImage.png"; + var dToutiaoX1080IntellijIdea3Png = @"D:\����\Release\Release\x64\Image\bgImage.png"; using (FreeBitmap freeBitmap = new FreeBitmap(dToutiaoX1080IntellijIdea3Png)) using (Bitmap bmp = freeBitmap.Clone()) { @@ -775,7 +791,7 @@ protected virtual bool ListViewController_MessageCaptured(ref Message msg) { return true; case WM.PAINT: - // ֱ Paint Ϣڲ + // ֱ���� Paint ��Ϣ�ڲ��������� // It's very dangerous to do automation-related things // during WM_PAINT. So, use PostMessage to do it later. PInvoke.PostMessage(ListViewController.Handle, WM_AFTERPAINT, IntPtr.Zero, IntPtr.Zero); @@ -947,6 +963,13 @@ protected void OnHotItemChanged(int iItem) { return; } + protected bool SelectCursorLoopFallback(Keys key) { + int itemCount = ShellBrowser.GetItemCount(); + if(itemCount <= 0) return false; + int selectMe = (key == Keys.Up || key == Keys.Left) ? itemCount - 1 : 0; + ShellBrowser.SelectItem(selectMe); + return true; + } protected bool OnSelectionActivated(Keys modKeys) { return SelectionActivated != null && SelectionActivated(modKeys); } @@ -1174,7 +1197,7 @@ private bool ShowSubDirTip(int iItem, bool fByKey, bool fSkipForegroundCheck) { private bool ShowThumbnailTooltip(int iItem, Point pnt, bool fKey) { string linkTargetPath; - if (ShellBrowser == null) // ¿ָ by indiff + if (ShellBrowser == null) // ���¿�ָ������ by indiff { return false; } @@ -1197,7 +1220,7 @@ private bool ShowThumbnailTooltip(int iItem, Point pnt, bool fKey) { thumbnailTooltip = new ThumbnailTooltipForm(); thumbnailTooltip.ThumbnailVisibleChanged += thumbnailTooltip_ThumbnailVisibleChanged; timer_Thumbnail = new Timer(); - timer_Thumbnail.Interval = 400; + timer_Thumbnail.Interval = QTUtility.ValidateMinMax(Config.Tips.PreviewDelay, 50, 5000); timer_Thumbnail.Tick += timer_Thumbnail_Tick; } if(thumbnailTooltip.IsShownByKey && !fKey) { diff --git a/QTTabBar/ExtendedSysListView32.cs b/QTTabBar/ExtendedSysListView32.cs index dbf9647..35d4118 100644 --- a/QTTabBar/ExtendedSysListView32.cs +++ b/QTTabBar/ExtendedSysListView32.cs @@ -214,7 +214,7 @@ protected override bool OnShellViewNotify(NMHDR nmhdr, ref Message msg) { private void SetStyleFlags() { - if (ShellBrowser == null) return; // qt desktop tool ÿָ https://www.yuque.com/indiff/lc0r1g/kqgkr0 + if (ShellBrowser == null) return; // qt desktop tool ���ÿ�ָ������ https://www.yuque.com/indiff/lc0r1g/kqgkr0 if(ShellBrowser.ViewMode != FVM.DETAILS) return; uint flags = 0; if(Config.Tweaks.DetailsGridLines) { @@ -339,10 +339,12 @@ protected override Point GetSubDirTipPoint(bool fByKey) { PInvoke.ClientToScreen(Handle, ref ret); return ret; } - // ʹüͷʱѡļ + // ʹ�ü�ͷ��ʱ����ѡ���ļ��� protected override bool HandleCursorLoop(Keys key) { int focusedIdx = ShellBrowser.GetFocusedIndex(); int itemCount = ShellBrowser.GetItemCount(); + if(itemCount <= 0) return false; + if(focusedIdx < 0 || focusedIdx >= itemCount) return SelectCursorLoopFallback(key); int selectMe = -1; FVM viewMode = ShellBrowser.ViewMode; if(viewMode == FVM.TILE && QTUtility.IsXP) { @@ -451,7 +453,7 @@ protected override bool HandleCursorLoop(Keys key) { } } - // Զ + // �����Զ������ private bool HandleCustomDraw(ref Message msg) { // TODO this needs to be cleaned if(Config.Tweaks.AlternateRowColors && (ShellBrowser.ViewMode == FVM.DETAILS)) { diff --git a/QTTabBar/HookLibManager.cs b/QTTabBar/HookLibManager.cs index d6a462b..aa6b032 100644 --- a/QTTabBar/HookLibManager.cs +++ b/QTTabBar/HookLibManager.cs @@ -22,12 +22,14 @@ using System.Runtime.InteropServices; using System.Text; using System.Windows.Forms; +using Microsoft.Win32; using QTTabBarLib.Interop; namespace QTTabBarLib { public static class HookLibManager { private static bool fShellBrowserIsHooked; private static bool fHookLibraryLoaded; + private static bool fBackgroundRendererLoaded; private static int[] hookStatus = Enumerable.Repeat(-1, Enum.GetNames(typeof(Hooks)).Length).ToArray(); [UnmanagedFunctionPointer(CallingConvention.StdCall)] @@ -54,6 +56,12 @@ private struct HookCallbacks [DllImport("QTTabBarNative.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)] private static extern int QTTabBarNative_InitializeBackgroundLibrary(ref HookCallbacks callbacks, string libraryPath); + [DllImport("QTTabBarNative.dll", CallingConvention = CallingConvention.StdCall)] + private static extern int QTTabBarNative_InitializeBackgroundRenderer(ref HookCallbacks callbacks); + + [DllImport("QTTabBarNative.dll", CallingConvention = CallingConvention.StdCall)] + private static extern int QTTabBarNative_InstallBackgroundHooks(); + [DllImport("QTTabBarNative.dll", CallingConvention = CallingConvention.StdCall)] private static extern void QTTabBarNative_ShutdownHookLibrary(); @@ -63,6 +71,10 @@ private struct HookCallbacks [DllImport("QTTabBarNative.dll", CallingConvention = CallingConvention.StdCall)] private static extern int QTTabBarNative_RegisterBackgroundWindow(IntPtr window); + [DllImport("QTTabBarNative.dll", CallingConvention = CallingConvention.StdCall, + CharSet = CharSet.Unicode)] + private static extern int QTTabBarNative_UpdateBackgroundWindow(IntPtr window, string path); + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] private static extern uint GetPrivateProfileString(string section, string key, string defaultValue, StringBuilder value, uint size, string filePath); @@ -172,20 +184,23 @@ public static void Initialize() "QTTabBar"); bool legacyHookEnabled = IsLegacyHookExplicitlyEnabled(installPath); bool backgroundEnabled = IsExplorerBackgroundEnabled(installPath); - bool backgroundOnly = backgroundEnabled && !legacyHookEnabled; - if(!legacyHookEnabled && !backgroundEnabled) + bool autoHookEnabled = Config.Window.AutoHookWindow || IsConfigRegistryBool("Window", "AutoHookWindow"); + bool fullHookRequested = legacyHookEnabled || autoHookEnabled; + bool backgroundOnly = backgroundEnabled && !fullHookRequested; + if(!fullHookRequested && !backgroundEnabled) { if(fHookLibraryLoaded) { QTTabBarNative_ShutdownHookLibrary(); } fHookLibraryLoaded = false; + fBackgroundRendererLoaded = false; LoadedHook = false; QTUtility2.flog("Legacy QTHookLib disabled by safe mode; Explorer background is not enabled"); return; } - bool shouldLoadHookLibrary = Config.Window.AutoHookWindow || backgroundEnabled; + bool shouldLoadHookLibrary = fullHookRequested || backgroundEnabled; try { if (LoadedHook) @@ -193,9 +208,6 @@ public static void Initialize() return; } - if(backgroundOnly) { - QTUtility2.flog("Loading isolated Explorer background renderer; legacy Shell hooks remain disabled"); - } ManagementObjectSearcher searcher = backgroundOnly ? null : new ManagementObjectSearcher("SELECT * FROM Win32_OperatingSystem"); @@ -237,6 +249,7 @@ public static void Initialize() { QTTabBarNative_ShutdownHookLibrary(); fHookLibraryLoaded = false; + fBackgroundRendererLoaded = false; LoadedHook = false; } return; @@ -244,28 +257,18 @@ public static void Initialize() if (!shouldLoadHookLibrary) { + fBackgroundRendererLoaded = false; LoadedHook = false; return; } - if (!File.Exists(Path.Combine(installPath, filename))) // 如果文件不存在则设置为不自动加载 + if (!backgroundOnly && !File.Exists(Path.Combine(installPath, filename))) // 如果文件不存在则设置为不自动加载 { QTUtility2.flog("not exists file , close auto hook " + Path.Combine(installPath, filename)); Config.Window.AutoHookWindow = false; LoadedHook = false; return; } - QTUtility2.flog("load library " + Path.Combine(installPath, filename) ); - string bridgePath = Path.Combine(installPath, "QTTabBarNative.dll"); - try - { - string bridgeVersion = System.Diagnostics.FileVersionInfo.GetVersionInfo(bridgePath).FileVersion; - QTUtility2.flog("hook bridge " + bridgePath + " version " + bridgeVersion); - } - catch (Exception ex) - { - QTUtility2.MakeErrorLog(ex, "Could not read the native hook bridge version."); - } if(!FluentGlassManager.EnsureNativeModuleLoaded()) { QTUtility2.log("Hook library skipped: QTTabBarNative.dll could not be loaded from ProgramData."); fHookLibraryLoaded = false; @@ -276,9 +279,19 @@ public static void Initialize() int hr = -1; try { - hr = backgroundOnly - ? QTTabBarNative_InitializeBackgroundLibrary(ref nativeCallbacks, Path.Combine(installPath, filename)) - : QTTabBarNative_InitializeHookLibrary(ref nativeCallbacks, Path.Combine(installPath, filename)); + if(backgroundOnly) { + hr = QTTabBarNative_InitializeBackgroundRenderer(ref nativeCallbacks); + if(hr == 0) { + int hookHr = QTTabBarNative_InstallBackgroundHooks(); + if(hookHr != 0) { + hr = hookHr; + } + } + } + else { + hr = QTTabBarNative_InitializeHookLibrary(ref nativeCallbacks, + Path.Combine(installPath, filename)); + } } catch (DllNotFoundException ex) { @@ -305,16 +318,14 @@ public static void Initialize() if (hr == 0) { fHookLibraryLoaded = true; + fBackgroundRendererLoaded = backgroundOnly; LoadedHook = true; - QTUtility2.log(backgroundOnly - ? "Explorer background renderer initialize success" - : "HookLib Initialize success"); - // MessageBox.Show("HookLib Initialize success"); return; } QTUtility2.MakeErrorLog(null, "HookLib Initialize failed: 0x" + hr.ToString("X8")); LoadedHook = false; fHookLibraryLoaded = false; + fBackgroundRendererLoaded = false; } private static bool IsLegacyHookExplicitlyEnabled(string installPath) @@ -351,15 +362,35 @@ private static bool IsExplorerBackgroundEnabled(string installPath) if(File.Exists(imagePath)) return true; } - string imageDirectory = Path.Combine(installPath, "Image"); + value.Clear(); + GetPrivateProfileString("image", "folder", "Image", value, + (uint)value.Capacity, configPath); + string imageDirectory = Environment.ExpandEnvironmentVariables( + value.ToString().Trim().Trim('"')); + if(String.IsNullOrEmpty(imageDirectory)) imageDirectory = "Image"; + if(!Path.IsPathRooted(imageDirectory)) imageDirectory = Path.Combine(installPath, imageDirectory); if(!Directory.Exists(imageDirectory)) return false; return Directory.EnumerateFiles(imageDirectory).Any(path => path.EndsWith(".png", StringComparison.OrdinalIgnoreCase) || + path.EndsWith(".bmp", StringComparison.OrdinalIgnoreCase) || path.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) || path.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase)); } - internal static void RegisterBackgroundWindow(IntPtr window) + private static bool IsConfigRegistryBool(string category, string name) + { + try { + using(RegistryKey key = Registry.CurrentUser.OpenSubKey(RegConst.Root + RegConst.Config + category, false)) { + object value = key == null ? null : key.GetValue(name); + return value is int && (int)value != 0; + } + } + catch { + return false; + } + } + + internal static void RegisterBackgroundWindow(IntPtr window, string path = null) { if(!fHookLibraryLoaded || window == IntPtr.Zero) return; try { @@ -368,7 +399,9 @@ internal static void RegisterBackgroundWindow(IntPtr window) QTUtility2.log("RegisterBackgroundWindow failed: 0x" + result.ToString("X8")); } else { - QTUtility2.flog("RegisterBackgroundWindow success hwnd=0x" + window.ToInt64().ToString("X")); + if(!String.IsNullOrEmpty(path)) { + UpdateBackgroundWindow(window, path); + } } } catch(Exception ex) { @@ -376,6 +409,19 @@ internal static void RegisterBackgroundWindow(IntPtr window) } } + internal static void UpdateBackgroundWindow(IntPtr window, string path) + { + if(!fHookLibraryLoaded || window == IntPtr.Zero) return; + try { + int result = QTTabBarNative_UpdateBackgroundWindow(window, path ?? String.Empty); + if(result != 0 && result != unchecked((int)0x80004001)) { + QTUtility2.log("UpdateBackgroundWindow failed: 0x" + result.ToString("X8")); + } + } + catch(Exception ex) { + QTUtility2.MakeErrorLog(ex, "UpdateBackgroundWindow"); + } + } private static void HookResult(int hookId, int retcode, IntPtr context) { try { @@ -432,7 +478,14 @@ public static void InitShellBrowserHook(IShellBrowser shellBrowser) { lock (typeof(HookLibManager)) { - if(fShellBrowserIsHooked || !fHookLibraryLoaded) return; + if(fShellBrowserIsHooked || !fHookLibraryLoaded || fBackgroundRendererLoaded) + { + if(fBackgroundRendererLoaded) + { + QTUtility2.flog("InitShellBrowserHook skipped for isolated background renderer"); + } + return; + } IntPtr pShellBrowser = Marshal.GetComInterfaceForObject(shellBrowser, typeof(IShellBrowser)); if(pShellBrowser == IntPtr.Zero) return; int retcode = -1; diff --git a/QTTabBar/IDLWrapper.cs b/QTTabBar/IDLWrapper.cs index 968b752..e1f8a8d 100644 --- a/QTTabBar/IDLWrapper.cs +++ b/QTTabBar/IDLWrapper.cs @@ -359,7 +359,12 @@ private static void ReadCache() { } public IDLWrapper ResolveTargetIfLink() { - return IsLink ? new IDLWrapper(ShellMethods.GetLinkTargetIDL(Path)) : null; + if(!IsLink) { + return null; + } + + IntPtr targetIDL = ShellMethods.GetLinkTargetIDL(Path); + return targetIDL != IntPtr.Zero ? new IDLWrapper(targetIDL) : null; } public static void SaveCache(RegistryKey rkUser) { @@ -616,6 +621,9 @@ public bool IsLinkToDeadFolder { return false; } string linkTargetPath = ShellMethods.GetLinkTargetPath(Path); + if(string.IsNullOrEmpty(linkTargetPath)) { + return false; + } if(FileExists(linkTargetPath)) { return false; } diff --git a/QTTabBar/Interop/ShellMethods.cs b/QTTabBar/Interop/ShellMethods.cs index 3508ea9..158733e 100644 --- a/QTTabBar/Interop/ShellMethods.cs +++ b/QTTabBar/Interop/ShellMethods.cs @@ -30,6 +30,13 @@ namespace QTTabBarLib.Interop { public static class ShellMethods { + private const int E_ACCESSDENIED = unchecked((int)0x80070005); + + private static bool IsRecoverableLinkLoadError(Exception ex) { + return ex is UnauthorizedAccessException || + (ex is COMException comException && comException.ErrorCode == E_ACCESSDENIED); + } + public static bool ClipboardContainsFileDropList(IntPtr hwnd) { if((hwnd != IntPtr.Zero) && PInvoke.OpenClipboard(hwnd)) { try { @@ -349,7 +356,12 @@ public static IntPtr GetLinkTargetIDL(string lnkPath) { } catch (Exception ex) { - QTUtility2.MakeErrorLog(ex, "ShellMethods _ShellLinkObject"); + if(IsRecoverableLinkLoadError(ex)) { + QTUtility2.log("ShellMethods GetLinkTargetIDL skipped inaccessible link " + lnkPath); + } + else { + QTUtility2.MakeErrorLog(ex, "ShellMethods _ShellLinkObject"); + } } finally { if(o != null) { @@ -372,7 +384,12 @@ public static string GetLinkTargetPath(string lnkPath) { } catch (Exception ex) { - QTUtility2.MakeErrorLog(ex, "ShellMethods GetLinkTargetPath"); + if(IsRecoverableLinkLoadError(ex)) { + QTUtility2.log("ShellMethods GetLinkTargetPath skipped inaccessible link " + lnkPath); + } + else { + QTUtility2.MakeErrorLog(ex, "ShellMethods GetLinkTargetPath"); + } } finally { diff --git a/QTTabBar/ListViewMonitor.cs b/QTTabBar/ListViewMonitor.cs index 8dc810f..67cc7c4 100644 --- a/QTTabBar/ListViewMonitor.cs +++ b/QTTabBar/ListViewMonitor.cs @@ -1,4 +1,4 @@ -// This file is part of QTTabBar, a shell extension for Microsoft +// This file is part of QTTabBar, a shell extension for Microsoft // Windows Explorer. // Copyright (C) 2007-2021 Quizo, Paul Accisano // @@ -16,14 +16,20 @@ // along with QTTabBar. If not, see . using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; using System.Windows.Forms; using QTTabBarLib.Interop; namespace QTTabBarLib { public class ListViewMonitor : IDisposable { public event EventHandler ListViewChanged; - private IntPtr hwndShellContainer; - private NativeWindowController ContainerController; + // Windows native Explorer tabs create one ShellTabWindowClass container per tab. + // Every container can own its own SHELLDLL_DefView, so monitor all containers + // and recapture when the active one changes. + private List containerControllers = new List(); + private NativeWindowController explorerController; + private List liveViews = new List(); private ShellBrowserEx ShellBrowser; private IntPtr hwndExplorer; private IntPtr hwndSubDirTipMessageReflect; @@ -33,32 +39,74 @@ internal ListViewMonitor(ShellBrowserEx shellBrowser, IntPtr hwndExplorer, IntPt ShellBrowser = shellBrowser; this.hwndExplorer = hwndExplorer; this.hwndSubDirTipMessageReflect = hwndSubDirTipMessageReflect; - hwndShellContainer = QTUtility.IsXP - ? hwndExplorer - : WindowUtils.FindChildWindow(hwndExplorer, hwnd => PInvoke.GetClassName(hwnd) == "ShellTabWindowClass"); - if(hwndShellContainer != IntPtr.Zero) { - ContainerController = new NativeWindowController(hwndShellContainer); - ContainerController.MessageCaptured += ContainerController_MessageCaptured; + if(QTUtility.IsXP) { + AddContainer(hwndExplorer); + } + else { + IntPtr hwndContainer = IntPtr.Zero; + while((hwndContainer = PInvoke.FindWindowEx(hwndExplorer, hwndContainer, "ShellTabWindowClass", null)) != IntPtr.Zero) { + AddContainer(hwndContainer); + } + explorerController = new NativeWindowController(hwndExplorer); + explorerController.MessageCaptured += ExplorerController_MessageCaptured; } } public AbstractListView CurrentListView { get; private set; } public AbstractListView PreviousListView { get; private set; } + private void AddContainer(IntPtr hwnd) { + if(hwnd == IntPtr.Zero || containerControllers.Exists(existing => existing.Handle == hwnd)) return; + NativeWindowController controller = new NativeWindowController(hwnd); + controller.MessageCaptured += ContainerController_MessageCaptured; + containerControllers.Add(controller); + } + + private IntPtr ActiveContainer() { + return QTUtility.IsXP ? hwndExplorer : WindowUtils.GetShellTabWindowClass(hwndExplorer); + } + + private bool ExplorerController_MessageCaptured(ref Message msg) { + if(msg.Msg == WM.PARENTNOTIFY && + PInvoke.LoWord((int)msg.WParam) == WM.CREATE && + PInvoke.GetClassName(msg.LParam) == "ShellTabWindowClass") { + AddContainer(msg.LParam); + } + return false; + } + private bool ContainerController_MessageCaptured(ref Message msg) { - // QTUtility2.debugMessage(msg); - if(msg.Msg == WM.PARENTNOTIFY && + if(msg.Msg == WM.PARENTNOTIFY && PInvoke.LoWord((int)msg.WParam) == WM.CREATE) { string name = PInvoke.GetClassName(msg.LParam); - if(name == "SHELLDLL_DefView") { + if(name == "SHELLDLL_DefView" && msg.HWnd == ActiveContainer()) { RecaptureHandles(msg.LParam); } } + else if(msg.Msg == WM.WINDOWPOSCHANGED && containerControllers.Count > 1) { + WINDOWPOS wp = (WINDOWPOS)Marshal.PtrToStructure(msg.LParam, typeof(WINDOWPOS)); + if((wp.flags & SWP.NOZORDER) == 0 && msg.HWnd == ActiveContainer()) { + IntPtr hwndShellView = WindowUtils.FindChildWindow(msg.HWnd, + hwnd => PInvoke.GetClassName(hwnd) == "SHELLDLL_DefView"); + if(hwndShellView != IntPtr.Zero) { + RecaptureHandles(hwndShellView); + } + } + } return false; } public void Initialize() { - IntPtr hwndShellView = WindowUtils.FindChildWindow(hwndExplorer, hwnd => PInvoke.GetClassName(hwnd) == "SHELLDLL_DefView"); + IntPtr searchRoot = ActiveContainer(); + if(searchRoot == IntPtr.Zero) { + searchRoot = hwndExplorer; + } + IntPtr hwndShellView = WindowUtils.FindChildWindow(searchRoot, + hwnd => PInvoke.GetClassName(hwnd) == "SHELLDLL_DefView"); + if(hwndShellView == IntPtr.Zero && searchRoot != hwndExplorer) { + hwndShellView = WindowUtils.FindChildWindow(hwndExplorer, + hwnd => PInvoke.GetClassName(hwnd) == "SHELLDLL_DefView"); + } if(hwndShellView == IntPtr.Zero) { if(CurrentListView != null) { CurrentListView.Dispose(); @@ -93,8 +141,16 @@ private void RecaptureHandles(IntPtr hwndShellView) { PreviousListView = CurrentListView; } - if(hwndListView == IntPtr.Zero) - { + AbstractListView live = hwndListView == IntPtr.Zero ? null + : liveViews.Find(view => view.Handle == hwndListView); + if(live != null) { + CurrentListView = live; + UpdateBackgroundWindow(hwndListView, fIsSysListView); + ListViewChanged(this, null); + return; + } + + if(hwndListView == IntPtr.Zero) { QTUtility2.log("new AbstractListView"); CurrentListView = new AbstractListView(); } @@ -106,11 +162,27 @@ private void RecaptureHandles(IntPtr hwndShellView) { QTUtility2.log("new ExtendedItemsView"); CurrentListView = new ExtendedItemsView(ShellBrowser, hwndShellView, hwndListView, hwndSubDirTipMessageReflect); } + UpdateBackgroundWindow(hwndListView, fIsSysListView); CurrentListView.ListViewDestroyed += ListView_Destroyed; + liveViews.Add(CurrentListView); ListViewChanged(this, null); } + private void UpdateBackgroundWindow(IntPtr hwndListView, bool fIsSysListView) { + if(fIsSysListView || hwndListView == IntPtr.Zero) return; + try { + using(IDLWrapper path = ShellBrowser.GetShellPath()) { + HookLibManager.UpdateBackgroundWindow(hwndListView, + path != null && path.Available ? path.Path : String.Empty); + } + } + catch(Exception ex) { + QTUtility2.MakeErrorLog(ex, "Explorer background initial path"); + } + } + private void ListView_Destroyed(object sender, EventArgs args) { + liveViews.Remove((AbstractListView)sender); if(sender == CurrentListView) { if(PreviousListView != null) { CurrentListView = PreviousListView; @@ -131,6 +203,18 @@ private void ListView_Destroyed(object sender, EventArgs args) { public void Dispose() { if(fDisposed) return; + if(explorerController != null) { + explorerController.MessageCaptured -= ExplorerController_MessageCaptured; + explorerController = null; + } + foreach(NativeWindowController controller in containerControllers) { + controller.MessageCaptured -= ContainerController_MessageCaptured; + } + containerControllers.Clear(); + foreach(AbstractListView view in liveViews) { + if(view != CurrentListView) view.Dispose(); + } + liveViews.Clear(); if(CurrentListView != null) { CurrentListView.Dispose(); CurrentListView = null; diff --git a/QTTabBar/MiscClasses.cs b/QTTabBar/MiscClasses.cs index ae1a2f6..bf885c9 100644 --- a/QTTabBar/MiscClasses.cs +++ b/QTTabBar/MiscClasses.cs @@ -192,13 +192,27 @@ public SerializeDelegate(Delegate del) { } public SerializeDelegate(SerializationInfo info, StreamingContext context) { - Type delType = (Type)info.GetValue("delegateType", typeof(Type)); + Type delType; + bool isSerializable; + try { + delType = (Type)info.GetValue("delegateType", typeof(Type)); + isSerializable = info.GetBoolean("isSerializable"); + } + catch(SerializationException) { + Delegate = null; + return; + } + + if(delType == null) { + Delegate = null; + return; + } // Type classType = (Type)info.GetValue("classType", typeof(Type)); // obj = Activator.CreateInstance(classType); //If it's a "simple" delegate we just read it straight off - if (info.GetBoolean("isSerializable")) { + if(isSerializable) { Delegate = (Delegate)info.GetValue("delegate", delType); } //otherwise, we need to read its anonymous class @@ -401,4 +415,3 @@ public void Dispose() { } } - diff --git a/QTTabBar/OptionsDialog/Options02_Tabs.xaml b/QTTabBar/OptionsDialog/Options02_Tabs.xaml index 794ddbf..5bb5b0a 100644 --- a/QTTabBar/OptionsDialog/Options02_Tabs.xaml +++ b/QTTabBar/OptionsDialog/Options02_Tabs.xaml @@ -123,7 +123,7 @@ Content="{qt:Resx Key=Options_Page02_Tabs, Index=5}" IsChecked="{Binding Path=RenameAmbTabs}" Style="{StaticResource CheckStyle}"/> - + + + + +