#334 New support of the mpv option snap-window

This commit is contained in:
stax76
2022-06-11 09:32:02 +02:00
parent 34031fa15d
commit b17ed3675e
10 changed files with 197 additions and 42 deletions

View File

@@ -4,8 +4,9 @@
- New tutorial: [Extending mpv and mpv.net via Lua scripting](https://github.com/stax76/mpv.net/wiki/Extending-mpv-and-mpv.net-via-Lua-scripting)
- New options `autofit-image` and `autofit-audio`, like autofit but used for image and audio files. Default 80.
- New [auto-mode](https://github.com/stax76/mpv-scripts) script to use mpv and mpv.net as image viewer and audio player.
- New support of the mpv option `snap-window`.
- Fix long commands causing key bindings not visible in the command palette.
- Fix compatibility with mpv-osc-tethys, which is a new osc script.
- Fix script compatibility with mordenx and mpv-osc-tethys.
- Fix borderless window not resizable with mouse.

View File

@@ -434,12 +434,6 @@ Window size is remembered in the current session.
**always**
Window size is always remembered.
#### --start-threshold=\<milliseconds\>
Threshold in milliseconds to wait for libmpv returning the video
resolution before the window is shown, otherwise default dimensions
are used as defined by autofit and start-size. Default: 1500
#### --minimum-aspect-ratio=\<float\>
Minimum aspect ratio, if the AR is smaller than the defined value then
@@ -450,6 +444,11 @@ with cover art. Default: 0
Save the window position on exit. Default: no
#### --start-threshold=\<milliseconds\>
Threshold in milliseconds to wait for libmpv returning the video
resolution before the window is shown, otherwise default dimensions
are used as defined by autofit and start-size. Default: 1500
### Playback
@@ -766,6 +765,7 @@ https://mpv.io/manual/master/#window
- [keepaspect-window](https://mpv.io/manual/master/#options-keepaspect-window)
- [ontop](https://mpv.io/manual/master/#options-ontop)
- [screen](https://mpv.io/manual/master/#options-screen)
- [snap-window](https://mpv.io/manual/master/#options-snap-window)
- [title](https://mpv.io/manual/master/#options-title)
- [window-maximized](https://mpv.io/manual/master/#options-window-maximized)
- [window-minimized](https://mpv.io/manual/master/#options-window-minimized)
@@ -774,9 +774,12 @@ https://mpv.io/manual/master/#window
**Partly implemented are:**
- [autofit-larger](https://mpv.io/manual/master/#options-autofit-larger)
- [autofit-smaller](https://mpv.io/manual/master/#options-autofit-smaller)
- [autofit](https://mpv.io/manual/master/#options-autofit)
- [autofit-larger](https://mpv.io/manual/master/#options-autofit-larger)
Supported is a single integer value in the range 0-100.
- [autofit-smaller](https://mpv.io/manual/master/#options-autofit-smaller)
Supported is a single integer value in the range 0-100.
- [autofit](https://mpv.io/manual/master/#options-autofit)
Supported is a single integer value in the range 0-100.
mpv.net specific window features are documented in the [screen section](#screen).

View File

@@ -98,6 +98,7 @@ namespace mpvnet
public bool KeepaspectWindow { get; set; }
public bool Paused { get; set; }
public bool Shown { get; set; }
public bool SnapWindow { get; set; }
public bool TaskbarProgress { get; set; } = true;
public bool WasInitialSizeSet;
public bool WindowMaximized { get; set; }
@@ -268,16 +269,17 @@ namespace mpvnet
AutofitLarger = result / 100f;
}
break;
case "border": Border = value == "yes"; break;
case "fs":
case "fullscreen": Fullscreen = value == "yes"; break;
case "border": Border = value == "yes"; break;
case "gpu-api": GPUAPI = value; break;
case "keepaspect-window": KeepaspectWindow = value == "yes"; break;
case "screen": Screen = Convert.ToInt32(value); break;
case "snap-window": SnapWindow = value == "yes"; break;
case "taskbar-progress": TaskbarProgress = value == "yes"; break;
case "vo": VO = value; break;
case "window-maximized": WindowMaximized = value == "yes"; break;
case "window-minimized": WindowMinimized = value == "yes"; break;
case "taskbar-progress": TaskbarProgress = value == "yes"; break;
case "screen": Screen = Convert.ToInt32(value); break;
case "gpu-api": GPUAPI = value; break;
case "vo": VO = value; break;
}
if (AutofitLarger > 1)

View File

@@ -21,6 +21,9 @@ namespace mpvnet
[DllImport("user32.dll")]
public static extern uint ActivateKeyboardLayout(IntPtr hkl, uint flags);
[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr FindWindowEx(
IntPtr parentHandle, IntPtr childAfter, string lclassName, string windowTitle);
@@ -88,6 +91,37 @@ namespace mpvnet
[DllImport("gdi32.dll")]
public static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
[DllImport("dwmapi.dll")]
public static extern int DwmGetWindowAttribute(
IntPtr hwnd, uint dwAttribute, out RECT pvAttribute, uint cbAttribute);
public static bool GetDwmWindowRect(IntPtr handle, out RECT rect)
{
const uint DWMWA_EXTENDED_FRAME_BOUNDS = 9;
return 0 == DwmGetWindowAttribute(handle, DWMWA_EXTENDED_FRAME_BOUNDS, out rect, (uint)Marshal.SizeOf<RECT>());
}
public static Rectangle GetWorkingArea(IntPtr handle, Rectangle workingArea)
{
if (handle != IntPtr.Zero && GetDwmWindowRect(handle, out RECT dwmRect) &&
GetWindowRect(handle, out RECT rect))
{
int left = workingArea.Left;
int top = workingArea.Top;
int right = workingArea.Right;
int bottom = workingArea.Bottom;
left += rect.Left - dwmRect.Left;
top -= rect.Top - dwmRect.Top;
right -= dwmRect.Right - rect.Right;
bottom -= dwmRect.Bottom - rect.Bottom;
return new Rectangle(left, top, right - left, bottom - top);
}
return workingArea;
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
@@ -116,6 +150,16 @@ namespace mpvnet
public Size Size => new Size(Right - Left, Bottom - Top);
public int Width => Right - Left;
public int Height => Bottom - Top;
public static RECT FromRectangle(Rectangle rect)
{
return new RECT(rect.X, rect.Y, rect.X + rect.Width, rect.Y + rect.Height);
}
public override string ToString()
{
return "{Left=" + Left + ",Top=" + Top + ",Right=" + Right + ",Bottom=" + Bottom + "}";
}
}
[StructLayout(LayoutKind.Sequential)]

View File

@@ -459,12 +459,6 @@ help = keepaspect-window will lock the window size to the video aspect. Default:
option = yes
option = no
[setting]
name = start-threshold
file = mpvnet
filter = Screen
help = Threshold in milliseconds to wait for libmpv returning the video resolution before the window is shown, otherwise default dimensions are used as defined by autofit and start-size. Default: 1500 (mpv.net specific option)
[setting]
name = minimum-aspect-ratio
file = mpvnet
@@ -481,6 +475,16 @@ help = Save the window position on exit. (mpv.net specific option)
option = yes
option = no
[setting]
name = snap-window
file = mpv
default = no
filter = Screen
help = Snap the player window to screen edges.
option = yes
option = no
[setting]
name = window-maximized
file = mpv
@@ -491,6 +495,12 @@ help = Start with a maximized window.
option = yes
option = no
[setting]
name = start-threshold
file = mpvnet
filter = Screen
help = Threshold in milliseconds to wait for libmpv returning the video resolution before the window is shown, otherwise default dimensions are used as defined by autofit and start-size. Default: 1500 (mpv.net specific option)
[setting]
name = taskbar-progress
file = mpv

View File

@@ -21,6 +21,7 @@ namespace mpvnet
{
public partial class MainForm : Form
{
public SnapManager SnapManager = new SnapManager();
public ElementHost CommandPaletteHost { get; set; }
public IntPtr mpvWindowHandle { get; set; }
public static MainForm Instance { get; set; }
@@ -221,7 +222,7 @@ namespace mpvnet
if (FormBorderStyle == FormBorderStyle.None)
top = ClientSize.Height * 0.1f;
return pos.Y > ClientSize.Height * 0.8 || pos.Y < top;
return pos.Y > ClientSize.Height * 0.78 || pos.Y < top;
}
void UpdateMenu()
@@ -432,13 +433,14 @@ namespace mpvnet
}
Screen screen = Screen.FromControl(this);
int autoFitHeight = Convert.ToInt32(screen.WorkingArea.Height * Core.Autofit);
Rectangle workingArea = GetWorkingArea(Handle, screen.WorkingArea);
int autoFitHeight = Convert.ToInt32(workingArea.Height * Core.Autofit);
if (App.AutofitAudio > 1) App.AutofitAudio = 1;
if (App.AutofitImage > 1) App.AutofitImage = 1;
if (Core.IsAudio) autoFitHeight = Convert.ToInt32(screen.WorkingArea.Height * App.AutofitAudio);
if (Core.IsImage) autoFitHeight = Convert.ToInt32(screen.WorkingArea.Height * App.AutofitImage);
if (Core.IsAudio) autoFitHeight = Convert.ToInt32(workingArea.Height * App.AutofitAudio);
if (Core.IsImage) autoFitHeight = Convert.ToInt32(workingArea.Height * App.AutofitImage);
if (Core.VideoSize.Height == 0 || Core.VideoSize.Width == 0 ||
Core.VideoSize.Width / (float)Core.VideoSize.Height < App.MinimumAspectRatio)
@@ -509,8 +511,10 @@ namespace mpvnet
void SetSize(int width, int height, Screen screen, bool checkAutofit = true)
{
int maxHeight = screen.WorkingArea.Height - (Height - ClientSize.Height) - 2;
int maxWidth = screen.WorkingArea.Width - (Width - ClientSize.Width);
Rectangle workingArea = GetWorkingArea(Handle, screen.WorkingArea);
int maxHeight = workingArea.Height - (Height - ClientSize.Height) - 2;
int maxWidth = workingArea.Width - (Width - ClientSize.Width);
int startWidth = width;
int startHeight = height;
@@ -565,10 +569,10 @@ namespace mpvnet
Screen[] screens = Screen.AllScreens;
int minLeft = screens.Select(val => val.WorkingArea.X).Min();
int maxRight = screens.Select(val => val.WorkingArea.Right).Max();
int minTop = screens.Select(val => val.WorkingArea.Y).Min();
int maxBottom = screens.Select(val => val.WorkingArea.Bottom).Max();
int minLeft = screens.Select(val => GetWorkingArea(Handle, val.WorkingArea).X).Min();
int maxRight = screens.Select(val => GetWorkingArea(Handle, val.WorkingArea).Right).Max();
int minTop = screens.Select(val => GetWorkingArea(Handle, val.WorkingArea).Y).Min();
int maxBottom = screens.Select(val => GetWorkingArea(Handle, val.WorkingArea).Bottom).Max();
if (left < minLeft)
left = minLeft;
@@ -629,7 +633,7 @@ namespace mpvnet
public int GetHorizontalLocation(Screen screen)
{
Rectangle workingArea = screen.WorkingArea;
Rectangle workingArea = GetWorkingArea(Handle, screen.WorkingArea);
Rectangle rect = new Rectangle(Left - workingArea.X, Top - workingArea.Y, Width, Height);
if (workingArea.Width / (float)Width < 1.1)
@@ -646,7 +650,7 @@ namespace mpvnet
public int GetVerticalLocation(Screen screen)
{
Rectangle workingArea = screen.WorkingArea;
Rectangle workingArea = GetWorkingArea(Handle, screen.WorkingArea);
Rectangle rect = new Rectangle(Left - workingArea.X, Top - workingArea.Y, Width, Height);
if (workingArea.Height / (float)Height < 1.1)
@@ -831,7 +835,7 @@ namespace mpvnet
if (mpvWindowHandle != IntPtr.Zero)
m.Result = SendMessage(mpvWindowHandle, m.Msg, m.WParam, m.LParam);
break;
case 0x051: // WM_INPUTLANGCHANGE
case 0x51: // WM_INPUTLANGCHANGE
ActivateKeyboardLayout(m.LParam, 0x00000100u /*KLF_SETFORPROCESS*/);
break;
case 0x319: // WM_APPCOMMAND
@@ -846,10 +850,10 @@ namespace mpvnet
}
}
break;
case 0x0312: // WM_HOTKEY
case 0x312: // WM_HOTKEY
GlobalHotkey.Execute(m.WParam.ToInt32());
break;
case 0x0200: // WM_MOUSEMOVE
case 0x200: // WM_MOUSEMOVE
if (Environment.TickCount - LastCycleFullscreen > 500)
{
Point pos = PointToClient(Cursor.Position);
@@ -869,7 +873,7 @@ namespace mpvnet
Core.Command($"mouse {pos.X} {pos.Y} 0 double");
}
break;
case 0x02E0: // WM_DPICHANGED
case 0x2E0: // WM_DPICHANGED
{
if (!Core.Shown)
break;
@@ -878,7 +882,7 @@ namespace mpvnet
SetWindowPos(Handle, IntPtr.Zero, rect.Left, rect.Top, rect.Width, rect.Height, 0);
}
break;
case 0x0214: // WM_SIZING
case 0x214: // WM_SIZING
if (Core.KeepaspectWindow)
{
RECT rc = Marshal.PtrToStructure<RECT>(m.LParam);
@@ -906,7 +910,7 @@ namespace mpvnet
m.Result = new IntPtr(1);
}
return;
case 0x004A: // WM_COPYDATA
case 0x4A: // WM_COPYDATA
{
var copyData = (COPYDATASTRUCT)m.GetLParam(typeof(COPYDATASTRUCT));
string[] args = copyData.lpData.Split('\n');
@@ -990,6 +994,15 @@ namespace mpvnet
}
}
break;
case 0x231: // WM_ENTERSIZEMOVE
case 0x005: // WM_SIZE
if (Core.SnapWindow)
SnapManager.OnSizeAndEnterSizeMove(this);
break;
case 0x216: // WM_MOVING
if (Core.SnapWindow)
SnapManager.OnMoving(ref m);
break;
}
if (m.Msg == TaskbarButtonCreatedMessage && Core.TaskbarProgress)

View File

@@ -0,0 +1,81 @@

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using static mpvnet.Native;
namespace mpvnet
{
public class SnapManager
{
int DragOffsetX { get; set; }
int DragOffsetY { get; set; }
IntPtr Handle;
[Flags]
public enum SnapLocation
{
None = 0,
Left = 1 << 0,
Top = 1 << 1,
Right = 1 << 2,
Bottom = 1 << 3,
All = Left | Top | Right | Bottom
}
public int AnchorDistance { get; set; }
public int SnapDistance { get; set; }
bool InSnapRange(int a, int b) => Math.Abs(a - b) < SnapDistance;
void FindSnap(ref Rectangle effectiveBounds)
{
Screen currentScreen = Screen.FromPoint(effectiveBounds.Location);
Rectangle workingArea = GetWorkingArea(Handle, currentScreen.WorkingArea);
if (InSnapRange(effectiveBounds.Left, workingArea.Left + AnchorDistance))
effectiveBounds.X = workingArea.Left + AnchorDistance;
else if (InSnapRange(effectiveBounds.Right, workingArea.Right - AnchorDistance))
effectiveBounds.X = workingArea.Right - AnchorDistance - effectiveBounds.Width;
if (InSnapRange(effectiveBounds.Top, workingArea.Top + AnchorDistance))
effectiveBounds.Y = workingArea.Top + AnchorDistance;
else if (InSnapRange(effectiveBounds.Bottom, workingArea.Bottom - AnchorDistance))
effectiveBounds.Y = workingArea.Bottom - AnchorDistance - effectiveBounds.Height;
}
public void OnMoving(ref Message m)
{
if (Handle == IntPtr.Zero)
return;
RECT boundsLtrb = Marshal.PtrToStructure<RECT>(m.LParam);
Rectangle bounds = boundsLtrb.ToRectangle();
// This is where the window _would_ be located if snapping
// had not occurred. This prevents the cursor from sliding
// off the title bar if the snap distance is too large.
Rectangle effectiveBounds = new Rectangle(
Cursor.Position.X - DragOffsetX,
Cursor.Position.Y - DragOffsetY,
bounds.Width,
bounds.Height);
FindSnap(ref effectiveBounds);
RECT newLtrb = RECT.FromRectangle(effectiveBounds);
Marshal.StructureToPtr(newLtrb, m.LParam, false);
m.Result = new IntPtr(1);
}
public void OnSizeAndEnterSizeMove(Form form)
{
Handle = form.Handle;
SnapDistance = form.Font.Height;
// Need to handle window size changed as well when
// un-maximizing the form by dragging the title bar.
DragOffsetX = Cursor.Position.X - form.Left;
DragOffsetY = Cursor.Position.Y - form.Top;
}
}
}

View File

@@ -93,6 +93,7 @@
<Compile Include="Misc\Common.cs" />
<Compile Include="Misc\FolderBrowser.cs" />
<Compile Include="Misc\JSONParser.cs" />
<Compile Include="WinForms\SnapManager.cs" />
<Compile Include="WPF\HandyControl\Controls\Attach\BorderElement.cs" />
<Compile Include="WPF\HandyControl\Controls\Attach\MenuTopLineAttach.cs" />
<Compile Include="WPF\HandyControl\Tools\Converter\BorderCircularConverter.cs" />
@@ -186,10 +187,10 @@
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Native\libmpv.cs" />
<Compile Include="Misc\MainForm.cs">
<Compile Include="WinForms\MainForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Misc\MainForm.Designer.cs">
<Compile Include="WinForms\MainForm.Designer.cs">
<DependentUpon>MainForm.cs</DependentUpon>
</Compile>
<Compile Include="Misc\Misc.cs" />
@@ -211,7 +212,7 @@
<DependentUpon>InputWindow.xaml</DependentUpon>
</Compile>
<Compile Include="WPF\WPF.cs" />
<EmbeddedResource Include="Misc\MainForm.resx">
<EmbeddedResource Include="WinForms\MainForm.resx">
<DependentUpon>MainForm.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>