Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OSD on all display #1469

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions LenovoLegionToolkit.Lib/NativeMethods.txt
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,7 @@ RegisterWindowMessage
WlanOpenHandle
WlanCloseHandle
WlanRegisterNotification

MONITORINFOF_PRIMARY

SetWindowPos
1 change: 1 addition & 0 deletions LenovoLegionToolkit.Lib/Settings/ApplicationSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class ApplicationSettingsStore
public bool DontShowNotifications { get; set; }
public NotificationPosition NotificationPosition { get; set; } = NotificationPosition.BottomCenter;
public NotificationDuration NotificationDuration { get; set; } = NotificationDuration.Normal;
public bool NotificationOnAllScreens { get; set; }
public Notifications Notifications { get; set; } = new();
public TemperatureUnit TemperatureUnit { get; set; }
public List<RefreshRate> ExcludedRefreshRates { get; set; } = [];
Expand Down
18 changes: 18 additions & 0 deletions LenovoLegionToolkit.WPF/Resources/Resource.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions LenovoLegionToolkit.WPF/Resources/Resource.resx
Original file line number Diff line number Diff line change
Expand Up @@ -2188,6 +2188,12 @@ Supported formats are: {1}.</value>
<data name="MainWindows_VantageRunning" xml:space="preserve">
<value>Lenovo Vantage and/or ImController is running in the background.</value>
</data>
<data name="NotificationsSettingsWindow_NotificationOnAllScreens_Message" xml:space="preserve">
<value>Show notification on all screens connected to your device.</value>
</data>
<data name="NotificationsSettingsWindow_NotificationOnAllScreens_Title" xml:space="preserve">
<value>Notification on all screens</value>
</data>
<data name="SettingsPage_CheckUpdatesButton_Content" xml:space="preserve">
<value>Check</value>
</data>
Expand Down
9 changes: 9 additions & 0 deletions LenovoLegionToolkit.WPF/Structs.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Windows;
using LenovoLegionToolkit.WPF.Resources;

namespace LenovoLegionToolkit.WPF;
Expand Down Expand Up @@ -56,3 +57,11 @@ public override string ToString() =>
$" {nameof(CustomName)}: {CustomName}," +
$" {nameof(Items)}: {string.Join(",", Items)}";
}

public readonly struct ScreenInfo(Rect workArea, uint dpiX, uint dpiY, bool isPrimary)
{
public Rect WorkArea { get; } = workArea;
public uint DpiX { get; } = dpiX;
public uint DpiY { get; } = dpiY;
public bool IsPrimary { get; } = isPrimary;
}
52 changes: 39 additions & 13 deletions LenovoLegionToolkit.WPF/Utils/NotificationsManager.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
Expand All @@ -22,7 +23,7 @@ public class NotificationsManager

private readonly ApplicationSettings _settings;

private NotificationWindow? _window;
private List<NotificationWindow?> _windows = [];

public NotificationsManager(ApplicationSettings settings)
{
Expand Down Expand Up @@ -229,22 +230,47 @@ private void ShowNotification(SymbolRegular symbol, SymbolRegular? overlaySymbol
if (App.Current.MainWindow is not MainWindow mainWindow)
return;

if (_window is not null)
if (_windows.Count != 0)
{
_window.WindowStyle = WindowStyle.None;
_window.Close();
foreach (var window in _windows)
{
if (window is not null)
{
window.WindowStyle = WindowStyle.None;
window.Close();
}
}
_windows.Clear();
}

var nw = new NotificationWindow(symbol, overlaySymbol, symbolTransform, text, clickAction, _settings.Store.NotificationPosition) { Owner = mainWindow };
nw.Show(_settings.Store.NotificationDuration switch
ScreenHelper.UpdateScreenInfos();
if (_settings.Store.NotificationOnAllScreens)
{
NotificationDuration.Short => 500,
NotificationDuration.Long => 2500,
NotificationDuration.Normal => 1000,
_ => throw new ArgumentException(nameof(_settings.Store.NotificationDuration))
});

_window = nw;
foreach (var screen in ScreenHelper.Screens)
{
var nw = new NotificationWindow(symbol, overlaySymbol, symbolTransform, text, clickAction, screen, _settings.Store.NotificationPosition) { Owner = mainWindow };
nw.Show(_settings.Store.NotificationDuration switch
{
NotificationDuration.Short => 500,
NotificationDuration.Long => 2500,
NotificationDuration.Normal => 1000,
_ => throw new ArgumentException(nameof(_settings.Store.NotificationDuration))
});
_windows.Add(nw);
}
}
else
{
var nw = new NotificationWindow(symbol, overlaySymbol, symbolTransform, text, clickAction, ScreenHelper.PrimaryScreen, _settings.Store.NotificationPosition) { Owner = mainWindow };
nw.Show(_settings.Store.NotificationDuration switch
{
NotificationDuration.Short => 500,
NotificationDuration.Long => 2500,
NotificationDuration.Normal => 1000,
_ => throw new ArgumentException(nameof(_settings.Store.NotificationDuration))
});
_windows.Add(nw);
}
}

