Listen to WM_SETTINGSCHANGED

Add Infobar reload button
This commit is contained in:
Stefan Markovic
2023-09-22 13:14:13 +02:00
parent 548e79d76b
commit 00ab5ca2e0
13 changed files with 222 additions and 115 deletions

View File

@@ -48,7 +48,7 @@ namespace EnvironmentVariables
services.AddSingleton<IElevationHelper, ElevationHelper>();
services.AddSingleton<IEnvironmentVariablesService, EnvironmentVariablesService>();
services.AddTransient<MainViewModel>();
services.AddSingleton<MainViewModel>();
}).Build();
UnhandledException += App_UnhandledException;

View File

@@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using EnvironmentVariables.Models;
using Microsoft.UI.Xaml.Data;
namespace EnvironmentVariables.Converters;
public class EnvironmentStateToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var type = (EnvironmentState)value;
return type switch
{
EnvironmentState.Unchanged => false,
_ => true,
};
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}

View File

@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using EnvironmentVariables.Helpers;
using EnvironmentVariables.Models;
using Microsoft.UI.Xaml.Data;
namespace EnvironmentVariables.Converters;
public class EnvironmentStateToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
var type = (EnvironmentState)value;
return type switch
{
EnvironmentState.Unchanged => string.Empty,
EnvironmentState.ChangedOnStartup => resourceLoader.GetString("StateNotUpToDateOnStartupMsg"),
EnvironmentState.EnvironmentMessageRecieved => resourceLoader.GetString("StateNotUpToDateEnvironmentMessageRecievedMsg"),
_ => throw new NotImplementedException(),
};
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}

View File

@@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using EnvironmentVariables.Models;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
namespace EnvironmentVariables.Converters;
public class EnvironmentStateToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var type = (EnvironmentState)value;
return type switch
{
EnvironmentState.Unchanged => Visibility.Collapsed,
_ => Visibility.Visible,
};
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}

View File

@@ -5,6 +5,9 @@
using System;
using System.Runtime.InteropServices;
using EnvironmentVariables.Helpers;
using EnvironmentVariables.Helpers.Win32;
using EnvironmentVariables.ViewModels;
using Microsoft.UI.Dispatching;
using WinUIEx;
namespace EnvironmentVariables
@@ -30,40 +33,36 @@ namespace EnvironmentVariables
RegisterWindow();
}
private static WinProc newWndProc;
private static readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
private static NativeMethods.WinProc newWndProc;
private static IntPtr oldWndProc = IntPtr.Zero;
private delegate IntPtr WinProc(IntPtr hWnd, WindowMessage msg, IntPtr wParam, IntPtr lParam);
[DllImport("User32.dll")]
internal static extern int GetDpiForWindow(IntPtr hwnd);
[DllImport("user32.dll", EntryPoint = "SetWindowLong")]
private static extern int SetWindowLong32(IntPtr hWnd, WindowLongIndexFlags nIndex, WinProc newProc);
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
private static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, WindowLongIndexFlags nIndex, WinProc newProc);
[DllImport("user32.dll")]
private static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, WindowMessage msg, IntPtr wParam, IntPtr lParam);
private void RegisterWindow()
{
newWndProc = new WinProc(WndProc);
newWndProc = new NativeMethods.WinProc(WndProc);
var handle = this.GetWindowHandle();
oldWndProc = SetWindowLongPtr(handle, WindowLongIndexFlags.GWL_WNDPROC, newWndProc);
oldWndProc = NativeMethods.SetWindowLongPtr(handle, NativeMethods.WindowLongIndexFlags.GWL_WNDPROC, newWndProc);
}
private static IntPtr WndProc(IntPtr hWnd, WindowMessage msg, IntPtr wParam, IntPtr lParam)
private static IntPtr WndProc(IntPtr hWnd, NativeMethods.WindowMessage msg, IntPtr wParam, IntPtr lParam)
{
switch (msg)
{
case WindowMessage.WM_SETTINGSCHANGED:
case NativeMethods.WindowMessage.WM_SETTINGSCHANGED:
{
var asd = Marshal.PtrToStringUTF8(lParam);
_ = asd.Substring(0, asd.Length - 1);
var lParamStr = Marshal.PtrToStringUTF8(lParam);
if (lParamStr == "Environment")
{
// Do not react on self - not nice, re-check this
if (wParam != (IntPtr)0x12345)
{
var viewModel = App.GetService<MainViewModel>();
viewModel.IsStateModified = Models.EnvironmentState.EnvironmentMessageRecieved;
}
}
break;
}
@@ -71,30 +70,7 @@ namespace EnvironmentVariables
break;
}
return CallWindowProc(oldWndProc, hWnd, msg, wParam, lParam);
}
[Flags]
private enum WindowLongIndexFlags : int
{
GWL_WNDPROC = -4,
}
private static IntPtr SetWindowLongPtr(IntPtr hWnd, WindowLongIndexFlags nIndex, WinProc newProc)
{
if (IntPtr.Size == 8)
{
return SetWindowLongPtr64(hWnd, nIndex, newProc);
}
else
{
return new IntPtr(SetWindowLong32(hWnd, nIndex, newProc));
}
}
private enum WindowMessage : int
{
WM_SETTINGSCHANGED = 0x001A,
return NativeMethods.CallWindowProc(oldWndProc, hWnd, msg, wParam, lParam);
}
}
}

