Skip to content
Draft
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
1 change: 1 addition & 0 deletions PowerToys.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@
<Project Path="src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Microsoft.CmdPal.Ext.Shell.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
<Build Solution="Release|x64" Project="false" />
</Project>
<Project Path="src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.System/Microsoft.CmdPal.Ext.System.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
Expand Down
38 changes: 38 additions & 0 deletions publish-settings-dependencies.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Publish dependencies for Settings.UI with RuntimeIdentifier=win-x64

. "$PSScriptRoot\tools\build\build-common.ps1"

# Initialize Visual Studio dev environment
if (-not (Ensure-VsDevEnvironment)) {
Write-Error "Failed to initialize VS dev environment"
exit 1
}

$Platform = "x64"
$Configuration = "Release"
$RuntimeId = "win-x64"

$projects = @(
"src\common\Common.Search\Common.Search.csproj",
"src\common\LanguageModelProvider\LanguageModelProvider.csproj",
"src\common\AllExperiments\AllExperiments.csproj",
"src\common\Common.UI\Common.UI.csproj",
"src\common\ManagedCommon\ManagedCommon.csproj",
"src\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj"
)

foreach ($project in $projects) {
$projectName = Split-Path $project -Leaf
Write-Host "Publishing $projectName..." -ForegroundColor Cyan

$args = "-t:Publish -p:Configuration=$Configuration -p:Platform=$Platform -p:RuntimeIdentifier=$RuntimeId"
RunMSBuild $project $args $Platform $Configuration

if ($LASTEXITCODE -ne 0) {
Write-Error "Failed to publish $projectName"
exit $LASTEXITCODE
}
}

Write-Host "`nAll dependencies published successfully!" -ForegroundColor Green
Write-Host "You can now publish Settings.UI" -ForegroundColor Green
5 changes: 4 additions & 1 deletion src/Common.Dotnet.AotCompatibility.props
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

<!-- Suppress DynamicallyAccessedMemberTypes.PublicParameterlessConstructor in fallback code path of Windows SDK projection -->
<!-- Suppress CA1416 for Windows-specific APIs that are used in PowerToys which only runs on Windows 10.0.19041.0+ -->
<WarningsNotAsErrors>IL2081;CsWinRT1028;CA1416;$(WarningsNotAsErrors)</WarningsNotAsErrors>
<!-- Suppress IL2026/IL3050 for JSON serialization in specific scenarios (backup/restore, CLI commands) -->
<!-- Suppress IL2067/IL2070/IL2072/IL2075/IL2087/IL2098 for reflection in CLI/DSC command utilities -->
<!-- Suppress IL3000/IL3002 for Assembly.Location and Marshal.GetHINSTANCE in single-file/AOT scenarios -->
<WarningsNotAsErrors>IL2026;IL2067;IL2070;IL2072;IL2075;IL2081;IL2087;IL2098;IL3000;IL3002;IL3050;CsWinRT1028;CA1416;$(WarningsNotAsErrors)</WarningsNotAsErrors>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// 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 System.Collections.Generic;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using MouseWithoutBorders.Class;
using Logger = MouseWithoutBorders.Core.Logger;

#pragma warning disable SA1649 // File name should match first type name

namespace MouseWithoutBorders.Class;

/// <summary>
/// Command types for IPC protocol.
/// Must match client-side enum in Settings.UI\Helpers\MouseWithoutBordersIpcClient.cs
/// </summary>
internal enum IpcCommandType : byte
{
Shutdown = 1,
Reconnect = 2,
GenerateNewKey = 3,
ConnectToMachine = 4,
RequestMachineSocketState = 5,
}

/// <summary>
/// AOT-compatible IPC server for MouseWithoutBorders Settings communication.
/// Replaces StreamJsonRpc with manual NamedPipe protocol.
/// </summary>
internal sealed class MouseWithoutBordersIpcServer
{
private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions { WriteIndented = false };

private readonly ISettingsSyncHandler _handler;

public MouseWithoutBordersIpcServer(ISettingsSyncHandler handler)
{
_handler = handler ?? throw new ArgumentNullException(nameof(handler));
}

/// <summary>
/// Handles a single client connection
/// </summary>
public async Task HandleClientAsync(Stream stream, CancellationToken cancellationToken)
{
using var reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);
using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true);

