Skip to content
6 changes: 5 additions & 1 deletion QuickLook/KeystrokeDispatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ protected KeystrokeDispatcher()
_validKeys =
[
Keys.Up, Keys.Down, Keys.Left, Keys.Right,
Keys.Enter, Keys.Space, Keys.Escape
Keys.Enter, Keys.Space, Keys.Escape, Keys.F11
];
}

Expand Down Expand Up @@ -135,6 +135,10 @@ private void InvokeRoutine(Keys key, bool isKeyDown)
case Keys.Space:
PipeServerManager.SendMessage(PipeMessages.Toggle);
break;

case Keys.F11:
PipeServerManager.SendMessage(PipeMessages.Fullscreen);
break;
Comment on lines 135 to +141
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

F11 is handled on KeyDown, but low-level hooks receive auto-repeat WM_KEYDOWN events while the key is held. Holding F11 will likely spam toggle requests and rapidly flip fullscreen on/off. Consider de-bouncing (track an "F11 is down" flag like Space) or triggering fullscreen on KeyUp instead.

Copilot uses AI. Check for mistakes.
}
}
else
Expand Down
7 changes: 7 additions & 0 deletions QuickLook/PipeServerManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public static class PipeMessages
public const string Forget = "QuickLook.App.PipeMessages.Forget";
public const string Close = "QuickLook.App.PipeMessages.Close";
public const string Quit = "QuickLook.App.PipeMessages.Quit";
public const string Fullscreen = "QuickLook.App.PipeMessages.Fullscreen";
}

public class PipeServerManager : IDisposable
Expand Down Expand Up @@ -161,6 +162,12 @@ private bool MessageReceived(string msg)
DispatcherPriority.ApplicationIdle);
return false;

case PipeMessages.Fullscreen:
Application.Current.Dispatcher.BeginInvoke(
new Action(() => ViewWindowManager.GetInstance().ToggleFullscreen()),
DispatcherPriority.ApplicationIdle);
return false;

case PipeMessages.Quit:
return true;

Expand Down
8 changes: 8 additions & 0 deletions QuickLook/ViewWindowManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,14 @@ public void ReloadPreview()
BeginShowNewWindow(_invokedPath, matchedPlugin);
}

public void ToggleFullscreen()
{
if (!_viewerWindow.IsVisible)
return;

_viewerWindow.ToggleFullscreen();
}

private void BeginShowNewWindow(string path, IViewer matchedPlugin)
{
_viewerWindow.UnloadPlugin();
Expand Down
66 changes: 66 additions & 0 deletions QuickLook/ViewerWindow.Actions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Threading;
using Wpf.Ui.Controls;
using WinForms = System.Windows.Forms;
using MenuItem = System.Windows.Controls.MenuItem;

namespace QuickLook;
Expand Down Expand Up @@ -62,6 +64,70 @@ internal void RunAndClose()
Close();
}

internal void ToggleFullscreen()
{
if (_isFullscreen)
{
// Exit fullscreen
_isFullscreen = false;

// Restore window properties
WindowStyle = _preFullscreenWindowStyle;
ResizeMode = _preFullscreenResizeMode;

// Restore position and size
Left = _preFullscreenBounds.Left;
Top = _preFullscreenBounds.Top;
Width = _preFullscreenBounds.Width;
Height = _preFullscreenBounds.Height;

Comment on lines +79 to +83
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Entering/exiting fullscreen changes Width/Height programmatically, but the window’s SizeChanged handler treats any non-ignored resize as user-driven and sets _customWindowSize. This means toggling fullscreen can “lock” future previews to the last fullscreen/restored size instead of auto-sizing to the plugin’s PreferredSize. Consider temporarily suppressing SaveWindowSizeOnSizeChanged during fullscreen transitions (e.g., set _ignoreNextWindowSizeChange for each expected size change or store/restore _customWindowSize around the toggle).

Copilot uses AI. Check for mistakes.
// Restore window state last to avoid flicker
WindowState = _preFullscreenWindowState;
}
else
{
// Enter fullscreen
_isFullscreen = true;

// Save current window properties before any changes
_preFullscreenWindowState = WindowState;
_preFullscreenWindowStyle = WindowStyle;
_preFullscreenResizeMode = ResizeMode;

Comment on lines +89 to +96
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fullscreen uses WindowState.Normal, so subsequent preview loads (BeginShow → PositionWindow) can still reposition/resize the window and effectively break fullscreen while _isFullscreen remains true. Consider short-circuiting PositionWindow/BeginShow while _isFullscreen is set, or otherwise ensuring plugin-driven resizing doesn’t run during fullscreen.

Copilot uses AI. Check for mistakes.
// Get current bounds (account for maximized state)
if (WindowState == WindowState.Maximized)
{
_preFullscreenBounds = RestoreBounds;
}
else
{
_preFullscreenBounds = new Rect(Left, Top, Width, Height);
}

// Get the screen bounds where the window is currently located
var screen = WinForms.Screen.FromHandle(new WindowInteropHelper(this).Handle);
var screenBounds = screen.Bounds;

// Get DPI scale factor for proper coordinate conversion
// scale.Horizontal and scale.Vertical contain the DPI scaling ratios (e.g., 1.5 for 150%)
var scale = DisplayDeviceHelper.GetScaleFactorFromWindow(this);

// Set to normal state first to allow manual positioning
WindowState = WindowState.Normal;

// Hide window chrome for true fullscreen
WindowStyle = WindowStyle.None;
ResizeMode = ResizeMode.NoResize;

// Convert screen bounds from physical pixels to DIPs for WPF
var dipWidth = screenBounds.Width / scale.Horizontal;
var dipHeight = screenBounds.Height / scale.Vertical;

// Use MoveWindow to set position and size with proper DPI handling
this.MoveWindow(screenBounds.Left, screenBounds.Top, dipWidth, dipHeight);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Screen coordinates are mixed between physical pixels and DIPs when calling MoveWindow, which can break on high-DPI setups.

Here screenBounds.Left/Top are still in physical pixels while dipWidth/dipHeight are DIPs. Please either convert Left/Top with the same DPI scale, or keep all four values in physical pixels so MoveWindow gets a consistent coordinate system.

}
}

private void PositionWindow(Size size)
{
// If the window is now maximized, do not move it
Expand Down
5 changes: 5 additions & 0 deletions QuickLook/ViewerWindow.Properties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ public partial class ViewerWindow : INotifyPropertyChanged

private bool _canOldPluginResize;
private bool _pinned;
private bool _isFullscreen;
private WindowState _preFullscreenWindowState;
private WindowStyle _preFullscreenWindowStyle;
private ResizeMode _preFullscreenResizeMode;
private Rect _preFullscreenBounds;

public bool Pinned
{
Expand Down
Loading