View File

@@ -41,6 +41,9 @@
</DataTemplate>
<converters:VariableTypeToGlyphConverter x:Key="VariableTypeToGlyphConverter" />
<converters:EnvironmentStateToBoolConverter x:Key="EnvironmentStateToBoolConverter" />
<converters:EnvironmentStateToStringConverter x:Key="EnvironmentStateToStringConverter" />
<converters:EnvironmentStateToVisibilityConverter x:Key="EnvironmentStateToVisibilityConverter" />
<converters1:BoolToVisibilityConverter
x:Key="BoolToInvertedVisibilityConverter"
@@ -68,13 +71,6 @@
<RowDefinition Height="Auto" />
<!-- content -->
</Grid.RowDefinitions>
<Grid Grid.Row="1">
<InfoBar
x:Uid="StateNotUpToDateInfoBar"
IsOpen="{x:Bind ViewModel.IsStateModified, Mode=TwoWay}"
Severity="Warning"
Visibility="{x:Bind ViewModel.IsStateModified, Mode=TwoWay, Converter={StaticResource BoolToVisibilityConverter}}" />
</Grid>
<!-- buttons -->
<StackPanel Orientation="Horizontal">
@@ -91,8 +87,26 @@
<KeyboardAccelerator Key="N" Modifiers="Control" />
</Button.KeyboardAccelerators>
</Button>
<ProgressRing IsActive="{x:Bind ViewModel.ApplyingChanges, Mode=TwoWay}" />
</StackPanel>
<Grid Grid.Row="1">
<InfoBar
x:Name="StateNotUpToDateInfoBar"
x:Uid="StateNotUpToDateInfoBar"
IsOpen="{x:Bind ViewModel.IsStateModified, Mode=OneWay, Converter={StaticResource EnvironmentStateToBoolConverter}}"
Message="{x:Bind ViewModel.IsStateModified, Mode=OneWay, Converter={StaticResource EnvironmentStateToStringConverter}}"
Severity="Warning"
Visibility="{x:Bind ViewModel.IsStateModified, Mode=OneWay, Converter={StaticResource EnvironmentStateToVisibilityConverter}}">
<InfoBar.ActionButton>
<Button
Click="ReloadButton_Click"
Content="{ui:FontIcon Glyph=&#xe72c;}"
Style="{StaticResource SubtleButtonStyle}"
Visibility="{x:Bind ViewModel.IsInfoBarButtonVisible, Mode=OneWay}" />
</InfoBar.ActionButton>
</InfoBar>
</Grid>
<Grid
Grid.Row="2"
Margin="0,24,0,0"

View File

@@ -161,5 +161,11 @@ namespace EnvironmentVariables.Views
}
}
}
private void ReloadButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
ViewModel.LoadEnvironmentVariables();
ViewModel.IsStateModified = EnvironmentState.Unchanged;
}
}
}

View File