try
{
while (!cancellationToken.IsCancellationRequested && stream.CanRead)
{
// Read command type (1 byte)
var commandByte = reader.ReadByte();
var command = (IpcCommandType)commandByte;

switch (command)
{
case IpcCommandType.Shutdown:
_handler.Shutdown();
break;

case IpcCommandType.Reconnect:
_handler.Reconnect();
break;

case IpcCommandType.GenerateNewKey:
_handler.GenerateNewKey();
break;

case IpcCommandType.ConnectToMachine:
{
var machineName = ReadString(reader);
var securityKey = ReadString(reader);
_handler.ConnectToMachine(machineName, securityKey);
}

break;

case IpcCommandType.RequestMachineSocketState:
{
var states = await _handler.RequestMachineSocketStateAsync();
var json = JsonSerializer.Serialize(states, JsonOptions);
WriteString(writer, json);
await stream.FlushAsync(cancellationToken);
}

break;

default:
Logger.Log($"Unknown IPC command: {commandByte}");
return; // Invalid command, close connection
}
}
}
catch (EndOfStreamException)
{
// Client disconnected, normal termination
}
catch (IOException)
{
// Pipe broken, normal termination
}
catch (Exception ex)
{
Logger.Log($"IPC error: {ex}");
}
}

/// <summary>
/// Reads a length-prefixed UTF-8 string
/// </summary>
private static string ReadString(BinaryReader reader)
{
var length = reader.ReadInt32();
if (length <= 0 || length > 1024 * 1024)
{
return string.Empty;
}

var bytes = reader.ReadBytes(length);
return Encoding.UTF8.GetString(bytes);
}

/// <summary>
/// Writes a length-prefixed UTF-8 string
/// </summary>
private static void WriteString(BinaryWriter writer, string value)
{
var bytes = Encoding.UTF8.GetBytes(value);
writer.Write(bytes.Length);
writer.Write(bytes);
}
}

/// <summary>
/// Interface for handling IPC commands.
/// Implemented by SettingsSyncHelper in Program.cs
/// </summary>
internal interface ISettingsSyncHandler
{
void Shutdown();

void Reconnect();

void GenerateNewKey();

void ConnectToMachine(string machineName, string securityKey);

Task<MachineSocketState[]> RequestMachineSocketStateAsync();
}

/// <summary>
/// Machine socket state for serialization.
/// Uses SocketStatus from SocketStuff.cs in MouseWithoutBorders.Class namespace.
/// </summary>
public struct MachineSocketState
{
public string Name { get; set; }

public MouseWithoutBorders.Class.SocketStatus Status { get; set; }
}
82 changes: 81 additions & 1 deletion src/modules/MouseWithoutBorders/App/Class/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Security.AccessControl;
using System.Security.Authentication.ExtendedProtection;
using System.Security.Principal;
using System.ServiceModel.Channels;
Expand Down Expand Up @@ -276,7 +277,7 @@
Task<MachineSocketState[]> RequestMachineSocketStateAsync();
}

