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);
}