Add Ctrl+C and Ctrl+A keyboard shortcuts to ScriptConsole output window#3258
Conversation
There was a problem hiding this comment.
PR Summary:
Adds a Win32 low-level keyboard hook (WH_KEYBOARD_LL) to the ScriptConsole output window to intercept Ctrl+C (copy) and Ctrl+A (select all) before Revit's IOleInPlaceActiveObject.TranslateAccelerator can consume them. The hook delegates to HtmlDocument.ExecCommand for native browser clipboard operations, is installed on construction, and disposed on window close.
Review Summary:
The approach is sound and well-motivated — a low-level hook is genuinely the only way to intercept keystrokes ahead of Revit's accelerator table. However, there are two functional bugs: (1) the focus guard uses renderer != null but doesn't check whether the renderer's ActiveX subtree actually has focus — without ContainsFocus, Ctrl+A will be stolen from stdinBar and other text inputs in the window; (2) Process.MainModule is fragile and unnecessary for WH_KEYBOARD_LL — the established pattern is GetModuleHandle(null). Additionally, installing the hook during InitializeComponent() (before window construction completes) creates a leak path if a later initialization step throws, since Window_Closing only fires for windows that were successfully shown.
Suggestions
- Add
SetWindowsHookExfailure detection: log or warn when_hookId == IntPtr.Zeroafter installation to aid diagnostics if the hook silently fails to install. Apply - Consider hooking
WM_SYSKEYDOWN(0x0104) in addition toWM_KEYDOWNto handle edge cases where Revit routes modified keystrokes through the system-key path. Apply
…t window Fixes pyrevitlabs#1729 The pyRevit output window hosts a WebBrowser control inside a WPF MetroWindow via WindowsFormsHost. Revit's message loop intercepts Ctrl+C via IOleInPlaceActiveObject.TranslateAccelerator before WPF, WinForms, IMessageFilter, or JavaScript events can see the keystroke. This adds a Win32 low-level keyboard hook (WH_KEYBOARD_LL) that intercepts Ctrl+C and Ctrl+A at the OS level, before Revit's accelerator table processes them. The hook only activates when the ScriptConsole is the active window with renderer focus, and forwards the keystrokes to HtmlDocument.ExecCommand for native browser copy. The hook is installed on window creation and disposed on close.
d0dff6b to
e4381f4
Compare
|
@devloai I pushed a new commit addressing your comments below, review and report back: |
|
All 4 previously raised issues have been addressed — looks clean now ✅
No new issues introduced. The implementation looks solid — the guard conditions ( |
|
This is something I wanted to implement 5 or 6 years ago! |
|
📦 New work-in-progress (wip) builds are available for 6.3.0.26094+0727-wip |
|
📦 New work-in-progress (wip) builds are available for 6.3.0.26095+0645-wip |
|
📦 New public release are available for 6.3.0.26095+0830 |
The hook is installed on window creation and disposed on close.
Add Ctrl+C and Ctrl+A keyboard shortcuts to ScriptConsole output window
Description
The pyRevit output window hosts a WebBrowser control inside a WPF MetroWindow via WindowsFormsHost. Revit's message loop intercepts Ctrl+C via IOleInPlaceActiveObject.TranslateAccelerator before WPF, WinForms, IMessageFilter, or JavaScript events can see the keystroke.
This adds a Win32 low-level keyboard hook (WH_KEYBOARD_LL) that intercepts Ctrl+C and Ctrl+A at the OS level, before Revit's accelerator table processes them. The hook only activates when the ScriptConsole is the active window with renderer focus, and forwards the keystrokes to HtmlDocument.ExecCommand for native browser copy.
Testing
Tested on Preflight checks, HTML output, PrintMD Output, Linkfy Output and Emojis Supported.
Select text in output window → Ctrl+C → paste → only selected text copied
Ctrl+A → all output text selected
Ctrl+A → Ctrl+C → paste → all text copied
No selection → Ctrl+C → no crash
Right-click → Copy → still works
"Copy All Text" button → still copies everything
Other Revit windows unaffected (Ctrl+C still works for Revit's own copy)
Multiple output windows → each has independent hook
Close output window → hook properly disposed
Related Issues
If applicable, link the issues resolved by this pull request:
Additional Notes
Problem
The pyRevit output window (ScriptConsole) renders print() output as HTML inside a System.Windows.Forms.WebBrowser control hosted in a WPF MetroWindow via WindowsFormsHost. Users can select text and copy via right-click → Copy, but the standard Ctrl+C keyboard shortcut does nothing.
Why this is hard
Revit's main message loop calls IOleInPlaceActiveObject.TranslateAccelerator which intercepts Ctrl+C for its own Copy command before any of the following can see the keystroke:
WPF PreviewKeyDown (tunneling events) — never fires
WinForms PreviewKeyDown on the WebBrowser — never fires
IMessageFilter.PreFilterMessage — never fires
ComponentDispatcher.ThreadPreprocessMessage — never fires
InputManager.Current.PreProcessInput — never fires
JavaScript document.onkeydown — never fires
Solution
A Win32 low-level keyboard hook (WH_KEYBOARD_LL via SetWindowsHookEx) intercepts keystrokes at the OS level, before Revit's message loop processes them. The hook only activates when the ScriptConsole window is active and the WebBrowser renderer has focus, forwarding:
Ctrl+C → HtmlDocument.ExecCommand("Copy") — copies selected text to clipboard
Ctrl+A → HtmlDocument.ExecCommand("SelectAll") — selects all text
The hook is installed when the ScriptConsole is created and unhooked when the window closes.