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
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,10 @@ artifacts/codex-dumps/
research/
__pycache__/
*.pyc

# Local Windows .NET builds and published native helper artifacts.
apps/OpenComputerUseWindows/native/bin/
apps/OpenComputerUseWindows/native/obj/
apps/OpenComputerUseWindows/fixture/HangWindowFixture/bin/
apps/OpenComputerUseWindows/fixture/HangWindowFixture/obj/
dist/windows-native/
14 changes: 13 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ SLUG ?=
AGENTS ?= claude,codex
SCENARIO ?= list-apps

.PHONY: init build app test conformance agent-smoke check-docs check-repo ci release-package npm-build npm-publish new-history new-plan
.PHONY: init build app test conformance agent-smoke check-docs check-repo ci release-package npm-build npm-publish new-history new-plan windows-native windows-native-publish windows-native-lifecycle

init:
@if [ -z "$(PROJECT)" ]; then echo "用法: make init PROJECT=项目名"; exit 1; fi
Expand Down Expand Up @@ -50,3 +50,15 @@ new-history:
new-plan:
@if [ -z "$(SLUG)" ]; then echo "用法: make new-plan SLUG=计划名"; exit 1; fi
./scripts/new-exec-plan.sh "$(SLUG)"

windows-native:
dotnet build apps/OpenComputerUseWindows/native/MakaCuWindows.csproj -c Release

windows-native-publish:
powershell -ExecutionPolicy Bypass -File scripts/windows/publish-native.ps1

windows-native-lifecycle:
powershell -ExecutionPolicy Bypass -File scripts/windows/publish-native.ps1 -IncludeFixture
node scripts/windows/lifecycle-driver.mjs \
dist/windows-native/win-x64/helper/maka-cu-windows.exe \
dist/windows-native/win-x64/fixture/maka-cu-windows-fixture.exe
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>HangWindowFixture</RootNamespace>
<AssemblyName>maka-cu-windows-fixture</AssemblyName>
</PropertyGroup>

</Project>


257 changes: 257 additions & 0 deletions apps/OpenComputerUseWindows/fixture/HangWindowFixture/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

// Deterministic test fixture for the maka-cu-windows native helper (checks 4-6 and
// identity cases). A WinForms window with a TextBox (ValuePattern), a Button
// (InvokePattern) and a static Label. Stdin line protocol:
// freeze - block the UI thread (message pump stops -> window becomes
// Not Responding -> UIA provider calls block: a REAL blocked
// provider call, not a sleep inside the helper)
// unfreeze - resume the UI thread
// cover - show a larger, topmost solid-red window over the main window
// uncover - hide the cover
// recreate - close + recreate the main window (new HWND, new generation)
// replace-control - dispose and replace the text control while preserving
// the top-level HWND (same-window identity regression)
// shutdown - exit
// stdout:
// READY <pid> <hwnd>
// SIZE <w> <h> (physical window rect via GetWindowRect, after recreate too)

using System.Runtime.InteropServices;
using System.Windows.Forms;

var fixture = new Fixture();
fixture.Run();

