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
2 changes: 1 addition & 1 deletion src/MauiDevFlow.Agent.Core/AgentHttpServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ private async Task HandleClientAsync(TcpClient client, CancellationToken ct)
await WriteResponseAsync(stream, response, ct).ConfigureAwait(false);
}
}
catch { /* swallow individual request errors */ }
catch (Exception ex) { Console.WriteLine($"[MauiDevFlow.Agent] Request error: {ex.GetType().Name}: {ex.Message}"); }
}

private async Task<HttpRequest?> ReadRequestAsync(NetworkStream stream, CancellationToken ct)
Expand Down
6 changes: 4 additions & 2 deletions src/MauiDevFlow.Agent.Core/DevFlowAgentService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ private void RegisterRoutes()
private Task<HttpResponse> HandleStatus(HttpRequest request)
{
var window = _app?.Windows.FirstOrDefault();
var w = window?.Width ?? 0;
var h = window?.Height ?? 0;
return Task.FromResult(HttpResponse.Json(new
{
agent = "MauiDevFlow.Agent",
Expand All @@ -135,8 +137,8 @@ private Task<HttpResponse> HandleStatus(HttpRequest request)
appName = _app?.GetType().Assembly.GetName().Name ?? "unknown",
running = _app != null,
cdpReady = CdpReadyCheck?.Invoke() ?? false,
windowWidth = window?.Width ?? 0,
windowHeight = window?.Height ?? 0
windowWidth = double.IsFinite(w) ? w : 0,
windowHeight = double.IsFinite(h) ? h : 0
}));
}

