diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index c4280b6..55f6da6 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -17,7 +17,9 @@ permissions: jobs: release: - runs-on: ${{ github.repository_owner == 'coder' && 'windows-latest-16-cores' || 'windows-latest' }} + # windows-2025 is required for an up-to-date version of OpenSSL for the + # appcast generation. + runs-on: ${{ github.repository_owner == 'coder' && 'windows-2025-16-cores' || 'windows-2025' }} outputs: version: ${{ steps.version.outputs.VERSION }} timeout-minutes: 15 @@ -166,6 +168,7 @@ jobs: APPCAST_GCS_URI: gs://releases.coder.com/coder-desktop/windows/appcast.xml APPCAST_SIGNATURE_GCS_URI: gs://releases.coder.com/coder-desktop/windows/appcast.xml.signature APPCAST_SIGNATURE_KEY_BASE64: ${{ secrets.APPCAST_SIGNATURE_KEY_BASE64 }} + GH_TOKEN: ${{ github.token }} GCLOUD_ACCESS_TOKEN: ${{ steps.gcloud_auth.outputs.access_token }} winget: diff --git a/App/Views/DirectoryPickerWindow.xaml b/App/Views/DirectoryPickerWindow.xaml index 8a107cb..ce1623b 100644 --- a/App/Views/DirectoryPickerWindow.xaml +++ b/App/Views/DirectoryPickerWindow.xaml @@ -13,7 +13,7 @@ MinWidth="400" MinHeight="600"> - + diff --git a/App/Views/DirectoryPickerWindow.xaml.cs b/App/Views/DirectoryPickerWindow.xaml.cs index 7af6db3..d2eb320 100644 --- a/App/Views/DirectoryPickerWindow.xaml.cs +++ b/App/Views/DirectoryPickerWindow.xaml.cs @@ -19,8 +19,6 @@ public DirectoryPickerWindow(DirectoryPickerViewModel viewModel) InitializeComponent(); TitleBarIcon.SetTitlebarIcon(this); - SystemBackdrop = new DesktopAcrylicBackdrop(); - viewModel.Initialize(this, DispatcherQueue); RootFrame.Content = new DirectoryPickerMainPage(viewModel); diff --git a/App/Views/FileSyncListWindow.xaml b/App/Views/FileSyncListWindow.xaml index 070efd2..991d02a 100644 --- a/App/Views/FileSyncListWindow.xaml +++ b/App/Views/FileSyncListWindow.xaml @@ -13,7 +13,7 @@ MinWidth="1000" MinHeight="300"> - + diff --git a/App/Views/FileSyncListWindow.xaml.cs b/App/Views/FileSyncListWindow.xaml.cs index ccd2452..9d8510b 100644 --- a/App/Views/FileSyncListWindow.xaml.cs +++ b/App/Views/FileSyncListWindow.xaml.cs @@ -16,8 +16,6 @@ public FileSyncListWindow(FileSyncListViewModel viewModel) InitializeComponent(); TitleBarIcon.SetTitlebarIcon(this); - SystemBackdrop = new DesktopAcrylicBackdrop(); - ViewModel.Initialize(this, DispatcherQueue); RootFrame.Content = new FileSyncListMainPage(ViewModel); diff --git a/App/Views/Pages/DirectoryPickerMainPage.xaml b/App/Views/Pages/DirectoryPickerMainPage.xaml index dd08c46..0fbbaea 100644 --- a/App/Views/Pages/DirectoryPickerMainPage.xaml +++ b/App/Views/Pages/DirectoryPickerMainPage.xaml @@ -9,8 +9,7 @@ xmlns:converters="using:Coder.Desktop.App.Converters" xmlns:toolkit="using:CommunityToolkit.WinUI.Controls" xmlns:viewmodels="using:Coder.Desktop.App.ViewModels" - mc:Ignorable="d" - Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> + mc:Ignorable="d"> + mc:Ignorable="d"> + mc:Ignorable="d"> + 4 diff --git a/App/Views/Pages/SignInTokenPage.xaml b/App/Views/Pages/SignInTokenPage.xaml index 0ca754d..7f20b69 100644 --- a/App/Views/Pages/SignInTokenPage.xaml +++ b/App/Views/Pages/SignInTokenPage.xaml @@ -6,8 +6,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - mc:Ignorable="d" - Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> + mc:Ignorable="d"> + mc:Ignorable="d"> + + + + - + diff --git a/App/Views/SettingsWindow.xaml.cs b/App/Views/SettingsWindow.xaml.cs index 7cc9661..f2a0fdb 100644 --- a/App/Views/SettingsWindow.xaml.cs +++ b/App/Views/SettingsWindow.xaml.cs @@ -16,8 +16,6 @@ public SettingsWindow(SettingsViewModel viewModel) InitializeComponent(); TitleBarIcon.SetTitlebarIcon(this); - SystemBackdrop = new DesktopAcrylicBackdrop(); - RootFrame.Content = new SettingsMainPage(ViewModel); this.CenterOnScreen(); diff --git a/App/Views/SignInWindow.xaml b/App/Views/SignInWindow.xaml index d2c1326..6d8340c 100644 --- a/App/Views/SignInWindow.xaml +++ b/App/Views/SignInWindow.xaml @@ -11,7 +11,7 @@ Title="Sign in to Coder"> - + diff --git a/App/Views/SignInWindow.xaml.cs b/App/Views/SignInWindow.xaml.cs index 2acd0a5..da68867 100644 --- a/App/Views/SignInWindow.xaml.cs +++ b/App/Views/SignInWindow.xaml.cs @@ -24,7 +24,6 @@ public SignInWindow(SignInViewModel viewModel) { InitializeComponent(); TitleBarIcon.SetTitlebarIcon(this); - SystemBackdrop = new DesktopAcrylicBackdrop(); RootFrame.SizeChanged += RootFrame_SizeChanged; _signInUrlPage = new SignInUrlPage(this, viewModel); diff --git a/App/Views/TrayWindow.xaml.cs b/App/Views/TrayWindow.xaml.cs index e505511..6131e25 100644 --- a/App/Views/TrayWindow.xaml.cs +++ b/App/Views/TrayWindow.xaml.cs @@ -9,7 +9,6 @@ using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media.Animation; using System; using System.Runtime.InteropServices; @@ -34,8 +33,6 @@ public sealed partial class TrayWindow : Window private int _lastWindowHeight; private Storyboard? _currentSb; - private NativeApi.POINT? _lastActivatePosition; - private readonly IRpcController _rpcController; private readonly ICredentialManager _credentialManager; private readonly ISyncSessionController _syncSessionController; @@ -62,7 +59,6 @@ public TrayWindow(IRpcController rpcController, ICredentialManager credentialMan InitializeComponent(); AppWindow.Hide(); - SystemBackdrop = new DesktopAcrylicBackdrop(); Activated += Window_Activated; RootFrame.SizeChanged += RootFrame_SizeChanged; @@ -100,18 +96,18 @@ public TrayWindow(IRpcController rpcController, ICredentialManager credentialMan WindowNative.GetWindowHandle(this))); SizeProxy.SizeChanged += (_, e) => { - if (_currentSb is null) return; // nothing running + if (_currentSb is null) return; // nothing running - int newHeight = (int)Math.Round( + var newHeight = (int)Math.Round( e.NewSize.Height * DisplayScale.WindowScale(this)); - int delta = newHeight - _lastWindowHeight; + var delta = newHeight - _lastWindowHeight; if (delta == 0) return; var pos = _aw.Position; var size = _aw.Size; - pos.Y -= delta; // grow upward + pos.Y -= delta; // grow upward size.Height = newHeight; _aw.MoveAndResize( @@ -227,7 +223,6 @@ private void OnStoryboardCompleted(object? sender, object e) private void MoveResizeAndActivate() { - SaveCursorPos(); var size = CalculateWindowSize(RootFrame.GetContentSize().Height); var pos = CalculateWindowPosition(size); var rect = new RectInt32(pos.X, pos.Y, size.Width, size.Height); @@ -236,18 +231,6 @@ private void MoveResizeAndActivate() ForegroundWindow.MakeForeground(this); } - private void SaveCursorPos() - { - var res = NativeApi.GetCursorPos(out var cursorPosition); - if (res) - _lastActivatePosition = cursorPosition; - else - // When the cursor position is null, we will spawn the window in - // the bottom right corner of the primary display. - // TODO: log(?) an error when this happens - _lastActivatePosition = null; - } - private SizeInt32 CalculateWindowSize(double height) { if (height <= 0) height = 100; // will be resolved next frame typically @@ -259,41 +242,44 @@ private SizeInt32 CalculateWindowSize(double height) return new SizeInt32(newWidth, newHeight); } - private PointInt32 CalculateWindowPosition(SizeInt32 size) + private PointInt32 CalculateWindowPosition(SizeInt32 panelSize) { - var width = size.Width; - var height = size.Height; - - var cursorPosition = _lastActivatePosition; - if (cursorPosition is null) + var area = DisplayArea.GetFromWindowId(AppWindow.Id, DisplayAreaFallback.Primary); + // whole monitor + var bounds = area.OuterBounds; + // monitor minus taskbar + var workArea = area.WorkArea; + + // get taskbar details - position, gap (size), auto-hide + var tb = GetTaskbarInfo(area); + + // safe edges where tray window can touch the screen + var safeRight = workArea.X + workArea.Width; + var safeBottom = workArea.Y + workArea.Height; + + // if the taskbar is auto-hidden at the bottom, stay clear of its reveal band + if (tb.Position == TaskbarPosition.Bottom && tb.AutoHide) + safeBottom -= tb.Gap; // shift everything up by its thickness + + // pick corner & position the panel + int x, y; + switch (tb.Position) { - var primaryWorkArea = DisplayArea.Primary.WorkArea; - return new PointInt32( - primaryWorkArea.Width - width, - primaryWorkArea.Height - height - ); - } - - // Spawn the window to the top right of the cursor. - var x = cursorPosition.Value.X + 10; - var y = cursorPosition.Value.Y - 10 - height; - - var workArea = DisplayArea.GetFromPoint( - new PointInt32(cursorPosition.Value.X, cursorPosition.Value.Y), - DisplayAreaFallback.Primary - ).WorkArea; - - // Adjust if the window goes off the right edge of the display. - if (x + width > workArea.X + workArea.Width) x = workArea.X + workArea.Width - width; - - // Adjust if the window goes off the bottom edge of the display. - if (y + height > workArea.Y + workArea.Height) y = workArea.Y + workArea.Height - height; + case TaskbarPosition.Left: // for Left we will stick to the left-bottom corner + x = bounds.X + tb.Gap; // just right of the bar + y = safeBottom - panelSize.Height; + break; - // Adjust if the window goes off the left edge of the display (somehow). - if (x < workArea.X) x = workArea.X; + case TaskbarPosition.Top: // for Top we will stick to the top-right corner + x = safeRight - panelSize.Width; + y = bounds.Y + tb.Gap; // just below the bar + break; - // Adjust if the window goes off the top edge of the display (somehow). - if (y < workArea.Y) y = workArea.Y; + default: // Bottom or Right bar we will stick to the bottom-right corner + x = safeRight - panelSize.Width; + y = safeBottom - panelSize.Height; + break; + } return new PointInt32(x, y); } @@ -344,4 +330,71 @@ public struct POINT public int Y; } } + + internal enum TaskbarPosition { Left, Top, Right, Bottom } + + internal readonly record struct TaskbarInfo(TaskbarPosition Position, int Gap, bool AutoHide); + + // ----------------------------------------------------------------------------- + // Taskbar helpers – ABM_GETTASKBARPOS / ABM_GETSTATE via SHAppBarMessage + // ----------------------------------------------------------------------------- + private static TaskbarInfo GetTaskbarInfo(DisplayArea area) + { + var data = new APPBARDATA + { + cbSize = (uint)Marshal.SizeOf() + }; + + // Locate the taskbar. + if (SHAppBarMessage(ABM_GETTASKBARPOS, ref data) == 0) + return new TaskbarInfo(TaskbarPosition.Bottom, 0, false); // failsafe + + var autoHide = (SHAppBarMessage(ABM_GETSTATE, ref data) & ABS_AUTOHIDE) != 0; + + // Use uEdge instead of guessing from the RECT. + var pos = data.uEdge switch + { + ABE_LEFT => TaskbarPosition.Left, + ABE_TOP => TaskbarPosition.Top, + ABE_RIGHT => TaskbarPosition.Right, + _ => TaskbarPosition.Bottom, // ABE_BOTTOM or anything unexpected + }; + + // Thickness (gap) = shorter side of the rect. + var gap = (pos == TaskbarPosition.Left || pos == TaskbarPosition.Right) + ? data.rc.right - data.rc.left // width + : data.rc.bottom - data.rc.top; // height + + return new TaskbarInfo(pos, gap, autoHide); + } + + // ------------- P/Invoke plumbing ------------- + private const uint ABM_GETTASKBARPOS = 0x0005; + private const uint ABM_GETSTATE = 0x0004; + private const int ABS_AUTOHIDE = 0x0001; + + private const int ABE_LEFT = 0; // values returned in APPBARDATA.uEdge + private const int ABE_TOP = 1; + private const int ABE_RIGHT = 2; + private const int ABE_BOTTOM = 3; + + [StructLayout(LayoutKind.Sequential)] + private struct APPBARDATA + { + public uint cbSize; + public IntPtr hWnd; + public uint uCallbackMessage; + public uint uEdge; // contains ABE_* value + public RECT rc; + public int lParam; + } + + [StructLayout(LayoutKind.Sequential)] + private struct RECT + { + public int left, top, right, bottom; + } + + [DllImport("shell32.dll", CharSet = CharSet.Auto)] + private static extern uint SHAppBarMessage(uint dwMessage, ref APPBARDATA pData); }