From 8d466e98502f1ae719d3ff9054ed94887199f859 Mon Sep 17 00:00:00 2001 From: "Pawel Troka, PhD Eng" Date: Mon, 26 Oct 2026 13:18:55 +0100 Subject: [PATCH 01/20] Constrain Mono autocomplete list width, height and screen position --- .../AutocompleteMenu/AutocompleteMenu.cs | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/Computator.NET.Desktop/Controls/AutocompleteMenu/AutocompleteMenu.cs b/Computator.NET.Desktop/Controls/AutocompleteMenu/AutocompleteMenu.cs index cb853f68..892175aa 100644 --- a/Computator.NET.Desktop/Controls/AutocompleteMenu/AutocompleteMenu.cs +++ b/Computator.NET.Desktop/Controls/AutocompleteMenu/AutocompleteMenu.cs @@ -60,7 +60,13 @@ public AutocompleteMenu(ISharedViewState sharedViewState) Enabled = true; AppearInterval = 500; _timer.Tick += Timer_Tick; +#if __MonoCS__ + // On Mono a 2,000-pixel completion menu obscures the editor and + // leaves no room for its function reference. Size to the items. + var preferred = new Size(580, 360).DpiScale(); +#else var preferred = new Size(2000, 420).DpiScale(); +#endif var available = Screen.FromControl(Host).WorkingArea.Size; MaximumSize = new Size(Math.Min(preferred.Width, Math.Max(1, available.Width - 4)), Math.Min(preferred.Height, Math.Max(1, available.Height - 4))); @@ -577,6 +583,12 @@ private void Control_Scroll(object sender, ScrollEventArgs e) private void Control_LostFocus(object sender, EventArgs e) { +#if __MonoCS__ + // Mono raises TextBox.LostFocus before the clicked dropdown child + // receives its mouse event. Keep the pending click alive. + if (Host.Visible && (Host.ContainsFocus || Host.Bounds.Contains(Control.MousePosition))) + return; +#endif if (!Host.Focused) Close(); } @@ -657,7 +669,13 @@ private void ShowMenu() point = TargetControlWrapper.GetPositionFromCharIndex(Fragment.Start); point.Offset(2, TargetControlWrapper.TargetControl.Font.Height + 2); // +#if __MonoCS__ + point = GetMonoMenuPosition(point); +#endif Host.Show(TargetControlWrapper.TargetControl, point); +#if __MonoCS__ + ShowMonoSelectedReference(); +#endif if (CaptureFocus) { (Host.ListView as Control).Focus(); @@ -666,8 +684,65 @@ private void ShowMenu() } } else + { +#if __MonoCS__ + // The number of matches, and hence the popup size, can change + // on every keystroke. Clamp it again after each reflow. + var point = TargetControlWrapper.GetPositionFromCharIndex(Fragment.Start); + point.Offset(2, TargetControlWrapper.TargetControl.Font.Height + 2); + Host.Location = TargetControlWrapper.TargetControl.PointToScreen(GetMonoMenuPosition(point)); + ShowMonoSelectedReference(); +#endif (Host.ListView as Control).Invalidate(); + } + } +#if __MonoCS__ + private void SizeMonoList() + { + var list = (AutocompleteListView)Host.ListView; + var area = Screen.FromControl(TargetControlWrapper.TargetControl).WorkingArea; + var maximumWidth = Math.Max(1, Math.Min(MaximumSize.Width, area.Width - 8)); + var measuredWidth = new Size(240, 0).DpiScale().Width; + // Measure a bounded sample; measuring thousands of suggestions + // for every character would make the editor feel unresponsive. + var count = Math.Min(VisibleItems.Count, 64); + for (var i = 0; i < count; i++) + measuredWidth = Math.Max(measuredWidth, + TextRenderer.MeasureText(VisibleItems[i].MenuText ?? "", list.Font, + new Size(4000, list.ItemHeight), TextFormatFlags.SingleLine | TextFormatFlags.NoPrefix).Width + + LeftPadding + SystemInformation.VerticalScrollBarWidth + 8); + if (SelectedItemIndex >= count && SelectedItemIndex < VisibleItems.Count) + measuredWidth = Math.Max(measuredWidth, + TextRenderer.MeasureText(VisibleItems[SelectedItemIndex].MenuText ?? "", list.Font, + new Size(4000, list.ItemHeight), TextFormatFlags.SingleLine | TextFormatFlags.NoPrefix).Width + + LeftPadding + SystemInformation.VerticalScrollBarWidth + 8); + var height = Math.Min(MaximumSize.Height, Math.Max(list.ItemHeight + 1, + VisibleItems.Count * list.ItemHeight + 1)); + list.Size = new Size(Math.Min(maximumWidth, measuredWidth), height); + list.AutoScrollMinSize = new Size(0, VisibleItems.Count * list.ItemHeight + 1); } + + private Point GetMonoMenuPosition(Point requestedClientPoint) + { + var control = TargetControlWrapper.TargetControl; + var caret = control.PointToScreen(requestedClientPoint); + var area = Screen.FromPoint(caret).WorkingArea; + var x = Math.Max(area.Left + 2, Math.Min(caret.X, area.Right - Host.Width - 2)); + var above = caret.Y - control.Font.Height - Host.Height - 4; + var y = caret.Y + Host.Height <= area.Bottom - 2 + ? caret.Y + : above >= area.Top + 2 ? above : area.Bottom - Host.Height - 2; + y = Math.Max(area.Top + 2, Math.Min(y, area.Bottom - Host.Height - 2)); + return control.PointToClient(new Point(x, y)); + } + + private void ShowMonoSelectedReference() + { + if (SelectedItemIndex < 0 || SelectedItemIndex >= VisibleItems.Count || !Host.Visible) + return; + ((AutocompleteListView)Host.ListView).ShowToolTip(VisibleItems[SelectedItemIndex]); + } +#endif private readonly ISharedViewState _sharedViewState; private void BuildAutocompleteList(bool forced) @@ -717,6 +792,9 @@ private void BuildAutocompleteList(bool forced) SelectedItemIndex = 0; Host.ListView.HighlightedItemIndex = -1; +#if __MonoCS__ + SizeMonoList(); +#endif Host.CalcSize(); } @@ -894,7 +972,12 @@ public void SelectNext(int shift) public bool ProcessKey(char c, Keys keyModifiers) { +#if __MonoCS__ + var list = (AutocompleteListView)Host.ListView; + var page = Math.Max(1, list.ClientSize.Height / Math.Max(1, list.ItemHeight)); +#else var page = Host.Height/(Font.Height + 4); +#endif if (keyModifiers == Keys.None) switch ((Keys) c) { From b53ef49dc65dee225ad14e3b388dfabf760f4bf9 Mon Sep 17 00:00:00 2001 From: "Pawel Troka, PhD Eng" Date: Mon, 26 Oct 2026 13:19:23 +0100 Subject: [PATCH 02/20] Keep Mono dropdown at measured size and protect focus transitions --- .../AutocompleteMenu/AutocompleteMenuHost.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Computator.NET.Desktop/Controls/AutocompleteMenu/AutocompleteMenuHost.cs b/Computator.NET.Desktop/Controls/AutocompleteMenu/AutocompleteMenuHost.cs index f7fc7ce3..8ca748f3 100644 --- a/Computator.NET.Desktop/Controls/AutocompleteMenu/AutocompleteMenuHost.cs +++ b/Computator.NET.Desktop/Controls/AutocompleteMenu/AutocompleteMenuHost.cs @@ -14,7 +14,13 @@ internal class AutocompleteMenuHost : ToolStripDropDown public AutocompleteMenuHost(AutocompleteMenu menu) { AutoClose = false; +#if __MonoCS__ + // WinForms/Mono's autosizing can override the list's measured + // dimensions and restore a screen-wide popup. + AutoSize = false; +#else AutoSize = true; +#endif Margin = Padding.Empty; Padding = Padding.Empty; @@ -74,6 +80,17 @@ public override RightToLeft RightToLeft } } +#if __MonoCS__ + protected override bool ShowWithoutActivation => true; + + private bool KeepMonoPopup() + { + var target = Menu.TargetControlWrapper?.TargetControl; + return (target != null && target.ContainsFocus) || + ContainsFocus || Bounds.Contains(Control.MousePosition); + } +#endif + protected override void OnPaintBackground(PaintEventArgs e) { using (var brush = new SolidBrush(listView.Colors.BackColor)) @@ -89,12 +106,18 @@ internal void CalcSize() protected override void OnLostFocus(EventArgs e) { base.OnLostFocus(e); +#if __MonoCS__ + if (KeepMonoPopup()) return; +#endif if (!(ListView as Control).Focused) Close(); } private void ListView_LostFocus(object sender, EventArgs e) { +#if __MonoCS__ + if (KeepMonoPopup()) return; +#endif if (!Focused) Close(); } From 815139e457cebd83f8e12ec2f33070040673affe Mon Sep 17 00:00:00 2001 From: "Pawel Troka, PhD Eng" Date: Mon, 26 Oct 2026 13:19:50 +0100 Subject: [PATCH 03/20] Make Mono suggestions scrollable and reliably clickable --- .../AutocompleteMenu/AutocompleteListView.cs | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/Computator.NET.Desktop/Controls/AutocompleteMenu/AutocompleteListView.cs b/Computator.NET.Desktop/Controls/AutocompleteMenu/AutocompleteListView.cs index f2cdff6e..dc7de4f5 100644 --- a/Computator.NET.Desktop/Controls/AutocompleteMenu/AutocompleteListView.cs +++ b/Computator.NET.Desktop/Controls/AutocompleteMenu/AutocompleteListView.cs @@ -48,7 +48,12 @@ internal AutocompleteListView() formTip = new WebBrowserForm(); Colors = new Colors(); AutoScaleMode=AutoScaleMode.Font; +#if __MonoCS__ + AutoSize = false; + AutoScroll = true; +#else AutoSize = true; +#endif } @@ -113,6 +118,7 @@ public IList VisibleItems { visibleItems = value; SelectedItemIndex = -1; + oldItemCount = -1; AdjustScroll(); Invalidate(); } @@ -134,7 +140,15 @@ public int SelectedItemIndex if (item != null) { +#if __MonoCS__ + // Filtering selects an item before the menu has been placed. + // Showing the help at that point can steal focus or put it + // at an unrelated screen origin on Mono. + if (Parent is AutocompleteMenuHost host && host.Visible) + ShowToolTip(item); +#else ShowToolTip(item); +#endif ScrollToSelected(); } @@ -338,9 +352,17 @@ protected override void OnMouseClick(MouseEventArgs e) if (e.Button == MouseButtons.Left) { - SelectedItemIndex = PointToItemIndex(e.Location); + var index = PointToItemIndex(e.Location); + if (index < 0 || VisibleItems == null || index >= VisibleItems.Count) + return; + SelectedItemIndex = index; ScrollToSelected(); Invalidate(); +#if __MonoCS__ + // A Mono popup can lose its focus between two clicks, so the + // usual Windows double-click-to-accept isn't dependable. + OnItemSelected(); +#endif } } @@ -364,9 +386,11 @@ protected override void OnMouseMove(MouseEventArgs e) protected override void OnMouseDoubleClick(MouseEventArgs e) { base.OnMouseDoubleClick(e); +#if !__MonoCS__ SelectedItemIndex = PointToItemIndex(e.Location); Invalidate(); OnItemSelected(); +#endif } private void OnItemSelected() From 43e03b4c7be8fbb727c19839bf70ebd920121cc4 Mon Sep 17 00:00:00 2001 From: "Pawel Troka, PhD Eng" Date: Mon, 26 Oct 2026 13:20:43 +0100 Subject: [PATCH 04/20] Place and size Mono function references within the visible work area --- .../AutocompleteMenu/WebBrowserTooltip.cs | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/Computator.NET.Desktop/Controls/AutocompleteMenu/WebBrowserTooltip.cs b/Computator.NET.Desktop/Controls/AutocompleteMenu/WebBrowserTooltip.cs index b7aff2f0..c1aa89b4 100644 --- a/Computator.NET.Desktop/Controls/AutocompleteMenu/WebBrowserTooltip.cs +++ b/Computator.NET.Desktop/Controls/AutocompleteMenu/WebBrowserTooltip.cs @@ -52,6 +52,9 @@ public string HTMLCode #endif public void ShowReference(Control owner, Point location) { +#if __MonoCS__ + ShowMonoReference(owner); +#else // Keep the readable width and a bounded height for long articles. // Once the current document has loaded, short articles give back // unused vertical space without widening for oversized equations. @@ -60,8 +63,80 @@ public void ShowReference(Control owner, Point location) maximumHeight = Math.Min(preferred.Height, workingArea.Height); SetReadingSize(new Size(Math.Min(preferred.Width, workingArea.Width), maximumHeight)); Show(owner, location); +#endif + } + +#if __MonoCS__ + protected override bool ShowWithoutActivation => true; + + private void ShowMonoReference(Control owner) + { + if (!owner.IsHandleCreated || !owner.Visible) return; + workingArea = Screen.FromControl(owner).WorkingArea; + var bounds = owner.RectangleToScreen(owner.ClientRectangle); + var preferred = new Size(600, 460).DpiScale(); + var spaceRight = workingArea.Right - bounds.Right - 5; + var spaceLeft = bounds.Left - workingArea.Left - 5; + int width, x, y; + if (Math.Max(spaceRight, spaceLeft) >= new Size(280, 0).DpiScale().Width) + { + // Open beside the actual completion list, not at a fixed + // screen-relative offset that can push the help off-screen. + var right = spaceRight >= spaceLeft; + width = Math.Min(preferred.Width, right ? spaceRight : spaceLeft); + x = right ? bounds.Right + 4 : bounds.Left - width - 4; + maximumHeight = Math.Min(preferred.Height, workingArea.Height - 8); + y = Math.Max(workingArea.Top + 4, + Math.Min(bounds.Top, workingArea.Bottom - maximumHeight - 4)); + } + else + { + // Very narrow displays: place a compact scrollable reference + // above or below the list instead of hiding it beyond an edge. + width = Math.Min(preferred.Width, workingArea.Width - 8); + x = Math.Max(workingArea.Left + 4, + Math.Min(bounds.Left, workingArea.Right - width - 4)); + var below = workingArea.Bottom - bounds.Bottom - 4; + var above = bounds.Top - workingArea.Top - 4; + if (below >= above && below >= 120) + { + maximumHeight = Math.Min(preferred.Height, below); + y = bounds.Bottom + 4; + } + else if (above >= 120) + { + maximumHeight = Math.Min(preferred.Height, above); + y = bounds.Top - maximumHeight - 4; + } + else + { + // Last resort: a bounded overlay, still fully visible. + maximumHeight = Math.Min(preferred.Height, workingArea.Height - 8); + y = workingArea.Top + 4; + } + } + SetReadingSize(new Size(Math.Max(1, width), Math.Max(1, maximumHeight))); + Show(owner, owner.PointToClient(new Point(x, y))); } + private void FitMonoReferenceHeight() + { + if (!Visible || referenceText.TextLength == 0 || maximumHeight <= 0) return; + // The reference is plain text on Mono: use the actual RichTextBox + // layout instead of Windows' HTML document dimensions. + var end = referenceText.GetPositionFromCharIndex(referenceText.TextLength - 1); + if (end.Y < 0 || end.Y >= referenceText.ClientSize.Height) return; + var chrome = Height - referenceText.ClientSize.Height; + var height = Math.Min(maximumHeight, + Math.Max(140, end.Y + referenceText.Font.Height * 2 + chrome)); + if (height >= Height) return; + SetReadingSize(new Size(Width, height)); + Location = new Point( + Math.Max(workingArea.Left + 2, Math.Min(Left, workingArea.Right - Width - 2)), + Math.Max(workingArea.Top + 2, Math.Min(Top, workingArea.Bottom - Height - 2))); + } +#endif + private void SetReadingSize(Size size) { Size = size; @@ -110,6 +185,7 @@ public void setFunctionInfo(FunctionInfo functionInfo, string displayedSignature { #if __MonoCS__ referenceText.ShowDocument(functionInfo); + FitMonoReferenceHeight(); #else HTMLCode = FunctionReferenceDocument.Render(functionInfo, displayedSignature); #endif From 6fa810a3ef0d47addcfcef701c93913c915a61cc Mon Sep 17 00:00:00 2001 From: "Pawel Troka, PhD Eng" Date: Mon, 26 Oct 2026 13:20:57 +0100 Subject: [PATCH 05/20] Use a readable native font for Mono function references --- .../Controls/AutocompleteMenu/MonoReferenceText.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Computator.NET.Desktop/Controls/AutocompleteMenu/MonoReferenceText.cs b/Computator.NET.Desktop/Controls/AutocompleteMenu/MonoReferenceText.cs index 04d9b092..a1642368 100644 --- a/Computator.NET.Desktop/Controls/AutocompleteMenu/MonoReferenceText.cs +++ b/Computator.NET.Desktop/Controls/AutocompleteMenu/MonoReferenceText.cs @@ -21,6 +21,7 @@ public MonoReferenceText() ReadOnly = true; DetectUrls = false; BorderStyle = BorderStyle.None; + Font = new Font(FontFamily.GenericSansSerif, 10f); BackColor = Color.White; ForeColor = Color.Black; Dock = DockStyle.Fill; From 5e30c6034b7d32073877f6136a327f89527e7307 Mon Sep 17 00:00:00 2001 From: "Pawel Troka, PhD Eng" Date: Mon, 26 Oct 2026 13:23:13 +0100 Subject: [PATCH 06/20] Exercise Mono autocomplete with real X11 typing, pointer selection and screenshots --- .../mono/SmokeTests/MonoAutocompleteTests.cs | 246 ++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 build/mono/SmokeTests/MonoAutocompleteTests.cs diff --git a/build/mono/SmokeTests/MonoAutocompleteTests.cs b/build/mono/SmokeTests/MonoAutocompleteTests.cs new file mode 100644 index 00000000..135c6546 --- /dev/null +++ b/build/mono/SmokeTests/MonoAutocompleteTests.cs @@ -0,0 +1,246 @@ +using System; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Windows.Forms; +using Computator.NET.Core.Autocompletion; +using Computator.NET.Core.Model; +using Computator.NET.DataTypes.SettingsTypes; +using CompletionMenu = Computator.NET.Desktop.Controls.AutocompleteMenu.AutocompleteMenu; +using CompletionList = Computator.NET.Desktop.Controls.AutocompleteMenu.AutocompleteListView; +using Settings = Computator.NET.Core.Properties.Settings; + +internal static partial class Program +{ + // This is a real Mono/WinForms-on-X11 acceptance test. The xdotool events + // come from XTEST, rather than invoking protected OnKeyDown/OnMouseClick + // methods or testing a fabricated bitmap of a hidden control. + private static void AutocompleteRealX11() + { + var previousTooltip = Settings.Default.TooltipType; + using (var window = new Form + { + Text = "Mono completion X11 acceptance", + Size = new Size(610, 310), + Location = new Point(90, 100), + ShowInTaskbar = false, + StartPosition = FormStartPosition.Manual + }) + using (var input = new TextBox { Location = new Point(24, 34), Size = new Size(340, 32) }) + using (var menu = new CompletionMenu(new SharedViewState()) { AppearInterval = 50 }) + { + var list = (CompletionList)menu.ListView; + var host = (ToolStripDropDown)typeof(CompletionMenu) + .GetProperty("Host", BindingFlags.Instance | BindingFlags.NonPublic) + .GetValue(menu, null); + var help = (ToolStripDropDown)typeof(CompletionList) + .GetField("toolTip", BindingFlags.Instance | BindingFlags.NonPublic) + .GetValue(list); + var reference = (RichTextBox)help.Items.OfType().Single().Control; + try + { + Settings.Default.TooltipType = TooltipType.Default; + menu.SetAutocompleteItems(new[] + { + Completion("sin(x)", "Sine reference"), + Completion("sinh(x)", "Hyperbolic sine reference"), + Completion("sign(x)", "Sign reference") + }); + window.Controls.Add(input); + menu.SetAutocompleteMenu(input, menu); + window.Show(); + window.Activate(); + input.Focus(); + PumpX11(); + + TypeX11("si"); + WaitForX11(() => menu.Visible, "Typing did not open the Mono autocomplete dropdown."); + Check(input.Text == "si", "Native X11 keystrokes were not received by the editor."); + Check(menu.VisibleItems.Count == 3, "Completion filtering lost the matching expressions."); + Check(host.Width >= 200 && host.Width < 660, "The Mono dropdown is empty or oversized: " + host.Bounds); + Check(host.Height >= list.ItemHeight && host.Height <= 380, "The dropdown height does not fit visible rows."); + Check(Screen.FromControl(input).WorkingArea.Contains(host.Bounds), "Completion list is outside the screen."); + WaitForX11(() => help.Visible, "The Mono function reference failed to open."); + Check(Screen.FromControl(input).WorkingArea.Contains(help.Bounds), "Reference help is clipped beyond the screen."); + Check(reference.Text.Contains("Sine reference") && reference.ReadOnly, + "The native text reference is missing or editable."); + Check(input.ContainsFocus, "Opening autocomplete moved keyboard focus away from the editor."); + CaptureAutocompleteX11(window, host, help, "autocomplete-after.png"); + + // Keyboard selection and insertion through the real X11 input + // queue (including the existing fragment replacement logic). + KeyX11("Down"); + PumpX11(); + Check(menu.SelectedItemIndex == 1, "ArrowDown did not change the highlighted suggestion."); + KeyX11("Return"); + PumpX11(); + Equal(input.Text, "sinh(x)"); + Check(!menu.Visible && !help.Visible, "Accepting a suggestion left a topmost popup behind."); + + // A single real mouse click must not be swallowed by the + // TextBox.LostFocus race before the list receives it. + ResetAutocompleteInput(menu, input); + TypeX11("si"); + WaitForX11(() => menu.Visible, "Mouse selection did not reopen completion."); + var item = menu.GetItemRectangle(2); + var point = list.PointToScreen(new Point(item.Left + Math.Min(50, item.Width / 2), + item.Top + item.Height / 2)); + MoveAndClickX11(point.X, point.Y); + PumpX11(); + Equal(input.Text, "sign(x)"); + Check(!menu.Visible, "Mouse acceptance did not close completion."); + + // More items than the viewport: native PageDown and Up must + // move selection and reveal the chosen row via scrolling. + menu.SetAutocompleteItems(Enumerable.Range(0, 42) + .Select(i => Completion("si" + i.ToString("D2") + "(x)", "Scrollable reference " + i)) + .ToArray()); + ResetAutocompleteInput(menu, input); + TypeX11("si"); + WaitForX11(() => menu.Visible, "Scrolling scenario did not open completion."); + Check(menu.VisibleItems.Count == 42, "Long suggestions were incorrectly filtered."); + Check(list.VerticalScroll.Visible, "The Mono completion list lacks a usable vertical scrollbar."); + KeyX11("Next"); + KeyX11("Next"); + PumpX11(); + Check(menu.SelectedItemIndex > 4 && list.VerticalScroll.Value > 0, + "PageDown failed to advance and scroll the actual completion list."); + KeyX11("Up"); + PumpX11(); + Check(menu.SelectedItemIndex >= 0, "ArrowUp left no valid selection."); + KeyX11("Escape"); + PumpX11(); + Check(!menu.Visible && !help.Visible, "Escape did not dismiss both autocomplete windows."); + + // Repeated show/close and Ctrl+Space through X11 must not + // leave orphaned help/toolstrip windows. + for (var i = 0; i < 3; i++) + { + input.Focus(); + KeyX11("ctrl+space"); + WaitForX11(() => menu.Visible, "Ctrl+Space failed after a previous close."); + KeyX11("Escape"); + PumpX11(); + Check(!menu.Visible && !help.Visible, "A reopened popup remained visible after Escape."); + } + + // Near the lower-right display edge the same native dropdown + // must flip above the caret and keep its reference on-screen. + window.Location = new Point(Screen.PrimaryScreen.WorkingArea.Right - window.Width - 5, + Screen.PrimaryScreen.WorkingArea.Bottom - window.Height - 5); + input.Location = new Point(window.ClientSize.Width - input.Width - 16, + window.ClientSize.Height - input.Height - 16); + window.Activate(); + ResetAutocompleteInput(menu, input); + TypeX11("si"); + WaitForX11(() => menu.Visible, "Edge-position completion did not open."); + var area = Screen.FromControl(input).WorkingArea; + Check(area.Contains(host.Bounds), "Completion dropdown is clipped at the bottom/right screen edge."); + WaitForX11(() => help.Visible, "Edge-position function reference did not open."); + Check(area.Contains(help.Bounds), "Function description is off-screen near the edge."); + Check(reference.Text.Contains("Scrollable reference"), "Wrong reference shown after reflow."); + menu.Close(); + Check(!host.Visible && !help.Visible, "Final close left a floating reference behind."); + } + finally + { + menu.Close(); + menu.SetAutocompleteMenu(input, null); + Settings.Default.TooltipType = previousTooltip; + } + } + } + + private static AutocompleteItem Completion(string signature, string title) + { + return new AutocompleteItem(signature) + { + Details = new FunctionInfo + { + Title = title, + Signature = signature, + Description = "