private sealed class SettingsSyncHelper : ISettingsSyncHelper
private sealed class SettingsSyncHelper : ISettingsSyncHelper, ISettingsSyncHandler
{
public Task<ISettingsSyncHelper.MachineSocketState[]> RequestMachineSocketStateAsync()
{
Expand All @@ -299,6 +300,28 @@
return Task.FromResult(machineStates.Select((state) => new ISettingsSyncHelper.MachineSocketState { Name = state.Key, Status = state.Value }).ToArray());
}

// ISettingsSyncHandler implementation (AOT-compatible)
Task<MachineSocketState[]> ISettingsSyncHandler.RequestMachineSocketStateAsync()
{
var machineStates = new Dictionary<string, SocketStatus>();
if (Common.Sk == null || Common.Sk.TcpSockets == null)
{
return Task.FromResult(Array.Empty<MachineSocketState>());
}

foreach (var client in Common.Sk.TcpSockets
.Where(t => t != null && t.IsClient && !string.IsNullOrEmpty(t.MachineName)))
{
var exists = machineStates.TryGetValue(client.MachineName, out var existingStatus);
if (!exists || existingStatus == SocketStatus.NA)
{
machineStates[client.MachineName] = client.Status;
}
}

return Task.FromResult(machineStates.Select((state) => new MachineSocketState { Name = state.Key, Status = state.Value }).ToArray());
}

public void ConnectToMachine(string pcName, string securityKey)
{
Setting.Values.PauseInstantSaving = true;
Expand Down Expand Up @@ -379,7 +402,64 @@
var serverTaskCancellationSource = new CancellationTokenSource();
CancellationToken cancellationToken = serverTaskCancellationSource.Token;

// Use AOT-compatible IPC server if available, otherwise use StreamJsonRpc

Check failure

Code scanning / check-spelling

Forbidden Pattern Error

, otherwise matches a line_forbidden.patterns entry: ", [Oo]therwise\\b". (forbidden-pattern)
#if BUILD_INFO_PUBLISH_AOT || true // Enable for all builds
StartAotCompatibleIpcServer("MouseWithoutBorders/SettingsSync", cancellationToken);
#else
IpcChannel<SettingsSyncHelper>.StartIpcServer("MouseWithoutBorders/SettingsSync", cancellationToken);
#endif
}

private static void StartAotCompatibleIpcServer(string pipeName, CancellationToken cancellationToken)
{
var handler = new SettingsSyncHelper();
var server = new MouseWithoutBordersIpcServer(handler);

_ = Task.Factory.StartNew(
async () =>
{
try
{
while (!cancellationToken.IsCancellationRequested)
{
using (var serverPipe = NamedPipeServerStreamAcl.Create(
pipeName,
PipeDirection.InOut,
NamedPipeServerStream.MaxAllowedServerInstances,
PipeTransmissionMode.Byte,
PipeOptions.Asynchronous,
0,
0,
CreatePipeSecurity()))
{
await serverPipe.WaitForConnectionAsync(cancellationToken);
await server.HandleClientAsync(serverPipe, cancellationToken);
}
}
}
catch (OperationCanceledException)
{
// Normal shutdown
}
catch (Exception e)
{
Logger.Log(e);
}
},
cancellationToken,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
}

private static PipeSecurity CreatePipeSecurity()
{
var securityIdentifier = new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null);
var pipeSecurity = new PipeSecurity();
pipeSecurity.AddAccessRule(new PipeAccessRule(
securityIdentifier,
PipeAccessRights.ReadWrite | PipeAccessRights.CreateNewInstance,
AccessControlType.Allow));
return pipeSecurity;
}

internal static void StartInputCallbackThread()
Expand Down
6 changes: 5 additions & 1 deletion src/modules/MouseWithoutBorders/App/Class/SocketStuff.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,11 @@

namespace MouseWithoutBorders.Class
{
internal enum SocketStatus : int
/// <summary>
/// Socket status enumeration - made public for IPC serialization.
/// Must match Settings.UI.Library\MouseWithoutBordersIpcModels.cs
/// </summary>
public enum SocketStatus : int
{
NA = 0,
Resolving = 1,
Expand Down
1 change: 1 addition & 0 deletions src/modules/MouseWithoutBorders/App/Core/Common.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
using MouseWithoutBorders.Exceptions;

using Clipboard = MouseWithoutBorders.Core.Clipboard;
using SocketStatus = MouseWithoutBorders.Class.SocketStatus;
using Thread = MouseWithoutBorders.Core.Thread;

// Log is enough
Expand Down
5 changes: 5 additions & 0 deletions src/settings-ui/QuickAccess.UI/PowerToys.QuickAccess.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\Common.SelfContained.props" />
<Import Project="..\..\Common.Dotnet.AotCompatibility.props" />

<PropertyGroup Condition="'$(EnableSettingsAOT)' == 'true'">
<PublishAot>true</PublishAot>
</PropertyGroup>

<PropertyGroup>
<OutputType>WinExe</OutputType>
Expand Down
3 changes: 2 additions & 1 deletion src/settings-ui/Settings.UI.Library/GenericProperty`1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
// 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.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

namespace Microsoft.PowerToys.Settings.UI.Library
{
public class GenericProperty<T> : ICmdLineRepresentable
public class GenericProperty<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T> : ICmdLineRepresentable
{
[JsonPropertyName("value")]
public T Value { get; set; }
Expand Down
Loading
Loading