Expand Down
10 changes: 5 additions & 5 deletions src/MauiDevFlow.Agent.Core/VisualTreeWalker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -253,13 +253,13 @@ private ElementInfo CreateElementInfo(IVisualTreeElement element, string id, str
info.IsVisible = ve.IsVisible;
info.IsEnabled = ve.IsEnabled;
info.IsFocused = ve.IsFocused;
info.Opacity = ve.Opacity;
info.Opacity = double.IsFinite(ve.Opacity) ? ve.Opacity : 1;
info.Bounds = new BoundsInfo
{
X = ve.Frame.X,
Y = ve.Frame.Y,
Width = ve.Frame.Width,
Height = ve.Frame.Height
X = double.IsFinite(ve.Frame.X) ? ve.Frame.X : 0,
Y = double.IsFinite(ve.Frame.Y) ? ve.Frame.Y : 0,
Width = double.IsFinite(ve.Frame.Width) ? ve.Frame.Width : 0,
Height = double.IsFinite(ve.Frame.Height) ? ve.Frame.Height : 0
};

// Populate native view info from handler
Expand Down
55 changes: 46 additions & 9 deletions src/MauiDevFlow.Agent.Gtk/GtkAgentServiceExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,48 @@ public static MauiAppBuilder AddMauiDevFlowAgent(this MauiAppBuilder builder, Ac
var options = new AgentOptions();
configure?.Invoke(options);

// Check AssemblyMetadata for port override
// Read project identity from assembly metadata (injected by .targets)
var project = ReadAssemblyMetadata("MauiDevFlowProject") ?? "unknown";
var tfm = ReadAssemblyMetadata("MauiDevFlowTfm") ?? "unknown";

// Try broker for port assignment first
BrokerRegistration? brokerReg = null;
if (options.Port == AgentOptions.DefaultPort)
{
try
{
var platform = "Linux";
var appName = System.Reflection.Assembly.GetEntryAssembly()?.GetName().Name ?? "unknown";
brokerReg = new BrokerRegistration(project, tfm, platform, appName);
var assignedPort = Task.Run(() => brokerReg.TryRegisterAsync(TimeSpan.FromSeconds(5))).GetAwaiter().GetResult();
if (assignedPort.HasValue)
{
options.Port = assignedPort.Value;
Console.WriteLine($"[MauiDevFlow] Broker assigned port {assignedPort.Value}");
}
}
catch (Exception ex)
{
Console.WriteLine($"[MauiDevFlow] Broker registration failed: {ex.Message}");
brokerReg?.Dispose();
brokerReg = null;
}
}

// Fall back to assembly metadata port if broker didn't assign one
if (brokerReg?.AssignedPort == null)
{
var metaPort = ReadAssemblyMetadataPort();
if (metaPort.HasValue)
options.Port = metaPort.Value;
}

var service = new GtkAgentService(options);
if (brokerReg != null)
{
brokerReg.CurrentPort = options.Port;
service.SetBrokerRegistration(brokerReg);
}
builder.Services.AddSingleton<DevFlowAgentService>(service);

if (options.EnableFileLogging)
Expand All @@ -50,7 +83,6 @@ public static MauiAppBuilder AddMauiDevFlowAgent(this MauiAppBuilder builder, Ac
/// </summary>
public static void StartDevFlowAgent(this Application app)
{
// Resolve the service from the app's handler service provider
var service = GetAgentService(app);
if (service != null)
{
Expand All @@ -70,23 +102,28 @@ public static void StartDevFlowAgent(this Application app)
}
}

private static int? ReadAssemblyMetadataPort()
private static string? ReadAssemblyMetadata(string key)
{
try
{
var attrs = System.Reflection.Assembly.GetEntryAssembly()?
.GetCustomAttributes(typeof(System.Reflection.AssemblyMetadataAttribute), false);

if (attrs != null)
var entry = System.Reflection.Assembly.GetEntryAssembly();
if (entry != null)
{
var attrs = entry.GetCustomAttributes(typeof(System.Reflection.AssemblyMetadataAttribute), false);
foreach (System.Reflection.AssemblyMetadataAttribute attr in attrs)
{
if (attr.Key == "MauiDevFlowPort" && int.TryParse(attr.Value, out var port))
return port;
if (attr.Key == key)
return attr.Value;
}
}
}
catch { }
return null;
}

private static int? ReadAssemblyMetadataPort()
{
var value = ReadAssemblyMetadata("MauiDevFlowPort");
return value != null && int.TryParse(value, out var port) ? port : null;
}
}
113 changes: 64 additions & 49 deletions src/MauiDevFlow.Blazor.Gtk/GtkBlazorDevFlowExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public static class GtkBlazorDevFlowExtensions
/// <summary>
/// Adds MauiDevFlow Blazor WebView debugging tools for WebKitGTK.
/// Enables Chrome DevTools Protocol (CDP) access to BlazorWebView content on Linux.
/// Automatically wires to the Agent and captures the WebView when a BlazorWebView is rendered.
/// </summary>
public static MauiAppBuilder AddMauiBlazorDevFlowTools(this MauiAppBuilder builder, bool enableLogging = false)
{
Expand All @@ -19,74 +20,88 @@ public static MauiAppBuilder AddMauiBlazorDevFlowTools(this MauiAppBuilder build
service.LogCallback = msg => System.Diagnostics.Debug.WriteLine(msg);

builder.Services.AddSingleton(service);

// Auto-wire to agent and capture WebView after app starts
Task.Run(async () =>
{
// Wait for app to initialize
await Task.Delay(2000);
service.WireBlazorCdpToAgent();
service.StartWebViewDiscovery();
});

return builder;
}

/// <summary>
/// Wires the Blazor CDP service to the Agent's /api/cdp endpoint via reflection.
/// Call after both Agent and Blazor services are initialized.
/// Called automatically by AddMauiBlazorDevFlowTools after app initialization.
/// </summary>
public static void WireBlazorCdpToAgent(this GtkBlazorWebViewDebugService blazorService)
{
Task.Run(async () =>
try
{
await Task.Delay(1000);
try
var app = Microsoft.Maui.Controls.Application.Current;
if (app == null)
{
var app = Microsoft.Maui.Controls.Application.Current;
if (app == null) return;

var services = app.Handler?.MauiContext?.Services;
if (services == null) return;
Console.WriteLine("[MauiDevFlow.Blazor.Gtk] Application.Current is null, cannot wire CDP");
return;
}

// Find DevFlowAgentService by scanning loaded assemblies
Type? agentType = null;
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
agentType = asm.GetType("MauiDevFlow.Agent.Core.DevFlowAgentService");
if (agentType != null) break;
}
var services = app.Handler?.MauiContext?.Services;
if (services == null)
{
Console.WriteLine("[MauiDevFlow.Blazor.Gtk] No service provider available");
return;
}

if (agentType == null)
{
System.Diagnostics.Debug.WriteLine("[MauiDevFlow.Blazor.Gtk] Agent service type not found");
return;
}
// Find DevFlowAgentService by scanning loaded assemblies
Type? agentType = null;
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
agentType = asm.GetType("MauiDevFlow.Agent.Core.DevFlowAgentService");
if (agentType != null) break;
}

var agentService = services.GetService(agentType);
if (agentService == null)
{
System.Diagnostics.Debug.WriteLine("[MauiDevFlow.Blazor.Gtk] Agent service not registered");
return;
}
if (agentType == null)
{
Console.WriteLine("[MauiDevFlow.Blazor.Gtk] Agent service type not found");
return;
}

// Wire CdpCommandHandler and CdpReadyCheck
var handlerProp = agentType.GetProperty("CdpCommandHandler");
var readyProp = agentType.GetProperty("CdpReadyCheck");
var agentService = services.GetService(agentType);
if (agentService == null)
{
Console.WriteLine("[MauiDevFlow.Blazor.Gtk] Agent service not registered");
return;
}

if (handlerProp != null)
handlerProp.SetValue(agentService, new Func<string, Task<string>>(blazorService.SendCdpCommandAsync));
// Wire CdpCommandHandler and CdpReadyCheck
var handlerProp = agentType.GetProperty("CdpCommandHandler");
var readyProp = agentType.GetProperty("CdpReadyCheck");

if (readyProp != null)
readyProp.SetValue(agentService, new Func<bool>(() => blazorService.IsReady));
if (handlerProp != null)
handlerProp.SetValue(agentService, new Func<string, Task<string>>(blazorService.SendCdpCommandAsync));

// Wire WebViewLogCallback
var writeLogMethod = agentType.GetMethod("WriteWebViewLog");
if (writeLogMethod != null)
{
blazorService.WebViewLogCallback = (level, message, exception) =>
{
try { writeLogMethod.Invoke(agentService, new object?[] { level, "WebView.Console", message, exception }); }
catch { }
};
}
if (readyProp != null)
readyProp.SetValue(agentService, new Func<bool>(() => blazorService.IsReady));

System.Diagnostics.Debug.WriteLine("[MauiDevFlow.Blazor.Gtk] CDP wired to Agent");
}
catch (Exception ex)
// Wire WebViewLogCallback
var writeLogMethod = agentType.GetMethod("WriteWebViewLog");
if (writeLogMethod != null)
{
System.Diagnostics.Debug.WriteLine($"[MauiDevFlow.Blazor.Gtk] Wire failed: {ex.Message}");
blazorService.WebViewLogCallback = (level, message, exception) =>
{
try { writeLogMethod.Invoke(agentService, new object?[] { level, "WebView.Console", message, exception }); }
catch { }
};
}
});

Console.WriteLine("[MauiDevFlow.Blazor.Gtk] CDP wired to Agent");
}
catch (Exception ex)
{
Console.WriteLine($"[MauiDevFlow.Blazor.Gtk] Wire failed: {ex.Message}");
}
}
}
Loading
Loading