Readable help for " + signature + " with a complete function description.

" + } + }; + } + + private static void ResetAutocompleteInput(CompletionMenu menu, TextBox input) + { + menu.Close(); + input.Text = ""; + input.SelectionStart = 0; + input.Focus(); + PumpX11(); + } + + private static void TypeX11(string characters) + { + Xdotool("type --clearmodifiers --delay 35 " + characters); + PumpX11(); + } + + private static void KeyX11(string key) + { + Xdotool("key --clearmodifiers " + key); + PumpX11(); + } + + private static void MoveAndClickX11(int x, int y) + { + Xdotool("mousemove " + x + " " + y + " click 1"); + PumpX11(); + } + + private static void Xdotool(string arguments) + { + using (var process = Process.Start(new ProcessStartInfo("xdotool", arguments) + { + UseShellExecute = false, RedirectStandardError = true, CreateNoWindow = true + })) + { + if (!process.WaitForExit(6000)) + { + process.Kill(); + throw new InvalidOperationException("Timed out sending real X11 events: " + arguments); + } + Check(process.ExitCode == 0, "xdotool failed: " + process.StandardError.ReadToEnd()); + } + } + + private static void WaitForX11(Func condition, string error) + { + var timer = Stopwatch.StartNew(); + while (!condition() && timer.ElapsedMilliseconds < 3000) + PumpX11(); + Check(condition(), error); + } + + private static void PumpX11() + { + for (var i = 0; i < 6; i++) + { + Application.DoEvents(); + Thread.Sleep(12); + } + } + + private static void CaptureAutocompleteX11(Form window, ToolStripDropDown menu, + ToolStripDropDown reference, string name) + { + var rect = Rectangle.Union(window.Bounds, Rectangle.Union(menu.Bounds, reference.Bounds)); + rect.Inflate(8, 8); + rect.Intersect(Screen.FromControl(window).Bounds); + Check(rect.Width > 0 && rect.Height > 0, "No visible autocomplete area to capture."); + using (var image = new Bitmap(rect.Width, rect.Height)) + { + using (var graphics = Graphics.FromImage(image)) + graphics.CopyFromScreen(rect.Location, Point.Empty, rect.Size); + AssertImageHasVariation(image, 20); + image.Save(Path.Combine(outputDirectory, name), ImageFormat.Png); + } + } +} From 6f896001c0413c4686d7cf7261652de0fcb1f1e9 Mon Sep 17 00:00:00 2001 From: "Pawel Troka, PhD Eng" Date: Mon, 26 Oct 2026 13:23:33 +0100 Subject: [PATCH 07/20] Include native Mono autocomplete GUI acceptance in the test suite --- build/mono/SmokeTests/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/build/mono/SmokeTests/Program.cs b/build/mono/SmokeTests/Program.cs index 6b5ccdeb..295aa7ff 100644 --- a/build/mono/SmokeTests/Program.cs +++ b/build/mono/SmokeTests/Program.cs @@ -97,6 +97,7 @@ private static int Main(string[] args) Run("desktop-numerical-method-catalog", NumericalMethodCatalog); Run("native-gsl-generated-import-and-error-recovery", NativeGsl); Run("mono-function-reference-readable-text", FunctionReference); + Run("mono-autocomplete-real-x11-layout-and-selection", AutocompleteRealX11); Run("unicode-editor-documents-and-renaming", EditorDocuments); Run("unshown-editor-tabs-have-backed-documents", UnshownEditorDocuments); Run("desktop-visible-custom-functions-file-and-script-workflow", DesktopWorkflow); From 9dd1f58909bb941ceef952a32ffbb408276cb2af Mon Sep 17 00:00:00 2001 From: "Pawel Troka, PhD Eng" Date: Mon, 26 Oct 2026 13:23:36 +0100 Subject: [PATCH 08/20] Install X11 input driver for native autocomplete acceptance --- .github/workflows/mono-desktop.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mono-desktop.yml b/.github/workflows/mono-desktop.yml index 4e92b47b..aaa955fc 100644 --- a/.github/workflows/mono-desktop.yml +++ b/.github/workflows/mono-desktop.yml @@ -23,7 +23,7 @@ jobs: - name: Install the actual Mono and X11 runtime run: | sudo apt-get update - sudo apt-get install -y --no-install-recommends mono-devel xvfb xauth libgdiplus libgsl-dev fonts-dejavu-core + sudo apt-get install -y --no-install-recommends mono-devel xvfb xauth xdotool libgdiplus libgsl-dev fonts-dejavu-core - name: Build the opt-in Framework graph and run desktop acceptance run: ./build.sh --target=Mono-Validate - name: Retain runtime evidence even on failure From e63efa74fe06d2629c64c72cc8cf8d16c2950ece Mon Sep 17 00:00:00 2001 From: "Pawel Troka, PhD Eng" Date: Mon, 26 Oct 2026 13:23:44 +0100 Subject: [PATCH 09/20] Clear stale autocomplete screenshot before each Mono acceptance run --- build/mono/validate.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/mono/validate.sh b/build/mono/validate.sh index b85b35e0..e16a21fb 100644 --- a/build/mono/validate.sh +++ b/build/mono/validate.sh @@ -55,7 +55,7 @@ mono "$validation_temp/LegacyFixture.exe" "$validation_temp/legacy-settings.dat" # A failed or timed-out run must not expose an old passing report/screenshot. rm -f -- "$results/mono-results.xml" "$results/startup.png" "$results/desktop-workflow.png" \ "$results/real-chart.png" "$results/complex-chart.png" "$results/unsupported-3d.png" "$results/function-reference.png" \ - "$results/calculator-inputs.png" "$results/numerical-inputs.png" + "$results/calculator-inputs.png" "$results/numerical-inputs.png" "$results/autocomplete-after.png" status=0 COMPUTATOR_DATA_DIRECTORY="$validation_temp/startup" \ XDG_CONFIG_HOME="$validation_temp/config" XDG_CACHE_HOME="$validation_temp/cache" \ From 37564f33d57db92b02b04c4383813d3f1a45cdc5 Mon Sep 17 00:00:00 2001 From: "Pawel Troka, PhD Eng" Date: Mon, 26 Oct 2026 13:23:47 +0100 Subject: [PATCH 10/20] Document X11 GUI regression-test dependency --- docs/MONO-DESKTOP.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/MONO-DESKTOP.md b/docs/MONO-DESKTOP.md index 1feb7672..ae8f4bae 100644 --- a/docs/MONO-DESKTOP.md +++ b/docs/MONO-DESKTOP.md @@ -21,7 +21,7 @@ On Ubuntu 24.04, install the GUI and native runtime prerequisites: ```sh sudo apt-get update sudo apt-get install -y --no-install-recommends \ - mono-devel libgdiplus libgsl-dev fonts-dejavu-core xvfb xauth + mono-devel libgdiplus libgsl-dev fonts-dejavu-core xvfb xauth xdotool ``` `libgsl-dev` supplies the unversioned library name used by Mono's P/Invoke loader. From 9d94d73ff77e68fab022528ec8bcc67c524fa462 Mon Sep 17 00:00:00 2001 From: "Pawel Troka, PhD Eng" Date: Mon, 26 Oct 2026 13:26:37 +0100 Subject: [PATCH 11/20] Rely on ToolStripDropDown native no-activation behavior --- .../Controls/AutocompleteMenu/AutocompleteMenuHost.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Computator.NET.Desktop/Controls/AutocompleteMenu/AutocompleteMenuHost.cs b/Computator.NET.Desktop/Controls/AutocompleteMenu/AutocompleteMenuHost.cs index 8ca748f3..ea08ef0e 100644 --- a/Computator.NET.Desktop/Controls/AutocompleteMenu/AutocompleteMenuHost.cs +++ b/Computator.NET.Desktop/Controls/AutocompleteMenu/AutocompleteMenuHost.cs @@ -81,8 +81,6 @@ public override RightToLeft RightToLeft } #if __MonoCS__ - protected override bool ShowWithoutActivation => true; - private bool KeepMonoPopup() { var target = Menu.TargetControlWrapper?.TargetControl; From 42021fa6e486d3bd0c6f7ee426351723dcea7b02 Mon Sep 17 00:00:00 2001 From: "Pawel Troka, PhD Eng" Date: Mon, 26 Oct 2026 13:26:41 +0100 Subject: [PATCH 12/20] Rely on ToolStripDropDown native no-activation behavior --- .../Controls/AutocompleteMenu/WebBrowserTooltip.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Computator.NET.Desktop/Controls/AutocompleteMenu/WebBrowserTooltip.cs b/Computator.NET.Desktop/Controls/AutocompleteMenu/WebBrowserTooltip.cs index c1aa89b4..6fbabbd0 100644 --- a/Computator.NET.Desktop/Controls/AutocompleteMenu/WebBrowserTooltip.cs +++ b/Computator.NET.Desktop/Controls/AutocompleteMenu/WebBrowserTooltip.cs @@ -67,8 +67,6 @@ public void ShowReference(Control owner, Point location) } #if __MonoCS__ - protected override bool ShowWithoutActivation => true; - private void ShowMonoReference(Control owner) { if (!owner.IsHandleCreated || !owner.Visible) return; From 2708318e38c9af7a11358c0cf598efd40b168c7d Mon Sep 17 00:00:00 2001 From: "Pawel Troka, PhD Eng" Date: Mon, 26 Oct 2026 13:30:48 +0100 Subject: [PATCH 13/20] Set explicit X11 input focus in headless autocomplete acceptance and improve diagnostics --- build/mono/SmokeTests/MonoAutocompleteTests.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/build/mono/SmokeTests/MonoAutocompleteTests.cs b/build/mono/SmokeTests/MonoAutocompleteTests.cs index 135c6546..26633751 100644 --- a/build/mono/SmokeTests/MonoAutocompleteTests.cs +++ b/build/mono/SmokeTests/MonoAutocompleteTests.cs @@ -56,9 +56,17 @@ private static void AutocompleteRealX11() window.Activate(); input.Focus(); PumpX11(); + // xvfb-run does not launch a window manager. Set the real X11 + // input focus explicitly; Form.Activate alone is insufficient. + Xdotool("search --onlyvisible --name Mono.completion.X11.acceptance windowfocus --sync"); + input.Focus(); + PumpX11(); TypeX11("si"); - WaitForX11(() => menu.Visible, "Typing did not open the Mono autocomplete dropdown."); + WaitForX11(() => menu.Visible, "Typing did not open the Mono autocomplete dropdown." + + " text='" + input.Text + "', focus=" + input.Focused + + ", containsFocus=" + input.ContainsFocus + + ", matches=" + menu.VisibleItems.Count + ", windowFocus=" + window.ContainsFocus); Check(input.Text == "si", "Native X11 keystrokes were not received by the editor."); Check(menu.VisibleItems.Count == 3, "Completion filtering lost the matching expressions."); Check(host.Width >= 200 && host.Width < 660, "The Mono dropdown is empty or oversized: " + host.Bounds); From 0ae3ee27be543db223b64ead014e3f73a0cdca19 Mon Sep 17 00:00:00 2001 From: "Pawel Troka, PhD Eng" Date: Mon, 26 Oct 2026 13:33:34 +0100 Subject: [PATCH 14/20] Accept native monochrome WinForms screenshots without weakening interaction checks --- build/mono/SmokeTests/MonoAutocompleteTests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build/mono/SmokeTests/MonoAutocompleteTests.cs b/build/mono/SmokeTests/MonoAutocompleteTests.cs index 26633751..43803153 100644 --- a/build/mono/SmokeTests/MonoAutocompleteTests.cs +++ b/build/mono/SmokeTests/MonoAutocompleteTests.cs @@ -247,7 +247,10 @@ private static void CaptureAutocompleteX11(Form window, ToolStripDropDown menu, { using (var graphics = Graphics.FromImage(image)) graphics.CopyFromScreen(rect.Location, Point.Empty, rect.Size); - AssertImageHasVariation(image, 20); + // WinForms suggestion controls use a deliberately restrained, + // mostly monochrome palette. A chart's 20-color threshold would + // reject a perfectly valid desktop capture on headless Xvfb. + AssertImageHasVariation(image, 4); image.Save(Path.Combine(outputDirectory, name), ImageFormat.Png); } } From 70bd467036fe44e7666ab2ee7f62922edf3b5828 Mon Sep 17 00:00:00 2001 From: "Pawel Troka, PhD Eng" Date: Mon, 26 Oct 2026 13:38:34 +0100 Subject: [PATCH 15/20] Capture full X11 framebuffer and native mapped-window tree for popup diagnosis --- .../mono/SmokeTests/MonoAutocompleteTests.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/build/mono/SmokeTests/MonoAutocompleteTests.cs b/build/mono/SmokeTests/MonoAutocompleteTests.cs index 43803153..a107c54b 100644 --- a/build/mono/SmokeTests/MonoAutocompleteTests.cs +++ b/build/mono/SmokeTests/MonoAutocompleteTests.cs @@ -77,6 +77,7 @@ private static void AutocompleteRealX11() Check(reference.Text.Contains("Sine reference") && reference.ReadOnly, "The native text reference is missing or editable."); Check(input.ContainsFocus, "Opening autocomplete moved keyboard focus away from the editor."); + InspectX11WindowStack(window, input, host, list, help); CaptureAutocompleteX11(window, host, help, "autocomplete-after.png"); // Keyboard selection and insertion through the real X11 input @@ -236,6 +237,35 @@ private static void PumpX11() } } + private static void InspectX11WindowStack(Form window, Control input, ToolStripDropDown menu, + Control list, ToolStripDropDown reference) + { + var details = "window=" + window.Bounds + " input=" + input.RectangleToScreen(input.ClientRectangle) + + " menu=" + menu.Bounds + " list=" + list.RectangleToScreen(list.ClientRectangle) + + " reference=" + reference.Bounds + + " visible(menu,ref)=" + menu.Visible + "," + reference.Visible + + " parent=" + list.Parent?.GetType().FullName + Environment.NewLine; + using (var process = Process.Start(new ProcessStartInfo("xwininfo", "-root -tree") + { + UseShellExecute = false, RedirectStandardOutput = true, + RedirectStandardError = true, CreateNoWindow = true + })) + { + details += process.StandardOutput.ReadToEnd() + Environment.NewLine + + process.StandardError.ReadToEnd(); + Check(process.WaitForExit(6000), "xwininfo hung during native window inspection."); + } + File.WriteAllText(Path.Combine(outputDirectory, "autocomplete-x11-windows.log"), details); + Console.WriteLine(details); + var screen = Screen.FromControl(window).Bounds; + using (var bitmap = new Bitmap(screen.Width, screen.Height)) + { + using (var graphics = Graphics.FromImage(bitmap)) + graphics.CopyFromScreen(screen.Location, Point.Empty, screen.Size); + bitmap.Save(Path.Combine(outputDirectory, "autocomplete-entire-x11-screen.png"), ImageFormat.Png); + } + } + private static void CaptureAutocompleteX11(Form window, ToolStripDropDown menu, ToolStripDropDown reference, string name) { From e97144e62beed20612bdbf76abb9e39b0ac40aed Mon Sep 17 00:00:00 2001 From: "Pawel Troka, PhD Eng" Date: Mon, 26 Oct 2026 13:38:48 +0100 Subject: [PATCH 16/20] Install xwininfo for native X11 popup mapping diagnostics --- .github/workflows/mono-desktop.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mono-desktop.yml b/.github/workflows/mono-desktop.yml index aaa955fc..150679cb 100644 --- a/.github/workflows/mono-desktop.yml +++ b/.github/workflows/mono-desktop.yml @@ -23,7 +23,7 @@ jobs: - name: Install the actual Mono and X11 runtime run: | sudo apt-get update - sudo apt-get install -y --no-install-recommends mono-devel xvfb xauth xdotool libgdiplus libgsl-dev fonts-dejavu-core + sudo apt-get install -y --no-install-recommends mono-devel xvfb xauth xdotool x11-utils libgdiplus libgsl-dev fonts-dejavu-core - name: Build the opt-in Framework graph and run desktop acceptance run: ./build.sh --target=Mono-Validate - name: Retain runtime evidence even on failure From a725f14096ff71329e0a40633939e4d92b9e23a5 Mon Sep 17 00:00:00 2001 From: "Pawel Troka, PhD Eng" Date: Mon, 26 Oct 2026 13:45:19 +0100 Subject: [PATCH 17/20] Fix Mono hosted autocomplete list centered outside its dropdown bounds --- .../Controls/AutocompleteMenu/AutocompleteMenuHost.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Computator.NET.Desktop/Controls/AutocompleteMenu/AutocompleteMenuHost.cs b/Computator.NET.Desktop/Controls/AutocompleteMenu/AutocompleteMenuHost.cs index ea08ef0e..f5f8148a 100644 --- a/Computator.NET.Desktop/Controls/AutocompleteMenu/AutocompleteMenuHost.cs +++ b/Computator.NET.Desktop/Controls/AutocompleteMenu/AutocompleteMenuHost.cs @@ -58,6 +58,14 @@ public IAutocompleteListView ListView AutoSize = false, AutoToolTip = false }; +#if __MonoCS__ + // Mono's ToolStripControlHost can center a shrunken list in + // its previous (screen-sized) hosting rectangle, leaving the + // actual X11 child window completely outside the popup. + // Top-left alignment keeps native hit testing and painting + // inside the visible ToolStripDropDown on every reflow. + Host.ControlAlign = ContentAlignment.TopLeft; +#endif (ListView as Control).MaximumSize = Menu.MaximumSize; (ListView as Control).Size = Menu.MaximumSize; From d97c213c9cf9c7808226e1c5e88b549ba5676a27 Mon Sep 17 00:00:00 2001 From: "Pawel Troka, PhD Eng" Date: Mon, 26 Oct 2026 13:45:38 +0100 Subject: [PATCH 18/20] Record X11 popup map states alongside native layout bounds --- build/mono/SmokeTests/MonoAutocompleteTests.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/build/mono/SmokeTests/MonoAutocompleteTests.cs b/build/mono/SmokeTests/MonoAutocompleteTests.cs index a107c54b..b74de94b 100644 --- a/build/mono/SmokeTests/MonoAutocompleteTests.cs +++ b/build/mono/SmokeTests/MonoAutocompleteTests.cs @@ -255,6 +255,20 @@ private static void InspectX11WindowStack(Form window, Control input, ToolStripD + process.StandardError.ReadToEnd(); Check(process.WaitForExit(6000), "xwininfo hung during native window inspection."); } + foreach (var popup in new[] { menu, reference }) + { + var nativeId = "0x" + popup.Handle.ToInt64().ToString("x"); + using (var inspect = Process.Start(new ProcessStartInfo("xwininfo", "-id " + nativeId) + { + UseShellExecute = false, RedirectStandardOutput = true, + RedirectStandardError = true, CreateNoWindow = true + })) + { + details += nativeId + Environment.NewLine + inspect.StandardOutput.ReadToEnd() + + Environment.NewLine + inspect.StandardError.ReadToEnd(); + Check(inspect.WaitForExit(6000), "Native X11 map-state query hung."); + } + } File.WriteAllText(Path.Combine(outputDirectory, "autocomplete-x11-windows.log"), details); Console.WriteLine(details); var screen = Screen.FromControl(window).Bounds; From a21a54fed286c1129fb6410cdb69a6ae3178ad26 Mon Sep 17 00:00:00 2001 From: "Pawel Troka, PhD Eng" Date: Tue, 27 Oct 2026 00:24:34 +0100 Subject: [PATCH 19/20] fix(mono): include MonoAutocompleteTests.cs in developer solution inventory --- Computator.NET.slnx | 1 + 1 file changed, 1 insertion(+) diff --git a/Computator.NET.slnx b/Computator.NET.slnx index 77c6a057..89f38ea5 100644 --- a/Computator.NET.slnx +++ b/Computator.NET.slnx @@ -127,6 +127,7 @@ + From 2394ffcd953acceac68dac69d6678149397456af Mon Sep 17 00:00:00 2001 From: "Pawel Troka, PhD Eng" Date: Tue, 27 Oct 2026 00:40:38 +0100 Subject: [PATCH 20/20] test(mono): require real painted autocomplete and help popup pixels in X11 screenshot --- .../mono/SmokeTests/MonoAutocompleteTests.cs | 61 +++++++++++++++++-- 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/build/mono/SmokeTests/MonoAutocompleteTests.cs b/build/mono/SmokeTests/MonoAutocompleteTests.cs index b74de94b..31cb65c3 100644 --- a/build/mono/SmokeTests/MonoAutocompleteTests.cs +++ b/build/mono/SmokeTests/MonoAutocompleteTests.cs @@ -287,15 +287,64 @@ private static void CaptureAutocompleteX11(Form window, ToolStripDropDown menu, rect.Inflate(8, 8); rect.Intersect(Screen.FromControl(window).Bounds); Check(rect.Width > 0 && rect.Height > 0, "No visible autocomplete area to capture."); + // Mapped X11 windows and WinForms Visible are not sufficient: on a + // headless Mono runner a popup can be mapped but not yet *painted*. + // The original screenshot check could pass on the form's own colors + // while the actual menu and reference remained invisible on screen. + var watch = Stopwatch.StartNew(); + var menuPainted = false; + var helpPainted = false; using (var image = new Bitmap(rect.Width, rect.Height)) { - using (var graphics = Graphics.FromImage(image)) - graphics.CopyFromScreen(rect.Location, Point.Empty, rect.Size); - // WinForms suggestion controls use a deliberately restrained, - // mostly monochrome palette. A chart's 20-color threshold would - // reject a perfectly valid desktop capture on headless Xvfb. - AssertImageHasVariation(image, 4); + do + { + Application.DoEvents(); + using (var graphics = Graphics.FromImage(image)) + graphics.CopyFromScreen(rect.Location, Point.Empty, rect.Size); + menuPainted = HasNativePopupPixels(image, rect.Location, menu.Bounds); + helpPainted = HasNativePopupPixels(image, rect.Location, reference.Bounds); + if (menuPainted && helpPainted) + break; + // Request another actual WinForms/X11 paint. Never draw an + // artificial popup onto the screenshot to satisfy acceptance. + menu.Invalidate(true); + reference.Invalidate(true); + menu.Refresh(); + reference.Refresh(); + PumpX11(); + } + while (watch.Elapsed < TimeSpan.FromSeconds(5)); + image.Save(Path.Combine(outputDirectory, name), ImageFormat.Png); } + Check(menuPainted && helpPainted, + "The Mono autocomplete windows are mapped but their real X11 pixels are missing: " + + "completion painted=" + menuPainted + ", help painted=" + helpPainted + + ". See " + name + " for the captured failure."); + } + + private static bool HasNativePopupPixels(Bitmap capture, Point origin, Rectangle popupBounds) + { + var region = Rectangle.Intersect( + new Rectangle(popupBounds.X - origin.X, popupBounds.Y - origin.Y, + popupBounds.Width, popupBounds.Height), + new Rectangle(Point.Empty, capture.Size)); + if (region.Width < 20 || region.Height < 20) + return false; + // The shipped Mono completion and read-only reference controls have + // white item/text surfaces. Sample native screen pixels *inside* each + // popup rather than counting the parent window's unrelated colors. + // The Xvfb form background is beige and the X11 root is black; + // neither can satisfy the white-surface threshold. + var light = 0; + var total = 0; + for (var y = region.Top + 4; y < region.Bottom - 4; y += 4) + for (var x = region.Left + 4; x < region.Right - 4; x += 4) + { + var pixel = capture.GetPixel(x, y); + if (pixel.R >= 245 && pixel.G >= 245 && pixel.B >= 245) light++; + total++; + } + return total > 0 && light * 4 > total; } }