private static void UpdateAvailableAction()
Expand Down
42 changes: 32 additions & 10 deletions LenovoLegionToolkit.WPF/Utils/ScreenHelper.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System.Runtime.InteropServices;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows;
using Windows.Win32;
using Windows.Win32.Graphics.Gdi;
using Windows.Win32.UI.HiDpi;
using Point = System.Drawing.Point;


#pragma warning disable CA1416
Expand All @@ -12,21 +14,41 @@ namespace LenovoLegionToolkit.WPF.Utils;

public static class ScreenHelper
{
public static Rect GetPrimaryDesktopWorkingArea()
public static List<ScreenInfo> Screens { get; private set; } = [];

public static ScreenInfo PrimaryScreen => Screens.Where(s => s.IsPrimary).First();

public static void UpdateScreenInfos()
{
Screens.Clear();
EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, MonitorEnumProc, IntPtr.Zero);
}

[DllImport("user32.dll")]
private static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, EnumDisplayMonitorsDelegate lpfnEnum, IntPtr dwData);

private delegate bool EnumDisplayMonitorsDelegate(HMONITOR hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData);

private static bool MonitorEnumProc(HMONITOR hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData)
{
var screen = PInvoke.MonitorFromPoint(Point.Empty, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTOPRIMARY);
MONITORINFO monitorInfo = new() { cbSize = (uint)Marshal.SizeOf<MONITORINFO>() };

var monitorInfo = new MONITORINFO { cbSize = (uint)Marshal.SizeOf<MONITORINFO>() };
if (!PInvoke.GetMonitorInfo(screen, ref monitorInfo))
return SystemParameters.WorkArea;
if (!PInvoke.GetMonitorInfo(hMonitor, ref monitorInfo))
return true;

if (!PInvoke.GetDpiForMonitor(screen, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out var dpiX, out var dpiY).Succeeded)
return SystemParameters.WorkArea;
if (!PInvoke.GetDpiForMonitor(hMonitor, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out var dpiX, out var dpiY).Succeeded)
return true;

var workArea = monitorInfo.rcWork;
var multiplierX = 96d / dpiX;
var multiplierY = 96d / dpiY;

return new Rect(workArea.X, workArea.Y, workArea.Width * multiplierX, workArea.Height * multiplierY);
Screens.Add(new ScreenInfo(
new Rect(workArea.X, workArea.Y, workArea.Width * multiplierX, workArea.Height * multiplierY),
dpiX, dpiY,
(monitorInfo.dwFlags & PInvoke.MONITORINFOF_PRIMARY) != 0
));

return true;
}
}
3 changes: 2 additions & 1 deletion LenovoLegionToolkit.WPF/Windows/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,8 @@ private void RestoreSize()
Width = Math.Max(MinWidth, _applicationSettings.Store.WindowSize.Value.Width);
Height = Math.Max(MinHeight, _applicationSettings.Store.WindowSize.Value.Height);

var desktopWorkingArea = ScreenHelper.GetPrimaryDesktopWorkingArea();
ScreenHelper.UpdateScreenInfos();
var desktopWorkingArea = ScreenHelper.PrimaryScreen.WorkArea;

