Skip to content
Merged
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
97 changes: 80 additions & 17 deletions src/OpenClaw.Tray.WinUI/Dialogs/QuickSendDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@
private readonly TextBox _errorDetailsTextBox;
private readonly Button _sendButton;
private bool _isSending;
private bool _isClosed;
private bool _focusRetryRunning;

private const string TitleIcon = "🦞";
private const double WindowControlsReservedWidth = 140;
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);

Expand All @@ -42,6 +46,7 @@
uint uFlags);

private static readonly IntPtr HWND_TOPMOST = new(-1);
private const int TitleBarHeight = 48;
private const int SW_SHOWNORMAL = 1;
private const uint SWP_NOMOVE = 0x0002;
private const uint SWP_NOSIZE = 0x0001;
Expand All @@ -53,7 +58,8 @@

// Window setup
Title = LocalizationHelper.GetString("WindowTitle_QuickSend");
this.SetWindowSize(420, 260);
ExtendsContentIntoTitleBar = true;
this.SetWindowSize(420, 260 + TitleBarHeight);
this.CenterOnScreen();
this.SetIcon(IconHelper.GetStatusIconPath(ConnectionStatus.Connected));

Expand All @@ -62,9 +68,9 @@
BackdropHelper.TrySetAcrylicBackdrop((Microsoft.UI.Xaml.Window)this);

// Hotkey-launched windows can fail to foreground on Windows 10 due to
// foreground activation restrictions. Ensure the window is topmost.
// foreground activation restrictions. Keep the existing topmost promotion.
this.IsAlwaysOnTop = true;

// Build UI programmatically (simple dialog)
var root = new Grid
{
Expand Down Expand Up @@ -130,20 +136,57 @@
Grid.SetRow(buttonPanel, 3);
root.Children.Add(buttonPanel);

Content = new Border
var body = new Border
{
Padding = new Thickness(24),
Child = root
};

// Focus the text box when shown
var outerGrid = new Grid();
outerGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(TitleBarHeight) });
outerGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });

var titleBar = new Grid { Padding = new Thickness(16, 0, WindowControlsReservedWidth, 0) };
var titleStack = new StackPanel { Orientation = Orientation.Horizontal };
titleStack.Children.Add(new TextBlock
{
Text = TitleIcon,
FontSize = 20,
VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(0, 0, 10, 0)
});
titleStack.Children.Add(new TextBlock
{
Text = LocalizationHelper.GetString("WindowTitle_QuickSend"),
FontSize = 13,
Style = (Style)Application.Current.Resources["CaptionTextBlockStyle"],
VerticalAlignment = VerticalAlignment.Center
});
titleBar.Children.Add(titleStack);
Grid.SetRow(titleBar, 0);
outerGrid.Children.Add(titleBar);

Grid.SetRow(body, 1);
outerGrid.Children.Add(body);

Content = outerGrid;
SetTitleBar(titleBar);

// Focus the text box when shown without closing on transient deactivation.
Activated += (s, e) =>
{
TryBringToFront();
RequestInputFocus();
if (e.WindowActivationState != WindowActivationState.Deactivated)
{
TryBringToFront();
RequestInputFocus();
}
};

Closed += (s, e) => Logger.Info("[QuickSend] Dialog closed");
Closed += (s, e) =>
{
_isClosed = true;
Logger.Info("[QuickSend] Dialog closed");
};

