mirror of
https://github.com/vczh-libraries/Release.git
synced 2026-08-18 01:44:28 +08:00
Sync shared copilot guidance
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
# Building a Solution
|
||||
|
||||
- Go to `Windows Specific` section if you are on Windows.
|
||||
- Go to `Linux Specific` section if you are on Linux or macOS.
|
||||
- Go to `Linux Specific` section if you are on Linux.
|
||||
- Go to `macOS Specific` section if you are on macOS.
|
||||
|
||||
## Windows Specific
|
||||
|
||||
@@ -57,3 +58,7 @@ Call `REPO-ROOT/.github/Ubuntu/build.sh -f` for full rebuild.
|
||||
`build.sh` will also run other script files in that folder; you may need to run `chmod +x` if any script file is blocked.
|
||||
|
||||
Only the "debug x64" configuration is supported on Linux. If you are instructed to build and run other configuration, ignore it.
|
||||
|
||||
## macOS Specific
|
||||
|
||||
(to be editing...)
|
||||
|
||||
@@ -13,6 +13,10 @@ In general, here is my preference for any languages:
|
||||
- DRY requires finding if a feature has already been implemented somewhere else before implementing it. avoiding massive duplication.
|
||||
- If the existing implementation is not sharable, refactoring is preferred.
|
||||
|
||||
## C++ Thread Safety and Multi-Threading Synchronization
|
||||
|
||||
Check out [Coding_MultiThreading.md](./Coding_MultiThreading.md).
|
||||
|
||||
## C++ Coding Convention
|
||||
|
||||
- Although C++ does not require this but we want to have `extern` on all function forward declarations.
|
||||
@@ -85,28 +89,15 @@ When `VlppParser2` is available to the current project, complex parsers always r
|
||||
- Prefer the latest C++ features (up to C++ 20).
|
||||
- Prefer template variadic arguments, over hard-coded-counting solutions.
|
||||
|
||||
### for Thread Safe Programming
|
||||
|
||||
- Most of the code do not require thread safety, DO NOT over engineering.
|
||||
- `SpinLock` is only for protecting a piece of code or data in a super flashy short time.
|
||||
- When defining a `SpinLock` field, names it begins with `lock`, a `// covers a, b, c` comment is recommended to put above it, a empty line is recommended to put around it.
|
||||
- When doing `read-process-write` but the `write` part doesn't depend on the result of `process`:
|
||||
- The below rules only involve when `process` is heavy, if anything is simple, keep it simple.
|
||||
- You can copy or move the heavy structure in `SPIN_LOCK`, and `process` after `SPIN_LOCK`.
|
||||
- Especially for scenario when a container should be processed and cleared, `std::move` would be the best choice to copy and clean `Vlpp` containers. Using moved `Vlpp` containers is not undefined behavior.
|
||||
- When using other locks, try your best to only use methods that available to all platforms.
|
||||
- Write cross platform code when it is performance optimal for all platforms.
|
||||
- If Windows specific methods could make Windows implementation much better, then you are allowed to implement them in different ways.
|
||||
- Use `lock` (both `SpinLock` and `CriticalSection`), `mutex`, `semaphore`, `event`, `semaphore`, `rwlock`, `cv` as lock variable prefixes.
|
||||
- Prefer lock free construction only when the code would be simple, DO NOT involve complex lock free trick unless explicitly required.
|
||||
- `std::atomic<T>` should be considered and use it precisely. The code should be correct when running parallelly while I don't want unnecessary `std::atomic<T>`.
|
||||
- `atomic_vint` is widely used in the library, use it for `vint`.
|
||||
- Avoid polling at all cost, I strongly prefer scheduling in async way.
|
||||
|
||||
### for Reflectable Types
|
||||
|
||||
- Any interface or class `X` should inherit from `vl::reflection::Description<X>`.
|
||||
- If such a class (not including interface) should be inheritable in Workflow script, use `AggregatableDescription` instead of `Description`.
|
||||
- If a class inherits directly or indirectly from multiple registered classes/interfaces:
|
||||
- Either register this class.
|
||||
- Or if multiple registered base types are all interfaces, another valid option would be to create a registered interface inheriting all of them, and let the class inherits from this new interface.
|
||||
- The reason is that, an object only has one pointer to a piece of reflection metadata. If a class is not registered but it inherits from multiple registered types, only a metadata from one of these base types will be brought along with the actual
|
||||
object, causing missing of a complete picture.
|
||||
- No `const` is allowed for methods or reference types.
|
||||
- Prefer `IValue*` interfaces for container types on interfaces.
|
||||
- Container types and some other types support range-based for loop. Always prefer range-based for loop over other loops.
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# Coding Convention (Multi-Threading)
|
||||
|
||||
- Most of the code do not require thread safety, DO NOT over engineering.
|
||||
- `SpinLock` is only for protecting a piece of code or data in a super flashy short time.
|
||||
- When defining a `SpinLock` field, names it begins with `lock`, a `// covers a, b, c` comment is recommended to put above it, a empty line is recommended to put around it.
|
||||
- When doing `read-process-write` but the `write` part doesn't depend on the result of `process`:
|
||||
- The below rules only involve when `process` is heavy, if anything is simple, keep it simple.
|
||||
- You can copy or move the heavy structure in `SPIN_LOCK`, and `process` after `SPIN_LOCK`.
|
||||
- Especially for scenario when a container should be processed and cleared, `std::move` would be the best choice to copy and clean `Vlpp` containers. Using moved `Vlpp` containers is not undefined behavior.
|
||||
- When using other locks, try your best to only use methods that available to all platforms.
|
||||
- Write cross platform code when it is performance optimal for all platforms.
|
||||
- If Windows specific methods could make Windows implementation much better, then you are allowed to implement them in different ways.
|
||||
- Use `lock` (both `SpinLock` and `CriticalSection`), `mutex`, `semaphore`, `event`, `semaphore`, `rwlock`, `cv` as lock variable prefixes.
|
||||
- Prefer lock free construction only when the code would be simple, DO NOT involve complex lock free trick unless explicitly required.
|
||||
- `std::atomic<T>` should be considered and use it precisely. The code should be correct when running parallelly while I don't want unnecessary `std::atomic<T>`.
|
||||
- `atomic_vint` is widely used in the library, use it for `vint`.
|
||||
- Avoid polling at all cost, I strongly prefer scheduling in async way.
|
||||
|
||||
## Platform Independence
|
||||
|
||||
- Avoid using any platform specific methods.
|
||||
- `ThreadPoolLite::Stop` is an exception, if it is used in the application, this function is expected to call at the end of `main`.
|
||||
|
||||
## Spawning Threads
|
||||
|
||||
`vl::Thread` class is for heavy resource and time consuming work work:
|
||||
- Heave time consuming usually means the thread lasts for the lifecycle of the process.
|
||||
- Inherit from `Thread` and override `Run`.
|
||||
- Avoid using `CreateAndStart` as your best effort, and strictly avoid in unit test projects, as there are small memory leaks that could pollute the memory leaks detecting.
|
||||
|
||||
`vl::ThreadPoolLite` class is for medium time consuming work:
|
||||
- Medium time consuming usually means it is spawned to complete a task, and although the task takes time but it is expected to end.
|
||||
- Avoid using this class **too parallelly**, it has a limited threads running at the background, queuing too many tasks would cause traffic.
|
||||
|
||||
`RepeatingTaskExecutor` queues inputs to trigger the same task when outdated input is discardable:
|
||||
- Inherit from `RepeatingTaskExecutor` and override `Execute`.
|
||||
- Calling `SubmitTask` cause the task to begin:
|
||||
- If the previous task is running, the new task would wait, otherwise it starts immediately.
|
||||
- If the last submitted task did not start, it is discarded and replaced by the new one, aka the queue only stores one pending task.
|
||||
- One submission executes the task once, when there is no task running, the system resource will be released.
|
||||
- One example is editor auto completion with editing text as an input, if the user is typing too fast, outdated queued text is discardable because we only response to the last editor state.
|
||||
|
||||
`TaskQueue` queues multiple tasks to execute in a single thread:
|
||||
- `RunTaskQueue` blocks the thread forever until `QueueExitTask` is called. Before calling `QueueExitTask`, it blocks even when no task is running.
|
||||
- `QueueExitTask` does not cancel any pending `QueueTask`, all queued task will be executed.
|
||||
|
||||
`ThreadVariable<T>` can be used on global variables, different threads see different copy even when using the same `ThreadVariable<T>` global variables.
|
||||
@@ -1,7 +1,8 @@
|
||||
# Debugging a Project
|
||||
|
||||
- Go to `Windows Specific` section if you are on Windows.
|
||||
- Go to `Linux Specific` section if you are on Linux or macOS.
|
||||
- Go to `Linux Specific` section if you are on Linux.
|
||||
- Go to `macOS Specific` section if you are on macOS.
|
||||
|
||||
## Windows Specific
|
||||
|
||||
@@ -101,3 +102,7 @@ quit
|
||||
```
|
||||
|
||||
For non-interactive one-shot debugging, wrap `lldb` with `timeout` so it cannot block forever.
|
||||
|
||||
## macOS Specific
|
||||
|
||||
(to be editing...)
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
# Running a CLI Application Project
|
||||
|
||||
- Go to `Windows Specific` section if you are on Windows.
|
||||
- Go to `Linux Specific` section if you are on Linux or macOS.
|
||||
- Go to `Linux Specific` section if you are on Linux.
|
||||
- Go to `macOS Specific` section if you are on macOS.
|
||||
|
||||
## Windows Specific
|
||||
|
||||
- Only run `copilotExecute.ps1` to run a CLI application project.
|
||||
- DO NOT call executables or scripts yourself.
|
||||
- CLI applications are interactable, or could end up in dead loop or dead locks so DO NOT JUST wait for the process to exit.
|
||||
- When it is crashed, sometimes (but not always) a native dialog would show and block the process.
|
||||
- If you believe the processing is blocked or is running too long, you are going to check out `Running-ComputerUse.md` and deal with it.
|
||||
|
||||
### Executing copilotExecute.ps1
|
||||
|
||||
@@ -44,3 +48,7 @@ Compiled binary might have a bug causing it to trap in a dead loop. DO NOT just
|
||||
If this seems suspicious, you are recommended to kill the process and run it again with the debugger.
|
||||
|
||||
Only the "debug x64" configuration is supported on Linux. If you are instructed to build and run other configuration, ignore it.
|
||||
|
||||
## macOS Specific
|
||||
|
||||
(to be editing...)
|
||||
|
||||
@@ -0,0 +1,395 @@
|
||||
# Basic Computer Use
|
||||
|
||||
This instruction is important for both Unit Test, CLI and GacUI applications.
|
||||
MSVC compiled applications are inevitably (but not always) prompting a native dialog when crashed.
|
||||
GacUI applications could invoke native dialogs proactivately, even when `FakeDialogService` is used.
|
||||
This document describes how to handle native UI to unblock the debugging or testing process.
|
||||
|
||||
- Go to `Windows Specific` section if you are on Windows.
|
||||
- Go to `Linux Specific` section if you are on Linux.
|
||||
- Go to `macOS Specific` section if you are on macOS.
|
||||
|
||||
## Windows Specific
|
||||
|
||||
UI Automation must not be used as the fallback for native windows. It can fail outright when the screen is locked, and calling UIA while a modal native dialog is blocking the application can leave the agent waiting forever.
|
||||
|
||||
When an application stops responding, assume a native modal window could be blocking its UI thread. Do not keep polling the application-level automation endpoint forever. Inspect native windows from a separate PowerShell process by using Win32 APIs, or take screenshots and then use Win32 APIs to interact with the dialog.
|
||||
|
||||
The safest pattern is:
|
||||
|
||||
- Identify the target process.
|
||||
- Enumerate top-level windows and child windows with Win32.
|
||||
- Read class names, window text, control ids and rectangles.
|
||||
- Prefer direct control messages such as `BM_CLICK`, `WM_SETTEXT` and combo-box messages over mouse input.
|
||||
- If direct control messages are not enough, use screenshots and absolute window/control rectangles to decide where to click.
|
||||
- Always close or cancel the native dialog before returning to application-level automation.
|
||||
|
||||
Useful PowerShell helper:
|
||||
|
||||
```powershell
|
||||
$source = @'
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
public static class NativeUi
|
||||
{
|
||||
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
|
||||
public delegate bool EnumChildProc(IntPtr hWnd, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll")] public static extern bool EnumWindows(EnumWindowsProc callback, IntPtr lParam);
|
||||
[DllImport("user32.dll")] public static extern bool EnumChildWindows(IntPtr hWnd, EnumChildProc callback, IntPtr lParam);
|
||||
[DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr hWnd);
|
||||
[DllImport("user32.dll")] public static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int maxCount);
|
||||
[DllImport("user32.dll")] public static extern int GetClassName(IntPtr hWnd, StringBuilder text, int maxCount);
|
||||
[DllImport("user32.dll")] public static extern int GetDlgCtrlID(IntPtr hWnd);
|
||||
[DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
|
||||
[DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr hWnd, out RECT rect);
|
||||
[DllImport("user32.dll")] public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
|
||||
[DllImport("user32.dll", CharSet=CharSet.Unicode)] public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, string lParam);
|
||||
|
||||
public const uint BM_CLICK = 0x00F5;
|
||||
public const uint WM_CLOSE = 0x0010;
|
||||
public const uint WM_SETTEXT = 0x000C;
|
||||
public const uint CB_SELECTSTRING = 0x014D;
|
||||
public const uint CB_SETCURSEL = 0x014E;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct RECT
|
||||
{
|
||||
public int Left;
|
||||
public int Top;
|
||||
public int Right;
|
||||
public int Bottom;
|
||||
}
|
||||
}
|
||||
'@
|
||||
|
||||
Add-Type -TypeDefinition $source
|
||||
|
||||
function Get-WindowTextValue([IntPtr]$Handle) {
|
||||
$text = New-Object Text.StringBuilder 512
|
||||
[void][NativeUi]::GetWindowText($Handle, $text, $text.Capacity)
|
||||
$text.ToString()
|
||||
}
|
||||
|
||||
function Get-ClassNameValue([IntPtr]$Handle) {
|
||||
$text = New-Object Text.StringBuilder 256
|
||||
[void][NativeUi]::GetClassName($Handle, $text, $text.Capacity)
|
||||
$text.ToString()
|
||||
}
|
||||
|
||||
function Format-NativeWindow([IntPtr]$Handle, [string]$Prefix = "") {
|
||||
[uint32]$processId = 0
|
||||
[void][NativeUi]::GetWindowThreadProcessId($Handle, [ref]$processId)
|
||||
$rect = New-Object NativeUi+RECT
|
||||
[void][NativeUi]::GetWindowRect($Handle, [ref]$rect)
|
||||
"{0}{1:X8} pid={2} id={3} visible={4} class={5} rect=({6},{7},{8},{9}) text={10}" -f `
|
||||
$Prefix,
|
||||
$Handle.ToInt64(),
|
||||
$processId,
|
||||
[NativeUi]::GetDlgCtrlID($Handle),
|
||||
[NativeUi]::IsWindowVisible($Handle),
|
||||
(Get-ClassNameValue $Handle),
|
||||
$rect.Left,
|
||||
$rect.Top,
|
||||
$rect.Right,
|
||||
$rect.Bottom,
|
||||
(Get-WindowTextValue $Handle)
|
||||
}
|
||||
|
||||
function Show-NativeWindowsForProcess([int]$ProcessId) {
|
||||
[NativeUi]::EnumWindows({
|
||||
param($window, $lParam)
|
||||
[uint32]$ownerProcessId = 0
|
||||
[void][NativeUi]::GetWindowThreadProcessId($window, [ref]$ownerProcessId)
|
||||
if ($ownerProcessId -eq $ProcessId) {
|
||||
[Console]::WriteLine((Format-NativeWindow $window))
|
||||
[NativeUi]::EnumChildWindows($window, {
|
||||
param($child, $childParam)
|
||||
[Console]::WriteLine((Format-NativeWindow $child " "))
|
||||
return $true
|
||||
}, [IntPtr]::Zero) | Out-Null
|
||||
}
|
||||
return $true
|
||||
}, [IntPtr]::Zero) | Out-Null
|
||||
}
|
||||
```
|
||||
|
||||
If a visual check is needed, take a screenshot without UIA:
|
||||
|
||||
```powershell
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
|
||||
$bounds = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
|
||||
$bitmap = New-Object System.Drawing.Bitmap $bounds.Width, $bounds.Height
|
||||
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
|
||||
$graphics.CopyFromScreen($bounds.Location, [System.Drawing.Point]::Empty, $bounds.Size)
|
||||
$bitmap.Save("$env:TEMP\native-window.png", [System.Drawing.Imaging.ImageFormat]::Png)
|
||||
$graphics.Dispose()
|
||||
$bitmap.Dispose()
|
||||
```
|
||||
|
||||
When outputting from inside a delegate callback, use `[Console]::WriteLine(...)`. Plain PowerShell output inside the callback can be swallowed as the callback return value.
|
||||
|
||||
### Native Message Dialog
|
||||
|
||||
Message boxes are usually top-level dialogs with class `#32770`. Their text is usually in `Static` children, and buttons are `Button` children.
|
||||
|
||||
Steps:
|
||||
|
||||
- Call `Show-NativeWindowsForProcess <pid>`.
|
||||
- Find the visible `#32770` window.
|
||||
- Read the `Static` child text to understand the prompt.
|
||||
- Enumerate `Button` children and choose the intended response.
|
||||
- Click the button with `BM_CLICK`.
|
||||
|
||||
Standard button ids:
|
||||
|
||||
- `1`: `OK`
|
||||
- `2`: `Cancel`
|
||||
- `3`: `Abort`
|
||||
- `4`: `Retry`
|
||||
- `5`: `Ignore`
|
||||
- `6`: `Yes`
|
||||
- `7`: `No`
|
||||
|
||||
Example:
|
||||
|
||||
```powershell
|
||||
function Click-DialogButtonByText([int]$ProcessId, [string]$ButtonText) {
|
||||
$script:button = [IntPtr]::Zero
|
||||
|
||||
[NativeUi]::EnumWindows({
|
||||
param($window, $lParam)
|
||||
[uint32]$ownerProcessId = 0
|
||||
[void][NativeUi]::GetWindowThreadProcessId($window, [ref]$ownerProcessId)
|
||||
|
||||
if ($ownerProcessId -eq $ProcessId -and (Get-ClassNameValue $window) -eq "#32770") {
|
||||
[NativeUi]::EnumChildWindows($window, {
|
||||
param($child, $childParam)
|
||||
if ((Get-ClassNameValue $child) -eq "Button" -and (Get-WindowTextValue $child) -eq $ButtonText) {
|
||||
$script:button = $child
|
||||
return $false
|
||||
}
|
||||
return $true
|
||||
}, [IntPtr]::Zero) | Out-Null
|
||||
}
|
||||
|
||||
return ($script:button -eq [IntPtr]::Zero)
|
||||
}, [IntPtr]::Zero) | Out-Null
|
||||
|
||||
if ($script:button -eq [IntPtr]::Zero) {
|
||||
throw "Button not found: $ButtonText"
|
||||
}
|
||||
|
||||
[void][NativeUi]::SendMessage($script:button, [NativeUi]::BM_CLICK, [IntPtr]::Zero, [IntPtr]::Zero)
|
||||
}
|
||||
```
|
||||
|
||||
If a message box has multiple buttons, do not blindly click the first button. Read the prompt and button text. If the goal is only to unblock after collecting diagnostic information, prefer the least destructive option, usually `Cancel`, `No`, or the close button. If the dialog reports a crash or runtime error and offers diagnostic buttons, capture the text/screenshot first, then choose the action that closes the dialog without changing project files.
|
||||
|
||||
If no button can be found but the dialog must be dismissed, send `WM_CLOSE` to the dialog:
|
||||
|
||||
```powershell
|
||||
[void][NativeUi]::SendMessage($dialogHandle, [NativeUi]::WM_CLOSE, [IntPtr]::Zero, [IntPtr]::Zero)
|
||||
```
|
||||
|
||||
### Native Color Picking Dialog
|
||||
|
||||
The standard Windows color dialog is also a `#32770` dialog. Its common control ids are:
|
||||
|
||||
- `1`: `OK`
|
||||
- `2`: `Cancel`
|
||||
- `706`: red edit box
|
||||
- `707`: green edit box
|
||||
- `708`: blue edit box
|
||||
- `719`: `Define Custom Colors`
|
||||
- `712`: `Add to Custom Colors`
|
||||
|
||||
The color grid itself is usually owner-drawn or static, so UIA-style discovery is not helpful. The most reliable programmatic path is to set the RGB edit fields and click `OK`.
|
||||
|
||||
Example:
|
||||
|
||||
```powershell
|
||||
function Set-DialogEditTextById([IntPtr]$Dialog, [int]$Id, [string]$Text) {
|
||||
$script:edit = [IntPtr]::Zero
|
||||
|
||||
[NativeUi]::EnumChildWindows($Dialog, {
|
||||
param($child, $lParam)
|
||||
if ([NativeUi]::GetDlgCtrlID($child) -eq $Id -and (Get-ClassNameValue $child) -eq "Edit") {
|
||||
$script:edit = $child
|
||||
return $false
|
||||
}
|
||||
return $true
|
||||
}, [IntPtr]::Zero) | Out-Null
|
||||
|
||||
if ($script:edit -eq [IntPtr]::Zero) {
|
||||
throw "Edit control not found: $Id"
|
||||
}
|
||||
|
||||
[void][NativeUi]::SendMessage($script:edit, [NativeUi]::WM_SETTEXT, [IntPtr]::Zero, $Text)
|
||||
}
|
||||
|
||||
function Click-DialogButtonById([IntPtr]$Dialog, [int]$Id) {
|
||||
$script:button = [IntPtr]::Zero
|
||||
|
||||
[NativeUi]::EnumChildWindows($Dialog, {
|
||||
param($child, $lParam)
|
||||
if ([NativeUi]::GetDlgCtrlID($child) -eq $Id -and (Get-ClassNameValue $child) -eq "Button") {
|
||||
$script:button = $child
|
||||
return $false
|
||||
}
|
||||
return $true
|
||||
}, [IntPtr]::Zero) | Out-Null
|
||||
|
||||
if ($script:button -eq [IntPtr]::Zero) {
|
||||
throw "Button control not found: $Id"
|
||||
}
|
||||
|
||||
[void][NativeUi]::SendMessage($script:button, [NativeUi]::BM_CLICK, [IntPtr]::Zero, [IntPtr]::Zero)
|
||||
}
|
||||
|
||||
# Example: set RGB to red, then accept.
|
||||
Set-DialogEditTextById $dialogHandle 706 "255"
|
||||
Set-DialogEditTextById $dialogHandle 707 "0"
|
||||
Set-DialogEditTextById $dialogHandle 708 "0"
|
||||
Click-DialogButtonById $dialogHandle 1
|
||||
```
|
||||
|
||||
To cancel, click button id `2`. After accepting a color, return to the application and remove selection/highlight if necessary before visually verifying the applied text color; selected text can hide the actual color.
|
||||
|
||||
If the RGB edits are not visible until custom colors are expanded, click id `719` first. If a specific palette color must be selected and no edit fields are usable, take a screenshot, use the `Static` color-grid rectangle from `GetWindowRect`, and click the palette cell by coordinate.
|
||||
|
||||
### Native Font Picking Dialog
|
||||
|
||||
The standard Windows font dialog is a `#32770` dialog. Its common control ids are:
|
||||
|
||||
- `1`: `OK`
|
||||
- `2`: `Cancel`
|
||||
- `1136`: font combo box
|
||||
- `1137`: font style combo box
|
||||
- `1138`: size combo box
|
||||
- `1139`: color combo box, when effects/color are enabled
|
||||
- `1140`: script combo box
|
||||
- `1040`: strikeout checkbox, when effects are enabled
|
||||
- `1041`: underline checkbox, when effects are enabled
|
||||
|
||||
Use combo-box messages to choose values. This works when the screen is locked because it talks to the HWND directly.
|
||||
|
||||
Example:
|
||||
|
||||
```powershell
|
||||
function Select-DialogComboTextById([IntPtr]$Dialog, [int]$Id, [string]$Text) {
|
||||
$script:combo = [IntPtr]::Zero
|
||||
|
||||
[NativeUi]::EnumChildWindows($Dialog, {
|
||||
param($child, $lParam)
|
||||
if ([NativeUi]::GetDlgCtrlID($child) -eq $Id -and (Get-ClassNameValue $child) -like "ComboBox*") {
|
||||
$script:combo = $child
|
||||
return $false
|
||||
}
|
||||
return $true
|
||||
}, [IntPtr]::Zero) | Out-Null
|
||||
|
||||
if ($script:combo -eq [IntPtr]::Zero) {
|
||||
throw "Combo box not found: $Id"
|
||||
}
|
||||
|
||||
$result = [NativeUi]::SendMessage($script:combo, [NativeUi]::CB_SELECTSTRING, [IntPtr](-1), $Text)
|
||||
if ($result.ToInt64() -lt 0) {
|
||||
throw "Combo item not found: $Text"
|
||||
}
|
||||
}
|
||||
|
||||
# Example.
|
||||
Select-DialogComboTextById $dialogHandle 1136 "Arial"
|
||||
Select-DialogComboTextById $dialogHandle 1137 "Bold Italic"
|
||||
Select-DialogComboTextById $dialogHandle 1138 "20"
|
||||
Click-DialogButtonById $dialogHandle 1
|
||||
```
|
||||
|
||||
For checkboxes such as underline or strikeout, enumerate child `Button` controls, confirm their text and id, and use `BM_CLICK` to toggle. If the dialog exposes a color combo, use id `1139` with the same combo helper.
|
||||
|
||||
To cancel, click button id `2`. After accepting a font, verify the resulting application state through the application itself, screenshots, or exposed rendering state. Some applications intentionally read only a subset of the font dialog result, so verify the specific properties that the application is expected to apply.
|
||||
|
||||
### Native File Picking Dialog
|
||||
|
||||
Native file dialogs come in two major shapes:
|
||||
|
||||
- Older common dialogs expose direct child controls such as `Edit`, `ComboBox`, `SysListView32`, and `Button`.
|
||||
- Newer Explorer-style dialogs may contain nested `DirectUIHWND`/`SHELLDLL_DefView` controls where the file list is not easy to operate by messages.
|
||||
|
||||
For both shapes, the most reliable path is to type the absolute file path into the filename edit and click `Open`/`Save`.
|
||||
|
||||
Common old-style open dialog ids:
|
||||
|
||||
- `1`: `Open` or `Save`
|
||||
- `2`: `Cancel`
|
||||
- `1148`: filename edit or combo/edit child
|
||||
- `1136`: file type combo box
|
||||
- `1137`: current folder combo box
|
||||
- `1121`: shell view
|
||||
|
||||
Example:
|
||||
|
||||
```powershell
|
||||
function Set-FileDialogName([IntPtr]$Dialog, [string]$Path) {
|
||||
$script:edit = [IntPtr]::Zero
|
||||
|
||||
[NativeUi]::EnumChildWindows($Dialog, {
|
||||
param($child, $lParam)
|
||||
if ([NativeUi]::GetDlgCtrlID($child) -eq 1148 -and (Get-ClassNameValue $child) -eq "Edit") {
|
||||
$script:edit = $child
|
||||
return $false
|
||||
}
|
||||
return $true
|
||||
}, [IntPtr]::Zero) | Out-Null
|
||||
|
||||
if ($script:edit -eq [IntPtr]::Zero) {
|
||||
throw "File name edit control not found."
|
||||
}
|
||||
|
||||
[void][NativeUi]::SendMessage($script:edit, [NativeUi]::WM_SETTEXT, [IntPtr]::Zero, $Path)
|
||||
}
|
||||
|
||||
Set-FileDialogName $dialogHandle "C:\path\to\file.png"
|
||||
Click-DialogButtonById $dialogHandle 1
|
||||
```
|
||||
|
||||
To cancel, click button id `2`.
|
||||
|
||||
For folder navigation:
|
||||
|
||||
- Prefer typing a full absolute path in the filename field and clicking `Open`.
|
||||
- To move to a folder without selecting a file, type the folder path in the filename field and click `Open`.
|
||||
- To select a visible file by clicking, enumerate the shell view/list control and use screenshots/rectangles to calculate the file row/cell, then click it. Use this only when full-path entry is not accepted.
|
||||
- If the dialog uses an address bar and the filename edit is unavailable, use keyboard input only after confirming focus with screenshots. Prefer `Alt+D`, type the folder path, press `Enter`, then type/select the file.
|
||||
|
||||
After accepting a file, verify the application state through the application-level automation, log output, or screenshot. If the app remains blocked, enumerate windows again; an error prompt may have appeared because the file does not exist, the extension is filtered out, or the application rejected the file.
|
||||
|
||||
### Other/General Native Windows
|
||||
|
||||
Not every native modal window is a standard dialog. Runtime-library errors, crash dialogs, debugger prompts and custom native windows may use different classes, child structures, or owner-drawn controls. The same rules still apply:
|
||||
|
||||
- Enumerate top-level windows for the target process.
|
||||
- Inspect visible windows first, then hidden windows if the app is still blocked.
|
||||
- Record the top-level window class, title, process id, rectangle and all child controls.
|
||||
- Read all `Static`, `Edit`, `RichEdit`, `Button`, `SysLink`, `ComboBox`, `ListBox`, `SysListView32` and `ToolbarWindow32` children.
|
||||
- Take a screenshot if text or buttons are owner-drawn.
|
||||
- Prefer explicit button clicks by id or text.
|
||||
- Use `WM_CLOSE` only after capturing useful text/screenshots and only when choosing a visible button is impossible or unsafe.
|
||||
- If the target process shows no windows but remains blocked, check for child/helper processes, debugger processes, crash-reporting processes, or windows owned by a different process id.
|
||||
|
||||
Keep the native-dialog helper separate from the application under test. A modal dialog blocks the application's UI thread, so any app-level automation command that requires the UI thread can hang or time out until the native dialog is handled.
|
||||
|
||||
Do not panic or wait indefinitely. Once a native window is suspected, switch to Win32 enumeration and close the blocking dialog deterministically.
|
||||
|
||||
## Linux Specific
|
||||
|
||||
(to be editing...)
|
||||
|
||||
## macOS Specific
|
||||
|
||||
(to be editing...)
|
||||
@@ -1,12 +1,17 @@
|
||||
# Running a GacUI Application Project
|
||||
|
||||
- Go to `Windows Specific` section if you are on Windows.
|
||||
- Go to `Linux Specific` section if you are on Linux or macOS.
|
||||
- Go to `Linux Specific` section if you are on Linux.
|
||||
- Go to `macOS Specific` section if you are on macOS.
|
||||
|
||||
## Windows Specific
|
||||
|
||||
You are strongly recommended to attach a debugger when running any GacUI application.
|
||||
Because some runtime exceptions are silently consumed by Windows causing the application not to crash, covering issues if no debugger is attached.
|
||||
- You are strongly recommended to attach a debugger when running any GacUI application.
|
||||
- Because some runtime exceptions are silently consumed by Windows causing the application not to crash, covering issues if no debugger is attached.
|
||||
- GacUI applications could end up in dead loop or dead locks, so DO NOT JUST wait for the process to exit.
|
||||
- When it is crashed, sometimes (but not always) a native dialog would show and block the process.
|
||||
- Native dialogs could be proactivately called from a GacUI application, even when `FakeDialogService` is not used.
|
||||
- If you believe the processing is blocked or is running too long, you are going to check out `Running-ComputerUse.md` and deal with it.
|
||||
|
||||
### Automation Service via HTTP
|
||||
|
||||
@@ -40,6 +45,12 @@ When remote protocol is in use:
|
||||
|
||||
GacUI does not support UI Automation so far, but this situation will be changed very soon.
|
||||
|
||||
UI Automation does not work when the screen is locked. Calling any UIA tools in this case will just fail.
|
||||
|
||||
## Linux Specific
|
||||
|
||||
NOT SUPPORTED
|
||||
(to be editing...)
|
||||
|
||||
## macOS Specific
|
||||
|
||||
(to be editing...)
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
# Running a Unit Test Project
|
||||
|
||||
- Go to `Windows Specific` section if you are on Windows.
|
||||
- Go to `Linux Specific` section if you are on Linux or macOS.
|
||||
- Go to `Linux Specific` section if you are on Linux.
|
||||
- Go to `macOS Specific` section if you are on macOS.
|
||||
|
||||
## Windows Specific
|
||||
|
||||
- Only run `copilotExecute.ps1` to run a unit test project.
|
||||
- DO NOT call executables or scripts yourself.
|
||||
- Unit Test applications could end up in dead loop or dead locks, so DO NOT JUST wait for the process to exit.
|
||||
- When it is crashed, sometimes (but not always) a native dialog would show and block the process.
|
||||
- If you believe the processing is blocked or is running too long, you are going to check out `Running-ComputerUse.md` and deal with it.
|
||||
|
||||
### Executing copilotExecute.ps1
|
||||
|
||||
@@ -94,3 +98,7 @@ If this seems suspicious, you are recommended to kill the process and run it aga
|
||||
When `/D` or `/C` is specified, the unit test binary stops at the first failure, causing it to be unable to summarize how many test cases pass or fail at the end. This is an obvious signal that it fails.
|
||||
|
||||
Only the "debug x64" configuration is supported on Linux. If you are instructed to build and run other configuration, ignore it.
|
||||
|
||||
## macOS Specific
|
||||
|
||||
(to be editing...)
|
||||
|
||||
@@ -55,3 +55,7 @@ In `vmake`, these variables are available for configuration, most of them are op
|
||||
- `CPP_COMPILE_OPTIONS`: Extra compiler options for `clang++` or `g++`.
|
||||
- `FOLDERS`: Folders that contain generated files. These folders will be completely removed during a full build.
|
||||
- `CPP_TARGET`: The compiled binary.
|
||||
|
||||
## Working on Mac
|
||||
|
||||
(to be editing...)
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
- `collections::List` has deleted copy constructor; use `std::move()` for structs with `List` members [1]
|
||||
- Compare type descriptors by pointer when descriptor identity is available [1]
|
||||
- Parse and validate before queuing asynchronous work [1]
|
||||
- Use Win32 messages for native dialogs when UIA is unavailable [1]
|
||||
|
||||
# Refinements
|
||||
|
||||
@@ -231,6 +232,10 @@ At script-visible reflection boundaries, translate recoverable collection operat
|
||||
|
||||
When an API needs to report syntax or validation errors synchronously but execute accepted work asynchronously, split those phases explicitly. Parse and validate the request on the caller/transport path, return errors immediately, then queue only a parsed command object for main-thread or background execution. This keeps modal or blocking work off the response path without hiding malformed input behind an async boundary.
|
||||
|
||||
## Use Win32 messages for native dialogs when UIA is unavailable
|
||||
|
||||
When native Windows dialogs must be handled under conditions where UI Automation is unreliable or unavailable, use screenshots and Win32 enumeration/messages from a separate process. Enumerate top-level and child windows, read text/class/control ids, then use standard messages such as `BM_CLICK`, `WM_SETTEXT`, and combo-box selection messages to confirm, cancel, or fill controls. Do not wait indefinitely on the application under test while a native modal dialog is open; that app may be blocked until the external Win32 operation closes the dialog.
|
||||
|
||||
## Proactively remove code made redundant by refactoring
|
||||
|
||||
When a change makes a construction unnecessary or no longer meaningful, delete it as part of the same change instead of leaving it behind. This includes redirection/adapter layers that only forward, transport methods that exist solely for a now-collapsed path, specialized helpers superseded by a generic one, duplicated post-processing a callee already performs, and null checks the callee already handles (e.g. when `BoxValue`/`UnboxValue` already accept null, or when an `Invoke*` helper already reads/checks the result). Do not leave redirections that exist only because of history. It is acceptable to take the risk of breaking tests while removing redundant code, and then fix the tests afterward, rather than preserving dead structure to keep tests green.
|
||||
|
||||
@@ -39,6 +39,7 @@ otherwise it won't work properly.
|
||||
|
||||
- **SUPER IMPORTANT** Your should always follow the coding convention when coding:
|
||||
- `REPO-ROOT/.github/Guidelines/Coding.md`
|
||||
- `REPO-ROOT/.github/Guidelines/Coding_MultiThreading.md`
|
||||
- `REPO-ROOT/.github/KnowledgeBase/Learning.md`
|
||||
- `REPO-ROOT/.github/Learning/Learning_Coding.md`
|
||||
- `REPO-ROOT/.github/Learning/Learning_Testing.md`
|
||||
@@ -48,6 +49,11 @@ otherwise it won't work properly.
|
||||
- Unit Test: `REPO-ROOT/.github/Guidelines/Running-UnitTest.md`
|
||||
- CLI Application: `REPO-ROOT/.github/Guidelines/Running-CLI.md`
|
||||
- GacUI Application: `REPO-ROOT/.github/Guidelines/Running-GacUI.md`
|
||||
- Basic Computer Use: `REPO-ROOT/.github/Guidelines/Running-ComputerUse.md`
|
||||
- This instruction is important for both Unit Test, CLI and GacUI applications.
|
||||
- MSVC compiled applications are inevitably (but not always) prompting a native dialog when crashed.
|
||||
- GacUI applications could invoke native dialogs proactivately, even when `FakeDialogService` is used.
|
||||
- This document describes how to handle native UI to unblock the debugging or testing process.
|
||||
- Debugging a Project: `REPO-ROOT/.github/Guidelines/Debugging.md`
|
||||
- Using Unit Test Framework: `REPO-ROOT/.github/KnowledgeBase/manual/unittest/vlpp.md`
|
||||
- Using Unit Test Framework for GacUI Application:
|
||||
|
||||
Reference in New Issue
Block a user