Skip to content
Merged
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
189 changes: 166 additions & 23 deletions Tools/windows/uia-live-gate.ps1

Large diffs are not rendered by default.

20 changes: 19 additions & 1 deletion graphcode-windows/src/AccessibilityProvider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,16 @@ class Node final : public IRawElementProviderSimple,
property == UIA_IsEnabledPropertyId ||
property == UIA_IsControlElementPropertyId ||
property == UIA_IsContentElementPropertyId) {
bool_value = true;
if (isRowKey(id_)) {
const Row &row = state_->rows.at(id_);
const bool sidebar_error_footer =
row.identity.rfind("sidebar-error-footer:", 0) == 0;
bool_value = sidebar_error_footer
? (property != UIA_IsKeyboardFocusablePropertyId)
: true;
} else {
bool_value = true;
}
kind = kBool;
} else if (property == UIA_HasKeyboardFocusPropertyId) {
bool_value = state_->focused == id_;
Expand Down Expand Up @@ -775,9 +784,12 @@ class Node final : public IRawElementProviderSimple,
row.identity.rfind("sidebar-section:", 0) == 0 ? L"sidebar-section-" :
row.identity.rfind("needs-you-header:", 0) == 0 ? L"needs-you-header-" :
row.identity.rfind("needs-you-row:", 0) == 0 ? L"needs-you-row-" :
row.identity.rfind("needs-you-stop:", 0) == 0 ? L"needs-you-stop-" :
row.identity.rfind("activity-header:", 0) == 0 ? L"activity-header-" :
row.identity.rfind("activity-filter:", 0) == 0 ? L"activity-filter-" :
row.identity.rfind("activity-row:", 0) == 0 ? L"activity-row-" :
row.identity.rfind("activity-control:", 0) == 0 ? L"activity-control-" :
row.identity.rfind("open-project:", 0) == 0 ? L"open-project-" :
row.identity.rfind("project-new-loop:", 0) == 0 ? L"project-new-loop-" :
row.identity.rfind("project-disclosure:", 0) == 0 ? L"project-disclosure-" :
row.identity.rfind("quick-chats-header:", 0) == 0 ? L"quick-chats-header-" :
Expand All @@ -795,6 +807,7 @@ class Node final : public IRawElementProviderSimple,
row.identity.rfind("workspace-new-tab:", 0) == 0 ? L"workspace-new-tab-" :
row.identity.rfind("workspace-split-right:", 0) == 0 ? L"workspace-split-right-" :
row.identity.rfind("workspace-split-down:", 0) == 0 ? L"workspace-split-down-" :
row.identity.rfind("sidebar-error-footer:", 0) == 0 ? L"sidebar-error-footer-" :
parent == 1 ? L"project-row-" :
parent == 2 ? L"loop-row-" :
parent == 3 ? L"worktree-row-" : L"canvas-card-";
Expand All @@ -814,10 +827,15 @@ class Node final : public IRawElementProviderSimple,
if (id_ >= 1 && id_ <= 3) return UIA_ListControlTypeId;
if (isRowKey(id_)) {
const Row &row = state_->rows.at(id_);
if (row.identity.rfind("sidebar-error-footer:", 0) == 0) {
return UIA_TextControlTypeId;
}
const bool action =
row.identity.rfind("sidebar-section:", 0) == 0 ||
row.identity.rfind("needs-you-header:", 0) == 0 ||
row.identity.rfind("needs-you-stop:", 0) == 0 ||
row.identity.rfind("activity-header:", 0) == 0 ||
row.identity.rfind("activity-filter:", 0) == 0 ||
row.identity.rfind("activity-control:", 0) == 0 ||
row.identity.rfind("project-new-loop:", 0) == 0 ||
row.identity.rfind("project-disclosure:", 0) == 0 ||
Expand Down
406 changes: 382 additions & 24 deletions graphcode-windows/src/App.zig

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions graphcode-windows/src/DaemonClient.zig
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,18 @@ pub const DaemonClient = struct {
self.sendCommand(command);
}

pub fn sendSidebarRootOrder(
self: *DaemonClient,
project_path: []const u8,
node_ids: []const []const u8,
) void {
const command = Wire.commandGraphSidebarNodesReordered(self.allocator, project_path, node_ids) catch {
self.publishState(self.connectionState(), "sidebar root order command encoding failed");
return;
};
self.sendCommand(command);
}

pub fn sendRenameNode(self: *DaemonClient, project_path: []const u8, node_id: []const u8, title: []const u8) void {
const command = Wire.commandGraphRenameNode(self.allocator, project_path, node_id, title) catch {
self.publishState(self.connectionState(), "rename node command encoding failed");
Expand Down Expand Up @@ -573,13 +585,23 @@ pub const DaemonClient = struct {
};
self.allocator.free(command_json);
}
self.recordUiaCommand(addressed);
_ = self.sendCommandInternal(addressed, null);
}

fn sendCommandWithRequestID(self: *DaemonClient, command_json: []u8, request_id: [36]u8) bool {
self.recordUiaCommand(command_json);
return self.sendCommandInternal(command_json, request_id);
}

fn recordUiaCommand(self: *DaemonClient, command_json: []const u8) void {
const log_path = std.process.getEnvVarOwned(self.allocator, "GRAPHCODE_UIA_DAEMON_COMMAND_LOG") catch return;
defer self.allocator.free(log_path);
var file = std.fs.createFileAbsolute(log_path, .{ .truncate = true }) catch return;
defer file.close();
file.writeAll(command_json) catch {};
}

fn sendCommandInternal(self: *DaemonClient, command_json: []u8, request_id: ?[36]u8) bool {
self.mutex.lock();
if (self.stop_worker or self.outbound_count == outbound_capacity) {
Expand Down
60 changes: 56 additions & 4 deletions graphcode-windows/src/MainWindow.zig
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ pub const Command = enum(u16) {

pub const empty_open_folder_id: usize = 4601;
pub const empty_new_loop_id: usize = 4602;
pub const recent_folder_command_base: usize = 4700;
pub const recent_folder_command_limit: usize = 4799;

pub const RecentFolderItem = struct {
path: []const u8,
name: []const u8,
};

pub const MenuState = struct {
has_project: bool,
Expand All @@ -66,6 +73,7 @@ pub const MenuState = struct {
workspace_visible: bool,
activity_visible: bool,
update_checking: bool,
recent_folders: []const RecentFolderItem = &.{},
};

pub fn commandFromId(id: usize) ?Command {
Expand Down Expand Up @@ -166,14 +174,19 @@ pub fn restoreExistingInstance() void {
pub fn installMenu(hwnd: c.HWND) !void {
const menu = c.CreateMenu() orelse return error.MenuCreationFailed;
const file = c.CreatePopupMenu() orelse return error.MenuCreationFailed;
const add_folder = c.CreatePopupMenu() orelse return error.MenuCreationFailed;
const recent_folders = c.CreatePopupMenu() orelse return error.MenuCreationFailed;
const loop = c.CreatePopupMenu() orelse return error.MenuCreationFailed;
const terminal = c.CreatePopupMenu() orelse return error.MenuCreationFailed;
const view = c.CreatePopupMenu() orelse return error.MenuCreationFailed;
const help = c.CreatePopupMenu() orelse return error.MenuCreationFailed;

append(file, "Open Folder...\tCtrl+O", @intFromEnum(Command.open_folder));
append(file, "Clone Repository...\tCtrl+Shift+C", @intFromEnum(Command.clone_repository));
append(file, "Add Remote Repository...\tCtrl+Shift+R", @intFromEnum(Command.remote_repository));
append(add_folder, "Open Folder...\tCtrl+O", @intFromEnum(Command.open_folder));
append(add_folder, "Clone Repository...\tCtrl+Shift+C", @intFromEnum(Command.clone_repository));
append(add_folder, "Add Remote Repository...\tCtrl+Shift+R", @intFromEnum(Command.remote_repository));
separator(add_folder);
appendPopup(add_folder, "Recent Folders", recent_folders);
appendPopup(file, "Add Folder", add_folder);
separator(file);
append(file, "New Quick Chat\tCtrl+Q", @intFromEnum(Command.new_quick_chat));
append(file, "Open Global Overview", @intFromEnum(Command.open_global_overview));
Expand Down Expand Up @@ -236,6 +249,7 @@ pub fn installMenu(hwnd: c.HWND) !void {
}

pub fn updateMenu(hwnd: c.HWND, state: MenuState) void {
updateRecentFolderMenu(hwnd, state.recent_folders);
setEnabled(hwnd, .open_global_overview, true);
setEnabled(hwnd, .worktrees, state.can_worktrees);
setEnabled(hwnd, .reclaim_worktrees, state.can_worktrees);
Expand Down Expand Up @@ -268,6 +282,32 @@ pub fn updateMenu(hwnd: c.HWND, state: MenuState) void {
_ = c.DrawMenuBar(hwnd);
}

pub fn isRecentFolderCommand(id: usize) bool {
return id >= recent_folder_command_base and id <= recent_folder_command_limit;
}

fn updateRecentFolderMenu(hwnd: c.HWND, recent_folders: []const RecentFolderItem) void {
const root = c.GetMenu(hwnd);
if (root == null) return;
const file = c.GetSubMenu(root, 0);
if (file == null) return;
const add_folder = c.GetSubMenu(file, 0);
if (add_folder == null) return;
const recent = c.GetSubMenu(add_folder, 4);
if (recent == null) return;
var count = c.GetMenuItemCount(recent);
while (count > 0) : (count -= 1) {
_ = c.DeleteMenu(recent, @intCast(count - 1), c.MF_BYPOSITION);
}
if (recent_folders.len == 0) {
appendEnabled(recent, "No recent folders", recent_folder_command_base, false);
return;
}
for (recent_folders[0..@min(recent_folders.len, recent_folder_command_limit - recent_folder_command_base + 1)], 0..) |project, index| {
append(recent, project.name, recent_folder_command_base + index);
}
}

fn setEnabled(hwnd: c.HWND, command: Command, enabled: bool) void {
const flags: c.UINT = @intCast(@as(i32, c.MF_BYCOMMAND) |
if (enabled) @as(i32, c.MF_ENABLED) else @as(i32, c.MF_GRAYED));
Expand All @@ -281,9 +321,15 @@ fn setChecked(hwnd: c.HWND, command: Command, checked: bool) void {
}

fn append(menu: c.HMENU, text: []const u8, id: usize) void {
appendEnabled(menu, text, id, true);
}

fn appendEnabled(menu: c.HMENU, text: []const u8, id: usize, enabled: bool) void {
const wide = toWideZ(std.heap.c_allocator, text) catch return;
defer std.heap.c_allocator.free(wide);
_ = c.AppendMenuW(menu, c.MF_STRING, id, wide.ptr);
var flags: c.UINT = c.MF_STRING;
if (!enabled) flags |= c.MF_GRAYED;
_ = c.AppendMenuW(menu, flags, id, wide.ptr);
}

fn appendPopup(menu: c.HMENU, text: []const u8, popup: c.HMENU) void {
Expand Down Expand Up @@ -332,6 +378,12 @@ test "native menu exposes the parity command groups" {
try std.testing.expectEqual(@as(?Command, null), commandFromId(9999));
}

test "recent folder commands use a dedicated command range" {
try std.testing.expect(isRecentFolderCommand(recent_folder_command_base));
try std.testing.expect(isRecentFolderCommand(recent_folder_command_limit));
try std.testing.expect(!isRecentFolderCommand(recent_folder_command_limit + 1));
}

test "native menu labels are NUL terminated UTF-16" {
const wide = try toWideZ(std.testing.allocator, "Clone Repository…");
defer std.testing.allocator.free(wide);
Expand Down
Loading
Loading