Left = (desktopWorkingArea.Width - Width) / 2 + desktopWorkingArea.Left;
Top = (desktopWorkingArea.Height - Height) / 2 + desktopWorkingArea.Top;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,17 @@
Click="DontShowNotificationsToggle_Click" />
</custom:CardControl>

<custom:CardControl Margin="0,0,0,8">
<custom:CardControl.Header>
<controls:CardHeaderControl Title="{x:Static resources:Resource.NotificationsSettingsWindow_NotificationOnAllScreens_Title}" Subtitle="{x:Static resources:Resource.NotificationsSettingsWindow_NotificationOnAllScreens_Message}" />
</custom:CardControl.Header>
<wpfui:ToggleSwitch
x:Name="_notificationOnAllScreensToggle"
Margin="0,0,0,8"
AutomationProperties.Name="{x:Static resources:Resource.NotificationsSettingsWindow_NotificationOnAllScreens_Title}"
Click="NotificationOnAllScreensToggle_Click" />
</custom:CardControl>

<custom:CardControl x:Name="_notificationPositionCard" Margin="0,0,0,8">
<custom:CardControl.Header>
<controls:CardHeaderControl Title="{x:Static resources:Resource.NotificationsSettingsWindow_NotificationPosition_Title}" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public NotificationsSettingsWindow()
InitializeComponent();

_dontShowNotificationsToggle.IsChecked = _settings.Store.DontShowNotifications;
_notificationOnAllScreensToggle.IsChecked = _settings.Store.NotificationOnAllScreens;

_notificationPositionComboBox.SetItems(Enum.GetValues<NotificationPosition>(), _settings.Store.NotificationPosition, v => v.GetDisplayName());
_notificationDurationComboBox.SetItems(Enum.GetValues<NotificationDuration>(), _settings.Store.NotificationDuration, v => v.GetDisplayName());
Expand Down Expand Up @@ -77,6 +78,16 @@ private void DontShowNotificationsToggle_Click(object sender, RoutedEventArgs e)
RefreshCards();
}

private void NotificationOnAllScreensToggle_Click(object sender, RoutedEventArgs e)
{
var state = _notificationOnAllScreensToggle.IsChecked;
if (state is null)
return;

_settings.Store.NotificationOnAllScreens = state.Value;
_settings.SynchronizeStore();
}

private void NotificationPositionComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!_notificationPositionComboBox.TryGetSelectedItem(out NotificationPosition state))
Expand Down
63 changes: 40 additions & 23 deletions LenovoLegionToolkit.WPF/Windows/Utils/NotificationWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media;
using LenovoLegionToolkit.Lib;
using LenovoLegionToolkit.WPF.Utils;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
using Wpf.Ui.Appearance;
using Wpf.Ui.Common;
using Wpf.Ui.Controls;
Expand Down Expand Up @@ -42,12 +46,12 @@ public class NotificationWindow : UiWindow
VerticalContentAlignment = VerticalAlignment.Center,
};