@@ -4,10 +4,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security;
using EnvironmentVariables.Helpers.Win32;
using EnvironmentVariables.Models;
using Microsoft.Win32;
@@ -77,14 +73,14 @@ namespace EnvironmentVariables.Helpers
}
}
private static void NotifyEnvironmentChange()
internal static void NotifyEnvironmentChange()
{
unsafe
{
// send a WM_SETTINGCHANGE message to all windows
fixed (char* lParam = "Environment")
{
_ = NativeMethods.SendNotifyMessage(new IntPtr(NativeMethods.HWND_BROADCAST), NativeMethods.WM_SETTINGCHANGE, IntPtr.Zero, (IntPtr)lParam);
_ = NativeMethods.SendNotifyMessage(new IntPtr(NativeMethods.HWND_BROADCAST), NativeMethods.WindowMessage.WM_SETTINGSCHANGED, (IntPtr)0x12345, (IntPtr)lParam);
}
}
}
@@ -108,6 +104,21 @@ namespace EnvironmentVariables.Helpers
}
}
internal static bool SetVariableWithoutNotify(Variable variable)
{
bool fromMachine = variable.ParentType switch
{
VariablesSetType.Profile => false,
VariablesSetType.User => false,
VariablesSetType.System => true,
_ => throw new NotImplementedException(),
};
SetEnvironmentVariableFromRegistryWithoutNotify(variable.Name, variable.Values, fromMachine);
return true;
}
internal static bool SetVariable(Variable variable)
{
bool fromMachine = variable.ParentType switch
@@ -124,9 +135,9 @@ namespace EnvironmentVariables.Helpers
return true;
}
internal static bool SetVariables(List<Variable> variables, VariablesSetType type)
internal static bool UnsetVariableWithoutNotify(Variable variable)
{
bool fromMachine = type switch
bool fromMachine = variable.ParentType switch
{
VariablesSetType.Profile => false,
VariablesSetType.User => false,
@@ -134,12 +145,7 @@ namespace EnvironmentVariables.Helpers
_ => throw new NotImplementedException(),
};
foreach (Variable variable in variables)
{
SetEnvironmentVariableFromRegistryWithoutNotify(variable.Name, variable.Values, fromMachine);
}
NotifyEnvironmentChange();
SetEnvironmentVariableFromRegistryWithoutNotify(variable.Name, null, fromMachine);
return true;
}
@@ -159,25 +165,5 @@ namespace EnvironmentVariables.Helpers
return true;
}
internal static bool UnsetVariables(List<Variable> variables, VariablesSetType type)
{
bool fromMachine = type switch
{
VariablesSetType.Profile => false,
VariablesSetType.User => false,
VariablesSetType.System => true,
_ => throw new NotImplementedException(),
};
foreach (Variable variable in variables)
{
SetEnvironmentVariableFromRegistryWithoutNotify(variable.Name, null, fromMachine);
}
NotifyEnvironmentChange();
return true;
}
}
}

View File

@@ -11,9 +11,44 @@ namespace EnvironmentVariables.Helpers.Win32
{
internal const int HWND_BROADCAST = 0xffff;
internal const int WM_SETTINGCHANGE = 0x001A;
internal delegate IntPtr WinProc(IntPtr hWnd, WindowMessage msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern int SendNotifyMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
internal static extern int SendNotifyMessage(IntPtr hWnd, WindowMessage msg, IntPtr wParam, IntPtr lParam);
[DllImport("User32.dll")]
internal static extern int GetDpiForWindow(IntPtr hwnd);
[DllImport("user32.dll", EntryPoint = "SetWindowLong")]
internal static extern int SetWindowLong32(IntPtr hWnd, WindowLongIndexFlags nIndex, WinProc newProc);
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
internal static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, WindowLongIndexFlags nIndex, WinProc newProc);
[DllImport("user32.dll")]
internal static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, WindowMessage msg, IntPtr wParam, IntPtr lParam);
[Flags]
internal enum WindowLongIndexFlags : int
{
GWL_WNDPROC = -4,
}
internal enum WindowMessage : int
{
WM_SETTINGSCHANGED = 0x001A,
}
internal static IntPtr SetWindowLongPtr(IntPtr hWnd, WindowLongIndexFlags nIndex, WinProc newProc)
{
if (IntPtr.Size == 8)
{
return SetWindowLongPtr64(hWnd, nIndex, newProc);
}
else
{
return new IntPtr(SetWindowLong32(hWnd, nIndex, newProc));
}
}
}
}

View File

@@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace EnvironmentVariables.Models
{
public enum EnvironmentState
{
Unchanged = 0,
ChangedOnStartup,
EnvironmentMessageRecieved,
}
}

View File

@@ -44,19 +44,19 @@ namespace EnvironmentVariables.Models
variableToOverride.Name = EnvironmentVariablesHelper.GetBackupVariableName(variableToOverride, this.Name);
// Backup the variable
if (!EnvironmentVariablesHelper.SetVariable(variableToOverride))
if (!EnvironmentVariablesHelper.SetVariableWithoutNotify(variableToOverride))
{
Logger.LogError("Failed to set backup variable.");
}
}
if (!EnvironmentVariablesHelper.SetVariable(variable))
if (!EnvironmentVariablesHelper.SetVariableWithoutNotify(variable))
{
Logger.LogError("Failed to set profile variable.");
}
}
// viewModel.ApplyingChanges = false;
EnvironmentVariablesHelper.NotifyEnvironmentChange();
});
}
@@ -64,12 +64,10 @@ namespace EnvironmentVariables.Models
{
return Task.Run(() =>
{
var viewModel = App.GetService<MainViewModel>();
viewModel.ApplyingChanges = true;
foreach (var variable in Variables)
{
// Unset the variable
if (!EnvironmentVariablesHelper.UnsetVariable(variable))
if (!EnvironmentVariablesHelper.UnsetVariableWithoutNotify(variable))
{
Logger.LogError("Failed to unset variable.");
}
@@ -84,19 +82,19 @@ namespace EnvironmentVariables.Models
{
var variableToRestore = new Variable(originalName, backupVariable.Values, backupVariable.ParentType);
if (!EnvironmentVariablesHelper.UnsetVariable(backupVariable))
if (!EnvironmentVariablesHelper.UnsetVariableWithoutNotify(backupVariable))
{
Logger.LogError("Failed to unset backup variable.");
}
if (!EnvironmentVariablesHelper.SetVariable(variableToRestore))
if (!EnvironmentVariablesHelper.SetVariableWithoutNotify(variableToRestore))
{
Logger.LogError("Failed to restore backup variable.");
}
}
}
viewModel.ApplyingChanges = false;
EnvironmentVariablesHelper.NotifyEnvironmentChange();
});
}

