Skip to content

Commit

Permalink
1. Drag to add (with Control key) I say that you have to press ctrl b…
Browse files Browse the repository at this point in the history
…ecause 1) performance and 2) it was really annoying without it if I don't want to add window to set but it is automatically added.

2. Drag to remove: Just drag the window out will now remove from set unlike before will still try to bring it back. If you move the sets window, the window will follow. If you move the window itself, it auto ejects out of United Sets. (This feature is planned to use for easier split window implementation)
3. Drag and drop tabs to other United Sets window
  • Loading branch information
GetGet99 committed Oct 24, 2022
1 parent 14e1a1e commit 84b0be8
Show file tree
Hide file tree
Showing 9 changed files with 406 additions and 226 deletions.
35 changes: 28 additions & 7 deletions UnitedSets/Classes/HwndHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ public bool IsWindowVisible
ForceUpdateWindow();
}
}
long VisiblePropertyChangedToken;
DispatcherQueueTimer timer;
WINDOW_STYLE InitialStyle;
readonly long VisiblePropertyChangedToken;
readonly DispatcherQueueTimer timer;
readonly WINDOW_STYLE InitialStyle;
readonly WINDOW_EX_STYLE InitialExStyle;
public HwndHost(MicaWindow Window, WindowEx WindowToHost)
{
this.Window = Window;
Expand All @@ -53,7 +54,8 @@ public HwndHost(MicaWindow Window, WindowEx WindowToHost)
WindowToHost.Owner = WinUIWindow;
IsOwnerSetSuccessful = WindowToHost.Owner == WinUIWindow;
InitialStyle = WindowToHost.Style;
//WindowToHost.Style &= ~(WindowStyles.WS_CAPTION | WindowStyles.WS_BORDER);
//InitialExStyle = WindowToHost.ExStyle;
//if (!IsOwnerSetSuccessful) WindowToHost.ExStyle |= WINDOW_EX_STYLE.WS_EX_TOOLWINDOW;
WinUI.Changed += WinUIAppWindowChanged;
SizeChanged += WinUIAppWindowChanged;
timer = DispatcherQueue.CreateTimer();
Expand All @@ -73,6 +75,7 @@ public void DetachAndDispose()
Dispose();
var WindowToHost = this.WindowToHost;
WindowToHost.Style = InitialStyle;
//WindowToHost.ExStyle = InitialExStyle;
WindowToHost.Owner = default;
WindowToHost.IsResizable = _DefaultIsResizable;
WindowToHost.IsVisible = true;
Expand All @@ -82,8 +85,18 @@ public void DetachAndDispose()

public event Action? Closed;
public event Action? Updating;
public event Action? Detaching;
int CountDown = 5;
public async void ForceUpdateWindow()
{
var WindowToHost = this.WindowToHost;
bool Check = false;
if (CountDown > 0)
{
CountDown--;
if (CountDown == 0) WindowToHost.Redraw();
}
else Check = true;
if (XamlRoot is null) return;
var windowpos = WinUI.Position;
var Pt = TransformToVisual(Window.Content).TransformPoint(
Expand All @@ -94,7 +107,6 @@ public async void ForceUpdateWindow()
Pt.X = windowpos.X + Pt.X * scale;
Pt.Y = windowpos.Y + Pt.Y * scale;
var Size = ActualSize;
var WindowToHost = this.WindowToHost;
try
{
WindowToHost.IsResizable = false;
Expand All @@ -113,17 +125,26 @@ public async void ForceUpdateWindow()
{
var YShift = WinUIWindow.IsMaximized ? 8 : 0;
var oldBounds = WindowToHost.Bounds;
WindowToHost.Bounds = new Rectangle(
var newBounds = new Rectangle(
(int)Pt._x + 8,
(int)Pt._y + YShift,
(int)(Size.X * scale),
(int)(Size.Y * scale)
);
if (oldBounds != newBounds)
{
if (Check && WindowEx.ForegroundWindow == WindowToHost)
{
DetachAndDispose();
return;
}
else WindowToHost.Bounds = newBounds;
}
if (!IsOwnerSetSuccessful)
{
if (new WinWrapper.WindowRelative(WindowToHost).GetAboves().Take(10).Any(x => x == WinUIWindow))
{
await Task.Delay(200);
await Task.Delay(500);
if (oldBounds == WindowToHost.Bounds && IsWindowVisible)
{
WindowToHost.IsVisible = false;
Expand Down
40 changes: 23 additions & 17 deletions UnitedSets/Classes/HwndHostTab.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@
using System.Runtime.CompilerServices;
using Microsoft.UI.Input;
using Microsoft.UI.Xaml.Input;
using System.Linq;

namespace UnitedSets.Classes;

public class HwndHostTab : ITab, INotifyPropertyChanged
{
// Assumption: 1 Process = 1 Window
public static List<WindowEx> MainWindows = new();
public static event Action? OnUpdateStatusLoopComplete;
static SynchronizedCollection<HwndHostTab> HwndHostTabs = new();
static HwndHostTab()
Expand All @@ -32,6 +35,10 @@ static HwndHostTab()
{
while (true)
{
do
Thread.Sleep(500);
while (!MainWindows.Any(x => x.IsVisible));

try
{
foreach (var tab in HwndHostTabs)
Expand All @@ -42,15 +49,17 @@ static HwndHostTab()
OnUpdateStatusLoopComplete?.Invoke();
} catch
{

Debug.WriteLine("[United Sets Update Status Loop] Exception Occured");
}
Thread.Sleep(500);
}
});
})
{
Name = "United Sets Update Status Loop"
};
UpdateStatusLoop.Start();
}

public SettingsService Settings = App.Current.Services.GetService<SettingsService>(); // cursed
public SettingsService Settings = App.Current.Services.GetService<SettingsService>() ?? throw new InvalidOperationException("Settings Init Failed"); // cursed
public readonly WindowEx Window;
public event PropertyChangedEventHandler? PropertyChanged;
public event Action Closed;
Expand Down Expand Up @@ -96,9 +105,6 @@ public void TabClick(object sender, PointerRoutedEventArgs e)
HwndHost.FocusWindow();
}

bool _IsWindowFlashing = false;


public HwndHostTab(MainWindow Window, WindowEx WindowEx)
{
MainWindow = Window;
Expand Down Expand Up @@ -145,24 +151,24 @@ async static ValueTask<BitmapImage> ImageFromStream(Stream Stream)
return image;
}
public async Task TryCloseAsync() => await Window.TryCloseAsync();
public void TryClose()
{
if (Settings.ExitOnClose) // temporary
Window.TryClose();
else
DetachAndDispose();
}
public void TryClose() => Window.TryClose();
public void TabCloseRequested(TabViewItem sender, TabViewTabCloseRequestedEventArgs args)
{
MainWindow.TabView.SelectedItem = sender;
TryClose();
if (Settings.ExitOnClose)
_ = TryCloseAsync();
else
DetachAndDispose(JumpToCursor: true);
}
public void DetachAndDispose()
public async void DetachAndDispose(bool JumpToCursor)
{
var Window = this.Window;
HwndHost.DetachAndDispose();
PInvoke.GetCursorPos(out var CursorPos);
Window.Location = new Point(CursorPos.X - 100, CursorPos.Y - 30);
if (JumpToCursor)
Window.Location = new Point(CursorPos.X - 100, CursorPos.Y - 30);
Window.Focus();
Window.Redraw();
await Task.Delay(1000).ContinueWith(_ => Window.Redraw());
}
}
104 changes: 32 additions & 72 deletions UnitedSets/Helpers/KeyboardHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,30 @@
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
namespace UnitedSets.Helpers
{
public class KeyboardHelper : IDisposable
{
public event EventHandler<KeyboardHelperEventArgs> KeyboardPressed;
public event EventHandler<KeyboardHelperEventArgs>? KeyboardPressed;

public KeyboardHelper()
{
_windowsHookHandle = IntPtr.Zero;
_user32LibraryHandle = IntPtr.Zero;
_hookProc = LowLevelKeyboardProc; // we must keep alive _hookProc, because GC is not aware about SetWindowsHookEx behaviour.

_user32LibraryHandle = LoadLibrary("User32");
if (_user32LibraryHandle == IntPtr.Zero)
_user32LibraryHandle = PInvoke.LoadLibrary("User32");
if (_user32LibraryHandle.IsInvalid)
{
int errorCode = Marshal.GetLastWin32Error();
throw new Win32Exception(errorCode, $"Failed to load library 'User32.dll'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
}



_windowsHookHandle = SetWindowsHookEx(WH_KEYBOARD_LL, _hookProc, _user32LibraryHandle, 0);
if (_windowsHookHandle == IntPtr.Zero)
_windowsHookHandle = PInvoke.SetWindowsHookEx(WINDOWS_HOOK_ID.WH_KEYBOARD_LL, _hookProc, _user32LibraryHandle, 0);
if (_windowsHookHandle.IsInvalid)
{
int errorCode = Marshal.GetLastWin32Error();
throw new Win32Exception(errorCode, $"Failed to adjust keyboard hooks for '{Process.GetCurrentProcess().ProcessName}'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
Expand All @@ -41,28 +41,30 @@ protected virtual void Dispose(bool disposing)
if (disposing)
{
// because we can unhook only in the same thread, not in garbage collector thread
if (_windowsHookHandle != IntPtr.Zero)
if (!_windowsHookHandle.IsInvalid)
{
if (!UnhookWindowsHookEx(_windowsHookHandle))
{
int errorCode = Marshal.GetLastWin32Error();
throw new Win32Exception(errorCode, $"Failed to remove keyboard hooks for '{Process.GetCurrentProcess().ProcessName}'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
}
_windowsHookHandle = IntPtr.Zero;
_windowsHookHandle.Dispose();
//if (Error From Disposing)
//{
// int errorCode = Marshal.GetLastWin32Error();
// throw new Win32Exception(errorCode, $"Failed to remove keyboard hooks for '{Process.GetCurrentProcess().ProcessName}'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
//}

// ReSharper disable once DelegateSubtraction
#pragma warning disable CS8601
_hookProc -= LowLevelKeyboardProc;
#pragma warning restore CS8601
}
}

if (_user32LibraryHandle != IntPtr.Zero)
if (_user32LibraryHandle.IsInvalid) return;
{
if (!FreeLibrary(_user32LibraryHandle)) // reduces reference to library by 1.
{
int errorCode = Marshal.GetLastWin32Error();
throw new Win32Exception(errorCode, $"Failed to unload library 'User32.dll'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
}
_user32LibraryHandle = IntPtr.Zero;
_user32LibraryHandle.Dispose(); // reduces reference to library by 1.
//if (Error From Disposing)
//{
// int errorCode = Marshal.GetLastWin32Error();
// throw new Win32Exception(errorCode, $"Failed to unload library 'User32.dll'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
//}
}
}

Expand All @@ -77,50 +79,9 @@ public void Dispose()
GC.SuppressFinalize(this);
}

private IntPtr _windowsHookHandle;
private IntPtr _user32LibraryHandle;
private HookProc _hookProc;

delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);

[DllImport("kernel32.dll")]
private static extern IntPtr LoadLibrary(string lpFileName);

[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool FreeLibrary(IntPtr hModule);

/// <summary>
/// The SetWindowsHookEx function installs an application-defined hook procedure into a hook chain.
/// You would install a hook procedure to monitor the system for certain types of events. These events are
/// associated either with a specific thread or with all threads in the same desktop as the calling thread.
/// </summary>
/// <param name="idHook">hook type</param>
/// <param name="lpfn">hook procedure</param>
/// <param name="hMod">handle to application instance</param>
/// <param name="dwThreadId">thread identifier</param>
/// <returns>If the function succeeds, the return value is the handle to the hook procedure.</returns>
[DllImport("USER32", SetLastError = true)]
static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);

/// <summary>
/// The UnhookWindowsHookEx function removes a hook procedure installed in a hook chain by the SetWindowsHookEx function.
/// </summary>
/// <param name="hhk">handle to hook procedure</param>
/// <returns>If the function succeeds, the return value is true.</returns>
[DllImport("USER32", SetLastError = true)]
public static extern bool UnhookWindowsHookEx(IntPtr hHook);

/// <summary>
/// The CallNextHookEx function passes the hook information to the next hook procedure in the current hook chain.
/// A hook procedure can call this function either before or after processing the hook information.
/// </summary>
/// <param name="hHook">handle to current hook</param>
/// <param name="code">hook code passed to hook procedure</param>
/// <param name="wParam">value passed to hook procedure</param>
/// <param name="lParam">value passed to hook procedure</param>
/// <returns>If the function succeeds, the return value is true.</returns>
[DllImport("USER32", SetLastError = true)]
static extern IntPtr CallNextHookEx(IntPtr hHook, int code, IntPtr wParam, IntPtr lParam);
private UnhookWindowsHookExSafeHandle _windowsHookHandle;
private FreeLibrarySafeHandle _user32LibraryHandle;
private HOOKPROC _hookProc;

[StructLayout(LayoutKind.Sequential)]
public struct LowLevelKeyboardInputEvent
Expand Down Expand Up @@ -171,25 +132,24 @@ public enum KeyboardState
const int KfAltdown = 0x2000;
public const int LlkhfAltdown = (KfAltdown >> 8);

public IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam)
public LRESULT LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
bool fEatKeyStroke = false;

var wparamTyped = wParam.ToInt32();
var wparamTyped = (int)wParam.Value;
if (Enum.IsDefined(typeof(KeyboardState), wparamTyped))
{
object o = Marshal.PtrToStructure(lParam, typeof(LowLevelKeyboardInputEvent));
object o = Marshal.PtrToStructure(lParam, typeof(LowLevelKeyboardInputEvent))!;
LowLevelKeyboardInputEvent p = (LowLevelKeyboardInputEvent)o;

var eventArguments = new KeyboardHelperEventArgs(p, (KeyboardState)wparamTyped);

EventHandler<KeyboardHelperEventArgs> handler = KeyboardPressed;
handler?.Invoke(this, eventArguments);
KeyboardPressed?.Invoke(this, eventArguments);

fEatKeyStroke = eventArguments.Handled;
}

return fEatKeyStroke ? (IntPtr)1 : CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
return fEatKeyStroke ? (LRESULT)1 : PInvoke.CallNextHookEx(null, nCode, wParam, lParam);
}
}
public class KeyboardHelperEventArgs : HandledEventArgs
Expand Down
24 changes: 8 additions & 16 deletions UnitedSets/Interfaces/ITab.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.UI.Xaml.Media.Imaging;
using UnitedSets.Classes;

namespace UnitedSets.Interfaces
namespace UnitedSets.Interfaces;

public interface ITab
{
interface ITab
{
BitmapImage? Icon { get; }
string Title { get; }
HwndHost HwndHost { get; }
bool Selected { get; set; }
}
BitmapImage? Icon { get; }
string Title { get; }
HwndHost HwndHost { get; }
bool Selected { get; set; }
}
Loading

0 comments on commit 84b0be8

Please sign in to comment.