public NotificationWindow(SymbolRegular symbol, SymbolRegular? overlaySymbol, Action<SymbolIcon>? symbolTransform, string text, Action? clickAction, NotificationPosition position)
public NotificationWindow(SymbolRegular symbol, SymbolRegular? overlaySymbol, Action<SymbolIcon>? symbolTransform, string text, Action? clickAction, ScreenInfo screenInfo, NotificationPosition position)
{
InitializeStyle();
InitializeContent(symbol, overlaySymbol, symbolTransform, text);

SourceInitialized += (_, _) => InitializePosition(position);
SourceInitialized += (_, _) => InitializePosition(screenInfo.WorkArea, screenInfo.DpiX, screenInfo.DpiY, position);
MouseDown += (_, _) =>
{
Close();
Expand Down Expand Up @@ -80,56 +84,69 @@ private void InitializeStyle()
_textBlock.Foreground = (SolidColorBrush)FindResource("TextFillColorPrimaryBrush");
}

private void InitializePosition(NotificationPosition position)
private void InitializePosition(Rect workArea, uint dpiX, uint dpiY, NotificationPosition position)
{
var desktopWorkingArea = ScreenHelper.GetPrimaryDesktopWorkingArea();

_mainGrid.Measure(new Size(double.PositiveInfinity, 80));

var multiplierX = dpiX / 96d;
var multiplierY = dpiY / 96d;
Rect nativeWorkArea = new(workArea.Left, workArea.Top, workArea.Width * multiplierX, workArea.Height * multiplierY);

Width = MaxWidth = MinWidth = Math.Max(_mainGrid.DesiredSize.Width, 300);
Height = MaxHeight = MinHeight = _mainGrid.DesiredSize.Height;
var nativeWidth = Width * multiplierX;
var nativeHeight = Height * multiplierY;

const int margin = 16;
var nativeMarginX = margin * multiplierX;
var nativeMarginY = margin * multiplierY;

double nativeLeft = 0;
double nativeTop = 0;

switch (position)
{
case NotificationPosition.BottomRight:
Left = desktopWorkingArea.Right - Width - margin;
Top = desktopWorkingArea.Bottom - Height - margin;
nativeLeft = nativeWorkArea.Right - nativeWidth - nativeMarginX;
nativeTop = nativeWorkArea.Bottom - nativeHeight - nativeMarginY;
break;
case NotificationPosition.BottomCenter:
Left = (desktopWorkingArea.Right - Width) / 2;
Top = desktopWorkingArea.Bottom - Height - margin;
nativeLeft = nativeWorkArea.Left + (nativeWorkArea.Width - nativeWidth) / 2;
nativeTop = nativeWorkArea.Bottom - nativeHeight - nativeMarginY;
break;
case NotificationPosition.BottomLeft:
Left = desktopWorkingArea.Left + margin;
Top = desktopWorkingArea.Bottom - Height - margin;
nativeLeft = nativeWorkArea.Left + nativeMarginX;
nativeTop = nativeWorkArea.Bottom - nativeHeight - nativeMarginY;
break;
case NotificationPosition.CenterLeft:
Left = desktopWorkingArea.Left + margin;
Top = (desktopWorkingArea.Bottom - Height) / 2;
nativeLeft = nativeWorkArea.Left + nativeMarginX;
nativeTop = nativeWorkArea.Top + (nativeWorkArea.Height - nativeHeight) / 2;
break;
case NotificationPosition.TopLeft:
Left = desktopWorkingArea.Left + margin;
Top = desktopWorkingArea.Top + margin;
nativeLeft = nativeWorkArea.Left + nativeMarginX;
nativeTop = nativeWorkArea.Top + nativeMarginY;
break;
case NotificationPosition.TopCenter:
Left = (desktopWorkingArea.Right - Width) / 2;
Top = desktopWorkingArea.Top + margin;
nativeLeft = nativeWorkArea.Left + (nativeWorkArea.Width - nativeWidth) / 2;
nativeTop = nativeWorkArea.Top + nativeMarginY;
break;
case NotificationPosition.TopRight:
Left = desktopWorkingArea.Right - Width - margin;
Top = desktopWorkingArea.Top + margin;
nativeLeft = nativeWorkArea.Right - nativeWidth - nativeMarginX;
nativeTop = nativeWorkArea.Top + nativeMarginY;
break;
case NotificationPosition.CenterRight:
Left = desktopWorkingArea.Right - Width - margin;
Top = (desktopWorkingArea.Bottom - Height) / 2;
nativeLeft = nativeWorkArea.Right - nativeWidth - nativeMarginX;
nativeTop = nativeWorkArea.Top + (nativeWorkArea.Height - nativeHeight) / 2;
break;
case NotificationPosition.Center:
Left = (desktopWorkingArea.Right - Width) / 2;
Top = (desktopWorkingArea.Bottom - Height) / 2;
nativeLeft = nativeWorkArea.Left + (nativeWorkArea.Width - nativeWidth) / 2;
nativeTop = nativeWorkArea.Top + (nativeWorkArea.Height - nativeHeight) / 2;
break;
}

var windowInteropHandler = new WindowInteropHelper(this);

PInvoke.SetWindowPos((HWND)windowInteropHandler.Handle, HWND.Null, (int)nativeLeft, (int)nativeTop, 0, 0, SET_WINDOW_POS_FLAGS.SWP_NOSIZE);
}

private void InitializeContent(SymbolRegular symbol, SymbolRegular? overlaySymbol, Action<SymbolIcon>? symbolTransform, string text)
Expand Down
Loading