View File

@@ -221,10 +221,13 @@
<data name="StateNotUpToDateInfoBar.Title" xml:space="preserve">
<value>Changes were made outside of this app.</value>
</data>
<data name="StateNotUpToDateInfoBar.Message" xml:space="preserve">
<data name="StateNotUpToDateOnStartupMsg" xml:space="preserve">
<value>Variables included in applied profile have been modified. Review the latest changes before applying the profile again.</value>
</data>
<data name="CancelBtn" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="StateNotUpToDateEnvironmentMessageRecievedMsg" xml:space="preserve">
<value>Variables have been modified. Reload to get the latest changes.</value>
</data>
</root>

View File

@@ -8,14 +8,12 @@ using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using System.Xml.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using EnvironmentVariables.Helpers;
using EnvironmentVariables.Models;
using ManagedCommon;
using Microsoft.UI.Dispatching;
using Windows.Foundation.Collections;
namespace EnvironmentVariables.ViewModels
{
@@ -41,10 +39,10 @@ namespace EnvironmentVariables.ViewModels
private bool _isElevated;
[ObservableProperty]
private bool _isStateModified;
[NotifyPropertyChangedFor(nameof(IsInfoBarButtonVisible))]
private EnvironmentState _isStateModified;
[ObservableProperty]
private bool _applyingChanges;
public bool IsInfoBarButtonVisible => IsStateModified == EnvironmentState.EnvironmentMessageRecieved;
public ProfileVariablesSet AppliedProfile { get; set; }
@@ -105,11 +103,11 @@ namespace EnvironmentVariables.ViewModels
if (appliedProfile.IsCorrectlyApplied())
{
AppliedProfile = appliedProfile;
IsStateModified = false;
IsStateModified = EnvironmentState.Unchanged;
}
else
{
IsStateModified = true;
IsStateModified = EnvironmentState.ChangedOnStartup;
appliedProfile.IsEnabled = false;
}
}
@@ -144,13 +142,11 @@ namespace EnvironmentVariables.ViewModels
bool changed = original.Name != edited.Name || original.Values != edited.Values;
if (changed)
{
ApplyingChanges = true;
var task = original.Update(edited, propagateChange);
task.ContinueWith(x =>
{
_dispatcherQueue.TryEnqueue(() =>
{
ApplyingChanges = false;
PopulateAppliedVariables();
});
});
@@ -211,13 +207,11 @@ namespace EnvironmentVariables.ViewModels
private void SetAppliedProfile(ProfileVariablesSet profile)
{
ApplyingChanges = true;
var task = profile.Apply();
task.ContinueWith((a) =>
{
_dispatcherQueue.TryEnqueue(() =>
{
ApplyingChanges = false;
PopulateAppliedVariables();
});
});
@@ -230,13 +224,11 @@ namespace EnvironmentVariables.ViewModels
{
var appliedProfile = AppliedProfile;
appliedProfile.PropertyChanged -= Profile_PropertyChanged;
ApplyingChanges = true;
var task = AppliedProfile.UnApply();
task.ContinueWith((a) =>
{
_dispatcherQueue.TryEnqueue(() =>
{
ApplyingChanges = false;
PopulateAppliedVariables();
});
});
@@ -286,7 +278,6 @@ namespace EnvironmentVariables.ViewModels
if (propagateChange)
{
ApplyingChanges = true;
var task = Task.Run(() =>
{
EnvironmentVariablesHelper.UnsetVariable(variable);
@@ -295,7 +286,6 @@ namespace EnvironmentVariables.ViewModels
{
_dispatcherQueue.TryEnqueue(() =>
{
ApplyingChanges = false;
PopulateAppliedVariables();
});
});