sealed class Fixture
{
const string WindowTitle = "maka-cu-windows-fixture";

Form _form = null!;
Form? _cover;
TextBox _input = null!;
readonly ManualResetEventSlim _freezeEvent = new(false);

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetWindowRect(IntPtr hWnd, out RECT rect);

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetProcessDpiAwarenessContext(IntPtr value);

[DllImport("dwmapi.dll")]
static extern int DwmGetWindowAttribute(IntPtr hWnd, int attribute, out RECT value, int valueSize);

[StructLayout(LayoutKind.Sequential)]
struct RECT { public int Left, Top, Right, Bottom; }

public void Run()
{
// WGC reports physical pixels. Set per-monitor awareness before any
// WinForms handle is created so SIZE and capture ContentSize agree.
_ = SetProcessDpiAwarenessContext(new IntPtr(-4)); // PER_MONITOR_AWARE_V2
CreateForm();
// The independent ApplicationContext does not auto-show a form.
_form.Show();

var reader = new Thread(() =>
{
while (Console.ReadLine() is { } line)
{
try
{
switch (line.Trim())
{
case "freeze":
// Runs on the UI thread; the pump stops while it waits.
_freezeEvent.Reset();
_form.BeginInvoke(() =>
{
Console.WriteLine("FROZEN");
Console.Out.Flush();
_freezeEvent.Wait();
});
break;
case "unfreeze":
_freezeEvent.Set();
Console.WriteLine("UNFROZEN");
Console.Out.Flush();
break;
case "cover": OnUi(ShowCover); break;
case "uncover": OnUi(HideCover); break;
case "recreate": OnUi(Recreate); break;
case "replace-control": OnUi(ReplaceControl); break;
case "shutdown": OnUi(() => Application.Exit()); return;
}
}
catch (Exception ex)
{
Console.WriteLine($"CMD_ERROR {ex.GetType().Name}: {ex.Message}");
Console.Out.Flush();
}
}
}) { IsBackground = true, Name = "cmd-reader" };
reader.Start();

// Use an independent application context so a test recreation can
// dispose the original form without ending the message loop.
Application.Run(new ApplicationContext());
}

void OnUi(Action a)
{
Form f;
lock (this) { f = _form; }
f.Invoke(a);
}

void CreateForm()
{
var form = new Form
{
Text = WindowTitle,
Width = 480,
Height = 360,
StartPosition = FormStartPosition.CenterScreen,
FormBorderStyle = FormBorderStyle.FixedDialog,
MaximizeBox = false,
MinimizeBox = false,
BackColor = Color.White,
};
var label = new Label
{
Text = "maka-cu-windows-fixture static content",
AutoSize = true,
Location = new Point(16, 16),
};
var input = new TextBox
{
Name = "fixture-input",
Text = "",
Width = 300,
Location = new Point(16, 48),
};
var button = new Button
{
Name = "fixture-button",
Text = "fixture-button",
Width = 120,
Location = new Point(16, 88),
};
var sentinel = new Panel
{
Name = "fixture-sentinel",
BackColor = Color.LimeGreen,
Width = 80,
Height = 50,
Location = new Point(360, 220),
};
form.Controls.Add(label);
form.Controls.Add(input);
form.Controls.Add(button);
form.Controls.Add(sentinel);
_input = input;
form.FormClosed += (sender, _) =>
{
// Recreate disposes the old form after installing the replacement;
// only the current top-level form owns application shutdown.
lock (this)
{
if (ReferenceEquals(sender, _form)) Application.Exit();
}
};

lock (this) { _form = form; }
form.Shown += (_, _) => PrintSize();
}

void PrintSize()
{
IntPtr h = _form.Handle;
if (GetWindowRect(h, out var r))
{
Console.WriteLine($"READY {Environment.ProcessId} {h.ToInt64()}");
Console.WriteLine($"SIZE {r.Right - r.Left} {r.Bottom - r.Top}");
// WGC excludes invisible resize borders. Keep this physical
// capture contract separate from the outer GetWindowRect value.
if (DwmGetWindowAttribute(h, 9, out var frame, Marshal.SizeOf<RECT>()) == 0)
Console.WriteLine($"CAPTURE_SIZE {frame.Right - frame.Left} {frame.Bottom - frame.Top}");
Console.Out.Flush();
}
}

void Recreate()
{
var old = _form;
old.Hide();
CreateForm();
_form.Show();
old.Dispose();
}

void ReplaceControl()
{
var old = _input;
var oldHandle = old.IsHandleCreated ? old.Handle : IntPtr.Zero;
var replacement = new TextBox
{
Name = "fixture-input-replacement",
Text = "replacement",
Width = old.Width,
Location = old.Location,
};
_form.Controls.Add(replacement);
_input = replacement;
old.Dispose();
Console.WriteLine($"CONTROL_REPLACED {oldHandle.ToInt64()} {replacement.Handle.ToInt64()}");
Console.Out.Flush();
}

void ShowCover()
{
if (_cover is not null && !_cover.IsDisposed) return;
var main = _form;
var b = main.Bounds;
b.Inflate(60, 60);
_cover = new Form
{
Text = "maka-cu-windows-fixture-cover",
StartPosition = FormStartPosition.Manual,
Bounds = b,
BackColor = Color.Red,
TopMost = true,
ShowInTaskbar = false,
};
_cover.Show(main);
_cover.BringToFront();
Console.WriteLine("COVERED");
Console.Out.Flush();
}

void HideCover()
{
if (_cover is null || _cover.IsDisposed) return;
_cover.Close();
_cover = null;
Console.WriteLine("UNCOVERED");
Console.Out.Flush();
}
}
43 changes: 43 additions & 0 deletions apps/OpenComputerUseWindows/native/MakaCuWindows.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<!-- net8.0-windows10.0.22621.0 pulls in the Windows SDK projection
(Windows.Graphics.Capture etc.). Plain net8.0-windows has none. -->
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
<RootNamespace>MakaCuWindows</RootNamespace>
<AssemblyName>maka-cu-windows</AssemblyName>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>false</InvariantGlobalization>
<!-- The dev loop is framework-dependent. Reproducible self-contained
publishing puts the managed payload in one file, while Windows
Desktop/WPF native companion DLLs remain beside that apphost. -->
</PropertyGroup>

<ItemGroup>
<!-- Provides System.Windows.Automation (UIAutomationClient) without pulling
in WinForms/WPF. Part of the Windows Desktop shared framework. -->
<FrameworkReference Include="Microsoft.WindowsDesktop.App" />
</ItemGroup>

</Project>
Loading