Logger.Info($"[QuickSend] Dialog opened (prefill={!string.IsNullOrEmpty(prefillMessage)})");
}
Expand All @@ -152,6 +195,9 @@
{
try
{
if (_isClosed)
return;

var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
if (hwnd == IntPtr.Zero) return;

Expand Down Expand Up @@ -191,7 +237,7 @@

_errorDetailsTextBox.Visibility = Visibility.Collapsed;
_errorDetailsTextBox.Text = string.Empty;
this.SetWindowSize(420, 260);
this.SetWindowSize(420, 260 + TitleBarHeight);

_isSending = true;
_sendButton.IsEnabled = false;
Expand Down Expand Up @@ -257,7 +303,7 @@
_errorDetailsTextBox.MinHeight = 140;
_errorDetailsTextBox.Text = details;
_errorDetailsTextBox.Visibility = Visibility.Visible;
this.SetWindowSize(520, 400);
this.SetWindowSize(520, 400 + TitleBarHeight);

// Move focus to the details box so users can immediately select/copy text.
_errorDetailsTextBox.Focus(FocusState.Programmatic);
Expand All @@ -269,7 +315,7 @@
_errorDetailsTextBox.MinHeight = 80;
_errorDetailsTextBox.Text = details;
_errorDetailsTextBox.Visibility = Visibility.Visible;
this.SetWindowSize(500, 320);
this.SetWindowSize(500, 320 + TitleBarHeight);
}

private static bool TryExtractMissingScope(string? message, out string scope)
Expand Down Expand Up @@ -311,23 +357,40 @@

private void QueueFocusMessageInput()
{
if (_isClosed)
return;

DispatcherQueue?.TryEnqueue(FocusMessageInput);
}

private void RequestInputFocus()
{
QueueFocusMessageInput();
_ = RetryFocusMessageInputAsync();
if (!_focusRetryRunning)
{
_focusRetryRunning = true;
_ = RetryFocusMessageInputAsync();
}
}

private async Task RetryFocusMessageInputAsync()
{
var delaysMs = new[] { 60, 160, 320 };
foreach (var delay in delaysMs)
try
{
var delaysMs = new[] { 60, 160, 320 };
foreach (var delay in delaysMs)
{
await Task.Delay(delay);
if (_isClosed)
return;

TryBringToFront();
QueueFocusMessageInput();
}
}
finally
{
await Task.Delay(delay);
TryBringToFront();
QueueFocusMessageInput();
_focusRetryRunning = false;
}
}

Expand Down Expand Up @@ -367,7 +430,7 @@
_messageTextBox.SelectionStart = _messageTextBox.Text?.Length ?? 0;
}

public new void ShowAsync()

Check warning on line 433 in src/OpenClaw.Tray.WinUI/Dialogs/QuickSendDialog.cs

View workflow job for this annotation

GitHub Actions / test

The member 'QuickSendDialog.ShowAsync()' does not hide an accessible member. The new keyword is not required.

Check warning on line 433 in src/OpenClaw.Tray.WinUI/Dialogs/QuickSendDialog.cs

View workflow job for this annotation

GitHub Actions / test

The member 'QuickSendDialog.ShowAsync()' does not hide an accessible member. The new keyword is not required.

Check warning on line 433 in src/OpenClaw.Tray.WinUI/Dialogs/QuickSendDialog.cs

View workflow job for this annotation

GitHub Actions / build (win-x64)

The member 'QuickSendDialog.ShowAsync()' does not hide an accessible member. The new keyword is not required.

Check warning on line 433 in src/OpenClaw.Tray.WinUI/Dialogs/QuickSendDialog.cs

View workflow job for this annotation

GitHub Actions / build (win-x64)

The member 'QuickSendDialog.ShowAsync()' does not hide an accessible member. The new keyword is not required.

Check warning on line 433 in src/OpenClaw.Tray.WinUI/Dialogs/QuickSendDialog.cs

View workflow job for this annotation

GitHub Actions / build (win-arm64)

The member 'QuickSendDialog.ShowAsync()' does not hide an accessible member. The new keyword is not required.

Check warning on line 433 in src/OpenClaw.Tray.WinUI/Dialogs/QuickSendDialog.cs

View workflow job for this annotation

GitHub Actions / build (win-arm64)

The member 'QuickSendDialog.ShowAsync()' does not hide an accessible member. The new keyword is not required.

Check warning on line 433 in src/OpenClaw.Tray.WinUI/Dialogs/QuickSendDialog.cs

View workflow job for this annotation

GitHub Actions / build-msix (win-x64)

The member 'QuickSendDialog.ShowAsync()' does not hide an accessible member. The new keyword is not required.

Check warning on line 433 in src/OpenClaw.Tray.WinUI/Dialogs/QuickSendDialog.cs

View workflow job for this annotation

GitHub Actions / build-msix (win-arm64)

The member 'QuickSendDialog.ShowAsync()' does not hide an accessible member. The new keyword is not required.
{
Activate();
RequestInputFocus();
Expand Down
Loading