uhdk tracy integration 0.14.1
This commit is contained in:
parent
d1c4f14877
commit
70f19f1652
29
README.md
29
README.md
|
|
@ -11,6 +11,10 @@ Xcode Command Line Tools. The Windows Win32/WGL backend cross-compiles for
|
|||
`x86_64-windows-gnu`; runtime verification on Windows is pending. It requires
|
||||
Windows 10 version 1703 or later and an OpenGL 3.3-capable driver.
|
||||
|
||||
Private profiling hooks compile away in this standalone build. Tracy support
|
||||
belongs to the parent repository's static demos; it adds no dependencies or
|
||||
public ABI to this package.
|
||||
|
||||
## Build
|
||||
|
||||
```sh
|
||||
|
|
@ -77,3 +81,28 @@ watch completion. Input uses IMM32 composition; TSF reconversion and pen/touch
|
|||
are not implemented. Native UTF-16 path bytes are kept separately from the WTF-8
|
||||
paths passed to Zig I/O. If a native path cannot be exported losslessly as a shell
|
||||
URI, URI export returns `UNSUPPORTED`; native-path export still preserves it.
|
||||
|
||||
|
||||
## Optional profiling
|
||||
|
||||
`uhdk.h` includes `uhdk_profile.h`: scope, frame, thread-name and named allocation/
|
||||
free macros. They compile away without evaluating arguments unless the consumer
|
||||
defines `UHDK_PROFILE_BACKEND` to a quoted adapter header before including UHDK.
|
||||
An adapter implements the macros documented in that header. Names require static
|
||||
lifetime; pair zones on the same thread in nesting order and report frees before
|
||||
releasing memory. Do not put allocation expressions inside profiling macros.
|
||||
|
||||
Internal CPU and memory instrumentation is disabled by default. A static consumer
|
||||
can pass `.tracy = true` to `b.dependency("uhdk", ...)` (or `-Dtracy=true` when
|
||||
building the archive) and link its own Tracy 0.14.1 client configured with
|
||||
`TRACY_ENABLE` and `TRACY_ON_DEMAND`. UHDK never fetches or links Tracy itself.
|
||||
Shared instrumentation is unsupported. Run standalone unit tests with the default
|
||||
configuration; enabled instrumentation requires the consumer's linked client.
|
||||
The full repository provides an example adapter under `examples/profiling`.
|
||||
|
||||
The `UHDK` memory pool measures requested Zig backing allocations, including
|
||||
objects and arena blocks. Win32 explicit calloc/free blocks use a separate
|
||||
`UHDK native` pool. OS/framework allocations and GPU memory are excluded. On-demand capture omits allocations made before connection, so it is not
|
||||
a full live-heap snapshot. Failed allocations emit nothing; in-place resizes emit
|
||||
free + alloc. Moving reallocations use alloc/copy/free to preserve event ordering.
|
||||
Disabled builds use the original allocator directly.
|
||||
|
|
|
|||
101
build.zig
101
build.zig
|
|
@ -5,42 +5,10 @@ pub fn build(b: *std.Build) void {
|
|||
if (target.result.os.tag != .macos and target.result.os.tag != .windows) @panic("Uhdk supports macOS and Windows.");
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
const shared = b.option(bool, "shared", "Build libuhdk as a shared library instead of static") orelse false;
|
||||
const module = b.createModule(.{
|
||||
.root_source_file = b.path("src/root.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.link_libc = true,
|
||||
});
|
||||
const native = b.addTranslateC(.{
|
||||
.root_source_file = b.path("src/native.h"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
native.addIncludePath(b.path("include"));
|
||||
module.addImport("native", native.createModule());
|
||||
module.addIncludePath(b.path("include"));
|
||||
module.addIncludePath(b.path("src"));
|
||||
if (target.result.os.tag == .macos) {
|
||||
module.addCSourceFile(.{
|
||||
.file = b.path("src/macos/native.m"),
|
||||
.flags = &.{
|
||||
"-fobjc-arc", "-fblocks", "-Wno-deprecated-declarations",
|
||||
"-Wall", "-Wextra", "-Werror",
|
||||
},
|
||||
});
|
||||
module.linkFramework("Cocoa", .{});
|
||||
module.linkFramework("OpenGL", .{});
|
||||
module.linkFramework("CoreServices", .{});
|
||||
} else {
|
||||
module.addCSourceFile(.{ .file = b.path("src/windows/native.c"), .flags = &.{ "-std=c11", "-Wall", "-Wextra", "-Werror", "-DUNICODE", "-D_UNICODE" } });
|
||||
for ([_][]const u8{ "user32", "gdi32", "opengl32", "shell32", "ole32", "uuid", "imm32", "comctl32", "shlwapi", "advapi32" }) |name| module.linkSystemLibrary(name, .{});
|
||||
}
|
||||
module.addCSourceFile(.{ .file = b.path("src/glad.c"), .flags = if (shared) &.{ "-std=c99", "-DGLAD_API_CALL_EXPORT", "-DGLAD_API_CALL_EXPORT_BUILD" } else &.{"-std=c99"} });
|
||||
const lib = b.addLibrary(.{
|
||||
.name = "uhdk",
|
||||
.linkage = if (shared) .dynamic else .static,
|
||||
.root_module = module,
|
||||
});
|
||||
const tracy = b.option(bool, "tracy", "Emit Tracy 0.14.1 on-demand hooks; consumer must link the client") orelse false;
|
||||
if (tracy and shared) @panic("Tracy instrumentation requires static linking");
|
||||
const lib = createLibraryNamed(b, target, optimize, shared, tracy, "uhdk");
|
||||
const module = lib.root_module;
|
||||
const install_lib = b.addInstallArtifact(lib, .{});
|
||||
const install_headers = b.addInstallDirectory(.{
|
||||
.source_dir = b.path("include"),
|
||||
|
|
@ -72,3 +40,64 @@ pub fn build(b: *std.Build) void {
|
|||
});
|
||||
public_module.linkLibrary(lib);
|
||||
}
|
||||
|
||||
// The parent development build uses this to create a separate, uninstalled
|
||||
// instrumented archive. Standalone consumers may opt in and supply their client.
|
||||
pub fn createLibrary(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, shared: bool, instrumented: bool) *std.Build.Step.Compile {
|
||||
return createLibraryNamed(b, target, optimize, shared, instrumented, if (instrumented) "uhdk-instrumented" else "uhdk");
|
||||
}
|
||||
|
||||
fn createLibraryNamed(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, shared: bool, instrumented: bool, artifact_name: []const u8) *std.Build.Step.Compile {
|
||||
std.debug.assert(!shared or !instrumented);
|
||||
const module = b.createModule(.{
|
||||
.root_source_file = b.path("src/root.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.link_libc = true,
|
||||
});
|
||||
const native = b.addTranslateC(.{
|
||||
.root_source_file = b.path("src/native.h"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
native.addIncludePath(b.path("include"));
|
||||
module.addImport("native", native.createModule());
|
||||
module.addImport("profile", createProfileModule(b, target, optimize, instrumented));
|
||||
module.addIncludePath(b.path("include"));
|
||||
module.addIncludePath(b.path("src"));
|
||||
if (instrumented) module.addCMacro("UHDK_INTERNAL_TRACY", "1");
|
||||
if (target.result.os.tag == .macos) {
|
||||
module.addCSourceFile(.{
|
||||
.file = b.path("src/macos/native.m"),
|
||||
.flags = &.{
|
||||
"-fobjc-arc", "-fblocks", "-Wno-deprecated-declarations",
|
||||
"-Wall", "-Wextra", "-Werror",
|
||||
},
|
||||
});
|
||||
module.linkFramework("Cocoa", .{});
|
||||
module.linkFramework("OpenGL", .{});
|
||||
module.linkFramework("CoreServices", .{});
|
||||
} else {
|
||||
module.addCSourceFile(.{ .file = b.path("src/windows/native.c"), .flags = &.{ "-std=c11", "-Wall", "-Wextra", "-Werror", "-DUNICODE", "-D_UNICODE" } });
|
||||
for ([_][]const u8{ "user32", "gdi32", "opengl32", "shell32", "ole32", "uuid", "imm32", "comctl32", "shlwapi", "advapi32" }) |name| module.linkSystemLibrary(name, .{});
|
||||
}
|
||||
module.addCSourceFile(.{ .file = b.path("src/glad.c"), .flags = if (shared) &.{ "-std=c99", "-DGLAD_API_CALL_EXPORT", "-DGLAD_API_CALL_EXPORT_BUILD" } else &.{"-std=c99"} });
|
||||
const lib = b.addLibrary(.{
|
||||
.name = artifact_name,
|
||||
.linkage = if (shared) .dynamic else .static,
|
||||
.root_module = module,
|
||||
});
|
||||
return lib;
|
||||
}
|
||||
|
||||
pub fn createProfileModule(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, enabled: bool) *std.Build.Module {
|
||||
const options = b.addOptions();
|
||||
options.addOption(bool, "enabled", enabled);
|
||||
const profile = b.createModule(.{
|
||||
.root_source_file = b.path("src/Profile.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
profile.addOptions("profile_options", options);
|
||||
return profile;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@
|
|||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "uhdk_profile.h"
|
||||
|
||||
#ifndef UHDK_API
|
||||
#if defined(_WIN32) && defined(UHDK_SHARED)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
#ifndef UHDK_PROFILE_H_INCLUDED
|
||||
#define UHDK_PROFILE_H_INCLUDED
|
||||
|
||||
/* Optional, consumer-owned instrumentation; no exported UHDK ABI or dependency.
|
||||
* Define UHDK_PROFILE_BACKEND to a quoted adapter header before including uhdk.h.
|
||||
* That header implements the macros below. Without it, arguments are not evaluated.
|
||||
* Names (zone, thread, pool) must be string literals or have static lifetime.
|
||||
* C zones: UHDK_PROFILE_BEGIN(token, "name"); ... UHDK_PROFILE_END(token);
|
||||
* End on the same thread, in reverse nesting order, including early exits.
|
||||
* C++ may use UHDK_PROFILE_SCOPE("name") for automatic scope exit.
|
||||
* Memory: report successful non-null allocations, then free BEFORE releasing
|
||||
* storage. A successful resize is free + alloc; failed allocations emit nothing.
|
||||
* A pointer must use the same pool name for allocation and free. These hooks do
|
||||
* not allocate/free memory themselves, intercept malloc, or transfer ownership.
|
||||
*/
|
||||
#ifdef UHDK_PROFILE_BACKEND
|
||||
#include UHDK_PROFILE_BACKEND
|
||||
#else
|
||||
#define UHDK_PROFILE_BEGIN(token, name) ((void)0)
|
||||
#define UHDK_PROFILE_END(token) ((void)0)
|
||||
#define UHDK_PROFILE_SCOPE(name) ((void)0)
|
||||
#define UHDK_PROFILE_FRAME() ((void)0)
|
||||
#define UHDK_PROFILE_THREAD(name) ((void)0)
|
||||
#define UHDK_PROFILE_ALLOC(ptr, size, pool) ((void)0)
|
||||
#define UHDK_PROFILE_FREE(ptr, pool) ((void)0)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
//! Private, compile-time instrumentation. Standalone UHDK builds disable it.
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
pub const enabled = @import("profile_options").enabled;
|
||||
const verify_stack_order = builtin.mode == .Debug or builtin.mode == .ReleaseSafe;
|
||||
|
||||
// Tracy 0.14.1 C ABI with TRACY_ON_DEMAND, linked only by the full repo's demos.
|
||||
// No C header translation or references to Tracy symbols in disabled builds.
|
||||
const SourceLocation = extern struct {
|
||||
name: [*:0]const u8,
|
||||
function: [*:0]const u8,
|
||||
file: [*:0]const u8,
|
||||
line: u32,
|
||||
color: u32 = 0,
|
||||
};
|
||||
const Token = extern struct {
|
||||
id: u32 = 0,
|
||||
active: i32 = 0,
|
||||
connection: u64 = 0,
|
||||
};
|
||||
extern fn ___tracy_emit_zone_begin(source: *const SourceLocation, active: i32) Token;
|
||||
extern fn ___tracy_emit_zone_end(zone: Token) void;
|
||||
extern fn ___tracy_emit_frame_mark(name: ?[*:0]const u8) void;
|
||||
extern fn ___tracy_set_thread_name(name: [*:0]const u8) void;
|
||||
|
||||
threadlocal var stack_depth: usize = 0;
|
||||
|
||||
pub const Zone = if (enabled) struct {
|
||||
token: Token,
|
||||
depth: if (verify_stack_order) usize else void,
|
||||
owner: if (verify_stack_order) std.Thread.Id else void,
|
||||
|
||||
pub inline fn end(self: @This()) void {
|
||||
if (verify_stack_order) {
|
||||
std.debug.assert(self.owner == std.Thread.getCurrentId());
|
||||
std.debug.assert(self.depth == stack_depth);
|
||||
stack_depth -= 1;
|
||||
}
|
||||
if (self.token.active != 0) ___tracy_emit_zone_end(self.token);
|
||||
}
|
||||
} else struct {
|
||||
pub inline fn end(_: @This()) void {}
|
||||
};
|
||||
|
||||
// The complete @src() (including column) specializes this type. Mutable static
|
||||
// storage guarantees a unique address even when two records have equal fields.
|
||||
// It is initialized at comptime and never rewritten, so threads can share it.
|
||||
fn StaticSource(comptime source: std.builtin.SourceLocation, comptime name: [:0]const u8) type {
|
||||
return struct {
|
||||
var location: SourceLocation = .{
|
||||
.name = name,
|
||||
.function = source.fn_name,
|
||||
.file = source.file,
|
||||
.line = source.line,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn begin(comptime source: std.builtin.SourceLocation, comptime name: [:0]const u8) Zone {
|
||||
if (!enabled) return .{};
|
||||
const Statics = StaticSource(source, name);
|
||||
const token = ___tracy_emit_zone_begin(&Statics.location, 1);
|
||||
if (verify_stack_order) stack_depth += 1;
|
||||
return .{
|
||||
// Tracy leaves id/connection unspecified for inactive zones.
|
||||
.token = if (token.active != 0) token else .{},
|
||||
.depth = if (verify_stack_order) stack_depth else {},
|
||||
.owner = if (verify_stack_order) std.Thread.getCurrentId() else {},
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn frame() void {
|
||||
if (enabled) ___tracy_emit_frame_mark(null);
|
||||
}
|
||||
|
||||
pub inline fn thread(comptime name: [:0]const u8) void {
|
||||
if (enabled) ___tracy_set_thread_name(name);
|
||||
}
|
||||
|
||||
extern fn ___tracy_emit_memory_alloc_named(ptr: *const anyopaque, size: usize, name: [*:0]const u8) void;
|
||||
extern fn ___tracy_emit_memory_free_named(ptr: *const anyopaque, name: [*:0]const u8) void;
|
||||
|
||||
pub inline fn alloc(ptr: *const anyopaque, size: usize, comptime pool: [:0]const u8) void {
|
||||
if (enabled) ___tracy_emit_memory_alloc_named(ptr, size, pool);
|
||||
}
|
||||
|
||||
pub inline fn free(ptr: *const anyopaque, comptime pool: [:0]const u8) void {
|
||||
if (enabled) ___tracy_emit_memory_free_named(ptr, pool);
|
||||
}
|
||||
|
||||
/// Keep this wrapper alive and at a stable address while its allocator is used.
|
||||
/// No wrapper or instrumentation overhead when disabled. Tracks backing blocks,
|
||||
/// not arena suballocations. Thread safety is inherited from the child allocator.
|
||||
pub fn TrackingAllocator(comptime pool: [:0]const u8) type {
|
||||
return struct {
|
||||
child: std.mem.Allocator,
|
||||
const Self = @This();
|
||||
pub fn allocator(self: *Self) std.mem.Allocator {
|
||||
if (!enabled) return self.child;
|
||||
return .{
|
||||
.ptr = self,
|
||||
.vtable = &.{
|
||||
.alloc = allocate,
|
||||
.resize = resize,
|
||||
// Let Allocator.realloc use alloc/copy/free. A moving rawRemap
|
||||
// releases the old address before we can report its free, so
|
||||
// another thread could reuse it and emit an out-of-order alloc.
|
||||
.remap = std.mem.Allocator.noRemap,
|
||||
.free = release,
|
||||
},
|
||||
};
|
||||
}
|
||||
fn allocate(ctx: *anyopaque, len: usize, alignment: std.mem.Alignment, ret: usize) ?[*]u8 {
|
||||
const self: *Self = @ptrCast(@alignCast(ctx));
|
||||
const ptr = self.child.rawAlloc(len, alignment, ret) orelse return null;
|
||||
alloc(ptr, len, pool);
|
||||
return ptr;
|
||||
}
|
||||
fn resize(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, len: usize, ret: usize) bool {
|
||||
const self: *Self = @ptrCast(@alignCast(ctx));
|
||||
if (!self.child.rawResize(memory, alignment, len, ret)) return false;
|
||||
free(memory.ptr, pool);
|
||||
alloc(memory.ptr, len, pool);
|
||||
return true;
|
||||
}
|
||||
fn release(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, ret: usize) void {
|
||||
const self: *Self = @ptrCast(@alignCast(ctx));
|
||||
free(memory.ptr, pool);
|
||||
self.child.rawFree(memory, alignment, ret);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var uhdk_allocator: TrackingAllocator("UHDK") = .{ .child = std.heap.smp_allocator };
|
||||
pub fn allocator() std.mem.Allocator {
|
||||
return uhdk_allocator.allocator();
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
//! C ABI entry points for app.
|
||||
const std = @import("std");
|
||||
const profile = @import("profile");
|
||||
const backend_kind = if (@import("builtin").os.tag == .windows) c.UHDK_BACKEND_WINDOWS else c.UHDK_BACKEND_MACOS;
|
||||
const c = @import("native");
|
||||
const runtime = @import("../runtime.zig");
|
||||
|
|
@ -22,7 +23,7 @@ pub export fn uhdk_app_create(desc: [*c]const c.uhdk_app_desc, out: [*c]?*c.uhdk
|
|||
!abi.validText(desc.*.application_id, true)) return abi.INVALID;
|
||||
if (desc.*.preferred_backend != c.UHDK_BACKEND_AUTO and desc.*.preferred_backend != backend_kind) return c.UHDK_STATUS_UNSUPPORTED;
|
||||
if (singleton) return c.UHDK_STATUS_BUSY;
|
||||
const gpa = std.heap.smp_allocator;
|
||||
const gpa = profile.allocator();
|
||||
const backend = gpa.create(std.Io.Threaded) catch return abi.OOM;
|
||||
backend.* = .init(gpa, .{ .async_limit = .nothing, .concurrent_limit = .nothing });
|
||||
const application = Application.create(gpa, backend.io(), abi.textBytes(desc.*.display_name)) catch {
|
||||
|
|
@ -45,6 +46,7 @@ pub export fn uhdk_app_run(
|
|||
if (c.z_native_is_main_thread() == 0) return c.UHDK_STATUS_WRONG_THREAD;
|
||||
const application = runtime.fromAppHandle(handle);
|
||||
if (application.has_run) return abi.UNAVAILABLE;
|
||||
profile.thread("UHDK platform");
|
||||
runtime.simpleEvent(application, c.UHDK_EVENT_READY);
|
||||
const status = c.z_native_start(handle, abi.textSpan(application.name));
|
||||
if (status != abi.OK) return status;
|
||||
|
|
@ -94,6 +96,8 @@ pub export fn uhdk_app_poll_events(
|
|||
if (handle == null or out == null) return abi.INVALID;
|
||||
const application = runtime.fromAppHandle(handle);
|
||||
if (runtime.checkThread(application) != abi.OK) return c.UHDK_STATUS_WRONG_THREAD;
|
||||
const poll_zone = profile.begin(@src(), "UHDK poll events (includes wait)");
|
||||
defer poll_zone.end();
|
||||
runtime.destroyEvents(application.published);
|
||||
application.published = null;
|
||||
application.events.clearRetainingCapacity();
|
||||
|
|
@ -123,6 +127,8 @@ pub export fn uhdk_app_poll_events(
|
|||
const ns = wake_time -| current;
|
||||
application.available.reset();
|
||||
application.unlock();
|
||||
const wait_zone = profile.begin(@src(), "UHDK wait for events");
|
||||
defer wait_zone.end();
|
||||
application.available.waitTimeout(application.io, .{ .duration = .{
|
||||
.raw = .fromNanoseconds(ns),
|
||||
.clock = .awake,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
//! C ABI entry points for graphics.
|
||||
const std = @import("std");
|
||||
const profile = @import("profile");
|
||||
const c = @import("native");
|
||||
const runtime = @import("../runtime.zig");
|
||||
const abi = @import("../abi.zig");
|
||||
|
|
@ -30,6 +31,8 @@ pub export fn uhdk_gl_context_destroy(handle: ?*c.uhdk_gl_context) void {
|
|||
}
|
||||
|
||||
pub export fn uhdk_gl_context_begin_frame(handle: ?*c.uhdk_gl_context) abi.Status {
|
||||
const zone = profile.begin(@src(), "UHDK acquire GL context");
|
||||
defer zone.end();
|
||||
if (handle == null) return abi.INVALID;
|
||||
const object = runtime.fromObjectHandle(handle);
|
||||
if (object.payload.gl.bracket) return c.UHDK_STATUS_BUSY;
|
||||
|
|
@ -91,6 +94,8 @@ pub export fn uhdk_gl_context_get_version(
|
|||
}
|
||||
|
||||
pub export fn uhdk_gl_context_swap_buffers(handle: ?*c.uhdk_gl_context) abi.Status {
|
||||
const zone = profile.begin(@src(), "UHDK present (includes vsync)");
|
||||
defer zone.end();
|
||||
if (handle == null) return abi.INVALID;
|
||||
const object = runtime.fromObjectHandle(handle);
|
||||
const valid = runtime.checkObject(object, c.Z_KIND_GL);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
//! Queue handoff and platform scheduling.
|
||||
const std = @import("std");
|
||||
const profile = @import("profile");
|
||||
const c = @import("native");
|
||||
const abi = @import("abi.zig");
|
||||
const Event = @import("Event.zig");
|
||||
|
|
@ -288,6 +289,7 @@ pub fn finish(
|
|||
}
|
||||
|
||||
pub fn applicationMain(application: *Application) void {
|
||||
profile.thread("UHDK application/render");
|
||||
const pool = c.z_native_pool_push();
|
||||
defer c.z_native_pool_pop(pool);
|
||||
application.lock();
|
||||
|
|
@ -451,6 +453,8 @@ pub fn fileError(err: anyerror) abi.Status {
|
|||
}
|
||||
|
||||
pub fn fileStep(application: *Application, command: *Request) bool {
|
||||
const zone = profile.begin(@src(), "UHDK file operation batch");
|
||||
defer zone.end();
|
||||
if (isCancelled(application, command)) {
|
||||
finish(application, command, abi.CANCELLED, null);
|
||||
return true;
|
||||
|
|
@ -611,6 +615,8 @@ pub fn complete(
|
|||
}
|
||||
|
||||
pub fn tick(handle: ?*c.uhdk_app) void {
|
||||
const zone = profile.begin(@src(), "UHDK platform dispatch");
|
||||
defer zone.end();
|
||||
const application = fromAppHandle(handle);
|
||||
// Native tracking loops can reenter the run loop; never reenter application command.
|
||||
if (application.in_tick) return;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,27 @@
|
|||
#include "native.h"
|
||||
#include "glad/gl.h"
|
||||
|
||||
/* Explicit native-owned heap blocks. Framework/COM/OS allocations retain their
|
||||
* own ownership APIs and are not included in this pool. */
|
||||
#if defined(UHDK_INTERNAL_TRACY)
|
||||
extern void ___tracy_emit_memory_alloc_named(const void *ptr, size_t size, const char *name);
|
||||
extern void ___tracy_emit_memory_free_named(const void *ptr, const char *name);
|
||||
static void *profileCalloc(size_t count, size_t size) {
|
||||
void *ptr = calloc(count, size);
|
||||
if (ptr)
|
||||
___tracy_emit_memory_alloc_named(ptr, count * size, "UHDK native");
|
||||
return ptr;
|
||||
}
|
||||
static void profileFree(void *ptr) {
|
||||
if (ptr)
|
||||
___tracy_emit_memory_free_named(ptr, "UHDK native");
|
||||
free(ptr);
|
||||
}
|
||||
#else
|
||||
#define profileCalloc calloc
|
||||
#define profileFree free
|
||||
#endif
|
||||
|
||||
#define Z_WAKE (WM_APP + 1)
|
||||
#define Z_DIALOG_MESSAGE (WM_APP + 2)
|
||||
#define Z_POPUP_MESSAGE (WM_APP + 3)
|
||||
|
|
@ -103,14 +124,14 @@ static WCHAR *wide(uhdk_text text) {
|
|||
: 0;
|
||||
if (text.len && !n)
|
||||
return NULL;
|
||||
WCHAR *s = calloc((size_t)n + 1, sizeof(WCHAR));
|
||||
WCHAR *s = profileCalloc((size_t)n + 1, sizeof(WCHAR));
|
||||
if (s && n)
|
||||
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text.ptr, (int)text.len, s, n);
|
||||
return s;
|
||||
}
|
||||
static char *utf8(const WCHAR *text, int len, uint64_t *out_len) {
|
||||
int n = len ? WideCharToMultiByte(CP_UTF8, 0, text, len, NULL, 0, NULL, NULL) : 0;
|
||||
char *s = calloc((size_t)n + 1, 1);
|
||||
char *s = profileCalloc((size_t)n + 1, 1);
|
||||
if (s && n)
|
||||
WideCharToMultiByte(CP_UTF8, 0, text, len, s, n, NULL, NULL);
|
||||
*out_len = (uint64_t)n;
|
||||
|
|
@ -118,7 +139,7 @@ static char *utf8(const WCHAR *text, int len, uint64_t *out_len) {
|
|||
}
|
||||
static WCHAR *filePath(uhdk_file *file) {
|
||||
uhdk_bytes bytes = z_file_path(file);
|
||||
WCHAR *s = calloc((size_t)bytes.len / 2 + 1, sizeof(WCHAR));
|
||||
WCHAR *s = profileCalloc((size_t)bytes.len / 2 + 1, sizeof(WCHAR));
|
||||
if (s && bytes.len)
|
||||
memcpy(s, bytes.ptr, (size_t)bytes.len);
|
||||
return s;
|
||||
|
|
@ -200,11 +221,11 @@ static void textEvent(Window *w, uint32_t type, const WCHAR *text, int length, i
|
|||
char *prefix = utf8(text, cursor, &position);
|
||||
if (prefix) {
|
||||
e.data.text.selection_start = position;
|
||||
free(prefix);
|
||||
profileFree(prefix);
|
||||
}
|
||||
}
|
||||
z_emit(host.app, &e);
|
||||
free(s);
|
||||
profileFree(s);
|
||||
}
|
||||
static void editEvent(Window *w, uint32_t command) {
|
||||
uhdk_event e = {0};
|
||||
|
|
@ -394,24 +415,24 @@ static void composition(Window *w, LPARAM flags) {
|
|||
LONG n = ImmGetCompositionStringW(ime, kinds[i], NULL, 0);
|
||||
if (n < 0)
|
||||
continue;
|
||||
WCHAR *s = calloc((size_t)n + 2, 1);
|
||||
WCHAR *s = profileCalloc((size_t)n + 2, 1);
|
||||
if (!s)
|
||||
continue;
|
||||
ImmGetCompositionStringW(ime, kinds[i], s, (DWORD)n);
|
||||
int cursor = i ? ImmGetCompositionStringW(ime, GCS_CURSORPOS, NULL, 0) : -1;
|
||||
textEvent(w, i ? UHDK_EVENT_TEXT_PREEDIT : UHDK_EVENT_TEXT_COMMIT, s, n / 2, cursor);
|
||||
free(s);
|
||||
profileFree(s);
|
||||
}
|
||||
ImmReleaseContext(w->hwnd, ime);
|
||||
}
|
||||
static void dropFiles(Window *w, HDROP drop) {
|
||||
UINT count = DragQueryFileW(drop, UINT_MAX, NULL, 0);
|
||||
uhdk_file **files = calloc(count ? count : 1, sizeof(*files));
|
||||
uhdk_file **files = profileCalloc(count ? count : 1, sizeof(*files));
|
||||
uint64_t used = 0;
|
||||
if (files)
|
||||
for (UINT i = 0; i < count; ++i) {
|
||||
UINT n = DragQueryFileW(drop, i, NULL, 0);
|
||||
WCHAR *p = calloc((size_t)n + 1, sizeof(WCHAR));
|
||||
WCHAR *p = profileCalloc((size_t)n + 1, sizeof(WCHAR));
|
||||
if (!p)
|
||||
break;
|
||||
DragQueryFileW(drop, i, p, n + 1);
|
||||
|
|
@ -419,7 +440,7 @@ static void dropFiles(Window *w, HDROP drop) {
|
|||
z_file_from_url(host.app, (uhdk_bytes){(uint8_t *)p, (uint64_t)n * 2}, NULL);
|
||||
if (file)
|
||||
files[used++] = file;
|
||||
free(p);
|
||||
profileFree(p);
|
||||
}
|
||||
POINT point;
|
||||
DragQueryPoint(drop, &point);
|
||||
|
|
@ -433,7 +454,7 @@ static void dropFiles(Window *w, HDROP drop) {
|
|||
}
|
||||
for (uint64_t i = 0; i < used; ++i)
|
||||
uhdk_file_release(files[i]);
|
||||
free(files);
|
||||
profileFree(files);
|
||||
DragFinish(drop);
|
||||
}
|
||||
static LRESULT CALLBACK windowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
|
||||
|
|
@ -741,13 +762,13 @@ uhdk_status z_native_file_init(uhdk_file *file, uhdk_bytes path, uhdk_text uri,
|
|||
if (path.len) {
|
||||
if (path.len % 2 || path.len > 65534)
|
||||
return UHDK_STATUS_INVALID_ARGUMENT;
|
||||
name = calloc((size_t)path.len / 2 + 1, sizeof(WCHAR));
|
||||
name = profileCalloc((size_t)path.len / 2 + 1, sizeof(WCHAR));
|
||||
if (!name)
|
||||
return UHDK_STATUS_OUT_OF_MEMORY;
|
||||
memcpy(name, path.ptr, (size_t)path.len);
|
||||
for (size_t i = 0; i < path.len / 2; i++)
|
||||
if (!name[i]) {
|
||||
free(name);
|
||||
profileFree(name);
|
||||
return UHDK_STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -755,19 +776,19 @@ uhdk_status z_native_file_init(uhdk_file *file, uhdk_bytes path, uhdk_text uri,
|
|||
if (!url)
|
||||
return UHDK_STATUS_INVALID_ARGUMENT;
|
||||
if (_wcsnicmp(url, L"file:", 5)) {
|
||||
free(url);
|
||||
profileFree(url);
|
||||
return UHDK_STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
DWORD capacity = 32768;
|
||||
name = calloc(capacity, sizeof(WCHAR));
|
||||
name = profileCalloc(capacity, sizeof(WCHAR));
|
||||
if (!name) {
|
||||
free(url);
|
||||
profileFree(url);
|
||||
return UHDK_STATUS_OUT_OF_MEMORY;
|
||||
}
|
||||
HRESULT hr = PathCreateFromUrlW(url, name, &capacity, 0);
|
||||
free(url);
|
||||
profileFree(url);
|
||||
if (FAILED(hr)) {
|
||||
free(name);
|
||||
profileFree(name);
|
||||
return UHDK_STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
}
|
||||
|
|
@ -777,13 +798,13 @@ uhdk_status z_native_file_init(uhdk_file *file, uhdk_bytes path, uhdk_text uri,
|
|||
name[1] == L':' && (name[2] == L'\\' || name[2] == L'/');
|
||||
BOOL unc = n >= 3 && name[0] == L'\\' && name[1] == L'\\';
|
||||
if (!drive && !unc) {
|
||||
free(name);
|
||||
profileFree(name);
|
||||
return UHDK_STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
DWORD uri_capacity = 3 * 32768;
|
||||
WCHAR *url = calloc(uri_capacity, sizeof(WCHAR));
|
||||
WCHAR *url = profileCalloc(uri_capacity, sizeof(WCHAR));
|
||||
if (!url) {
|
||||
free(name);
|
||||
profileFree(name);
|
||||
return UHDK_STATUS_OUT_OF_MEMORY;
|
||||
}
|
||||
HRESULT hr = UrlCreateFromPathW(name, url, &uri_capacity, 0);
|
||||
|
|
@ -802,10 +823,10 @@ uhdk_status z_native_file_init(uhdk_file *file, uhdk_bytes path, uhdk_text uri,
|
|||
display ? z_file_init(file, (uhdk_bytes){(uint8_t *)name, n * 2},
|
||||
(uhdk_text){url8, uri_len}, (uhdk_text){display, display_len}, NULL)
|
||||
: UHDK_STATUS_OS_ERROR;
|
||||
free(display);
|
||||
free(url8);
|
||||
free(url);
|
||||
free(name);
|
||||
profileFree(display);
|
||||
profileFree(url8);
|
||||
profileFree(url);
|
||||
profileFree(name);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
|
@ -819,13 +840,13 @@ static PROC glProc(const char *name) {
|
|||
static uhdk_status createGraphics(Window *w, void *handle) {
|
||||
if (!(w->flags & UHDK_WINDOW_OPENGL))
|
||||
return UHDK_STATUS_INVALID_ARGUMENT;
|
||||
Graphics *g = calloc(1, sizeof(*g));
|
||||
Graphics *g = profileCalloc(1, sizeof(*g));
|
||||
if (!g)
|
||||
return UHDK_STATUS_OUT_OF_MEMORY;
|
||||
g->window = w->hwnd;
|
||||
g->dc = GetDC(w->hwnd);
|
||||
if (!g->dc) {
|
||||
free(g);
|
||||
profileFree(g);
|
||||
return UHDK_STATUS_OS_ERROR;
|
||||
}
|
||||
PIXELFORMATDESCRIPTOR pfd = {0};
|
||||
|
|
@ -888,7 +909,7 @@ failed:
|
|||
if (g->rc)
|
||||
wglDeleteContext(g->rc);
|
||||
ReleaseDC(g->window, g->dc);
|
||||
free(g);
|
||||
profileFree(g);
|
||||
return UHDK_STATUS_UNSUPPORTED;
|
||||
}
|
||||
uhdk_status z_native_gl(void *native, uint32_t op, void *out) {
|
||||
|
|
@ -982,11 +1003,11 @@ static HMENU buildMenu(uhdk_menu *source, BOOL popup) {
|
|||
!InsertMenuItemW(menu, (UINT)i, TRUE, &native)) {
|
||||
if (native.hSubMenu)
|
||||
DestroyMenu(native.hSubMenu);
|
||||
free(label);
|
||||
profileFree(label);
|
||||
DestroyMenu(menu);
|
||||
return NULL;
|
||||
}
|
||||
free(label);
|
||||
profileFree(label);
|
||||
}
|
||||
return menu;
|
||||
}
|
||||
|
|
@ -1087,10 +1108,10 @@ static void showDialog(void) {
|
|||
IFileDialog_SetFolder(dialog, folder);
|
||||
IShellItem_Release(folder);
|
||||
}
|
||||
free(path);
|
||||
profileFree(path);
|
||||
}
|
||||
COMDLG_FILTERSPEC *filters =
|
||||
calloc(d->filters_len ? (size_t)d->filters_len : 1, sizeof(*filters));
|
||||
profileCalloc(d->filters_len ? (size_t)d->filters_len : 1, sizeof(*filters));
|
||||
BOOL filter_ok = filters != NULL;
|
||||
if (filters)
|
||||
for (uint64_t i = 0; i < d->filters_len; i++) {
|
||||
|
|
@ -1099,7 +1120,7 @@ static void showDialog(void) {
|
|||
size_t capacity = 2;
|
||||
for (uint64_t j = 0; j < extensions.len; j++)
|
||||
capacity += (size_t)extensions.ptr[j].len + 3;
|
||||
WCHAR *pattern = calloc(capacity, sizeof(WCHAR));
|
||||
WCHAR *pattern = profileCalloc(capacity, sizeof(WCHAR));
|
||||
filters[i].pszSpec = pattern;
|
||||
if (!filters[i].pszName || !pattern) {
|
||||
filter_ok = FALSE;
|
||||
|
|
@ -1117,7 +1138,7 @@ static void showDialog(void) {
|
|||
wcscat(pattern, L";");
|
||||
wcscat(pattern, L"*.");
|
||||
wcscat(pattern, ext);
|
||||
free(ext);
|
||||
profileFree(ext);
|
||||
}
|
||||
}
|
||||
if (filter_ok && d->filters_len)
|
||||
|
|
@ -1137,7 +1158,7 @@ static void showDialog(void) {
|
|||
if (SUCCEEDED(hr)) {
|
||||
DWORD n = 0;
|
||||
IShellItemArray_GetCount(items, &n);
|
||||
files = calloc(n ? n : 1, sizeof(*files));
|
||||
files = profileCalloc(n ? n : 1, sizeof(*files));
|
||||
if (files)
|
||||
for (DWORD i = 0; i < n; i++) {
|
||||
IShellItem *item = NULL;
|
||||
|
|
@ -1162,7 +1183,7 @@ static void showDialog(void) {
|
|||
status = UHDK_STATUS_OS_ERROR;
|
||||
} else if (status == UHDK_STATUS_OK) {
|
||||
IShellItem *item = NULL;
|
||||
files = calloc(1, sizeof(*files));
|
||||
files = profileCalloc(1, sizeof(*files));
|
||||
if (files && SUCCEEDED(IFileDialog_GetResult(dialog, &item))) {
|
||||
files[0] = shellFile(item);
|
||||
IShellItem_Release(item);
|
||||
|
|
@ -1178,20 +1199,20 @@ static void showDialog(void) {
|
|||
host.dialog = NULL;
|
||||
host.dialog_id = 0;
|
||||
IFileDialog_Release(dialog);
|
||||
free(title);
|
||||
free(label);
|
||||
free(suggestion);
|
||||
profileFree(title);
|
||||
profileFree(label);
|
||||
profileFree(suggestion);
|
||||
if (filters) {
|
||||
for (uint64_t i = 0; i < d->filters_len; i++) {
|
||||
free((void *)filters[i].pszName);
|
||||
free((void *)filters[i].pszSpec);
|
||||
profileFree((void *)filters[i].pszName);
|
||||
profileFree((void *)filters[i].pszSpec);
|
||||
}
|
||||
free(filters);
|
||||
profileFree(filters);
|
||||
}
|
||||
z_complete(host.app, id, status, &result);
|
||||
for (uint64_t i = 0; i < count; i++)
|
||||
uhdk_file_release(files[i]);
|
||||
free(files);
|
||||
profileFree(files);
|
||||
}
|
||||
|
||||
static BOOL armWatch(Watch *watch) {
|
||||
|
|
@ -1207,15 +1228,15 @@ static uhdk_status createWatch(uhdk_file *root, uhdk_watch *handle) {
|
|||
WCHAR *path = filePath(root);
|
||||
if (!path)
|
||||
return UHDK_STATUS_OUT_OF_MEMORY;
|
||||
Watch *watch = calloc(1, sizeof(*watch));
|
||||
Watch *watch = profileCalloc(1, sizeof(*watch));
|
||||
if (!watch) {
|
||||
free(path);
|
||||
profileFree(path);
|
||||
return UHDK_STATUS_OUT_OF_MEMORY;
|
||||
}
|
||||
DWORD attributes = GetFileAttributesW(path);
|
||||
if (attributes == INVALID_FILE_ATTRIBUTES) {
|
||||
free(path);
|
||||
free(watch);
|
||||
profileFree(path);
|
||||
profileFree(watch);
|
||||
return osError();
|
||||
}
|
||||
if (!(attributes & FILE_ATTRIBUTE_DIRECTORY)) {
|
||||
|
|
@ -1224,14 +1245,14 @@ static uhdk_status createWatch(uhdk_file *root, uhdk_watch *handle) {
|
|||
if (other && (!slash || other > slash))
|
||||
slash = other;
|
||||
if (!slash) {
|
||||
free(path);
|
||||
free(watch);
|
||||
profileFree(path);
|
||||
profileFree(watch);
|
||||
return UHDK_STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
watch->leaf = _wcsdup(slash + 1);
|
||||
if (!watch->leaf) {
|
||||
free(path);
|
||||
free(watch);
|
||||
profileFree(path);
|
||||
profileFree(watch);
|
||||
return UHDK_STATUS_OUT_OF_MEMORY;
|
||||
}
|
||||
slash[1] = 0;
|
||||
|
|
@ -1239,7 +1260,7 @@ static uhdk_status createWatch(uhdk_file *root, uhdk_watch *handle) {
|
|||
watch->directory = CreateFileW(
|
||||
path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
|
||||
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
|
||||
free(path);
|
||||
profileFree(path);
|
||||
watch->overlapped.hEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
|
||||
watch->handle = handle;
|
||||
if (watch->directory == INVALID_HANDLE_VALUE || !watch->overlapped.hEvent || !armWatch(watch)) {
|
||||
|
|
@ -1248,8 +1269,8 @@ static uhdk_status createWatch(uhdk_file *root, uhdk_watch *handle) {
|
|||
CloseHandle(watch->directory);
|
||||
if (watch->overlapped.hEvent)
|
||||
CloseHandle(watch->overlapped.hEvent);
|
||||
free(watch->leaf);
|
||||
free(watch);
|
||||
profileFree(watch->leaf);
|
||||
profileFree(watch);
|
||||
return status;
|
||||
}
|
||||
watch->next = host.watches;
|
||||
|
|
@ -1462,7 +1483,7 @@ void z_native_destroy(uint32_t kind, void *native) {
|
|||
ReleaseDC(g->window, g->dc);
|
||||
LeaveCriticalSection(&g->lock);
|
||||
DeleteCriticalSection(&g->lock);
|
||||
free(g);
|
||||
profileFree(g);
|
||||
} else if (kind == Z_KIND_WINDOW) {
|
||||
Window *w = native;
|
||||
Window **link = &host.windows;
|
||||
|
|
@ -1481,7 +1502,7 @@ void z_native_destroy(uint32_t kind, void *native) {
|
|||
DestroyWindow(w->hwnd);
|
||||
if (w->menu_source)
|
||||
uhdk_menu_release(w->menu_source);
|
||||
free(w);
|
||||
profileFree(w);
|
||||
} else if (kind == Z_KIND_WATCH) {
|
||||
Watch *w = native;
|
||||
Watch **link = &host.watches;
|
||||
|
|
@ -1496,8 +1517,8 @@ void z_native_destroy(uint32_t kind, void *native) {
|
|||
}
|
||||
CloseHandle(w->directory);
|
||||
CloseHandle(w->overlapped.hEvent);
|
||||
free(w->leaf);
|
||||
free(w);
|
||||
profileFree(w->leaf);
|
||||
profileFree(w);
|
||||
}
|
||||
}
|
||||
static uhdk_status clipboardWrite(uhdk_text text) {
|
||||
|
|
@ -1509,18 +1530,18 @@ static uhdk_status clipboardWrite(uhdk_text text) {
|
|||
size_t length = (wcslen(value) + 1) * sizeof(WCHAR);
|
||||
HGLOBAL memory = GlobalAlloc(GMEM_MOVEABLE, length);
|
||||
if (!memory) {
|
||||
free(value);
|
||||
profileFree(value);
|
||||
return UHDK_STATUS_OUT_OF_MEMORY;
|
||||
}
|
||||
void *bytes = GlobalLock(memory);
|
||||
if (!bytes) {
|
||||
GlobalFree(memory);
|
||||
free(value);
|
||||
profileFree(value);
|
||||
return UHDK_STATUS_OS_ERROR;
|
||||
}
|
||||
memcpy(bytes, value, length);
|
||||
GlobalUnlock(memory);
|
||||
free(value);
|
||||
profileFree(value);
|
||||
if (!OpenClipboard(host.dispatch)) {
|
||||
GlobalFree(memory);
|
||||
return UHDK_STATUS_BUSY;
|
||||
|
|
@ -1554,13 +1575,13 @@ static uhdk_status clipboardRead(uint64_t id, uint64_t limit) {
|
|||
if (!text)
|
||||
return UHDK_STATUS_OS_ERROR;
|
||||
if (len > limit) {
|
||||
free(text);
|
||||
profileFree(text);
|
||||
return UHDK_STATUS_BUFFER_TOO_SMALL;
|
||||
}
|
||||
uhdk_request_result result = {0};
|
||||
result.value.text = (uhdk_text){text, len};
|
||||
z_complete(host.app, id, UHDK_STATUS_OK, &result);
|
||||
free(text);
|
||||
profileFree(text);
|
||||
return Z_DEFERRED;
|
||||
}
|
||||
static uhdk_status setIcon(const uhdk_image *image) {
|
||||
|
|
@ -1617,7 +1638,7 @@ uhdk_status z_native_execute(uhdk_app *app, const z_command *cmd, uint64_t reque
|
|||
cmd->target && z_object_kind(cmd->target) == Z_KIND_WINDOW ? windowOf(cmd->target) : NULL;
|
||||
switch (cmd->op) {
|
||||
case Z_WINDOW: {
|
||||
w = calloc(1, sizeof(*w));
|
||||
w = profileCalloc(1, sizeof(*w));
|
||||
if (!w)
|
||||
return UHDK_STATUS_OUT_OF_MEMORY;
|
||||
w->handle = cmd->target;
|
||||
|
|
@ -1638,16 +1659,16 @@ uhdk_status z_native_execute(uhdk_app *app, const z_command *cmd, uint64_t reque
|
|||
AdjustWindowRectExForDpi(&r, style, FALSE, 0, dpi);
|
||||
WCHAR *title = wide(cmd->window.title);
|
||||
if (!title) {
|
||||
free(w);
|
||||
profileFree(w);
|
||||
return UHDK_STATUS_OUT_OF_MEMORY;
|
||||
}
|
||||
HWND parent = cmd->window.parent ? windowOf(cmd->window.parent)->hwnd : NULL;
|
||||
HWND hwnd = CreateWindowExW(0, window_class, title, style, CW_USEDEFAULT, CW_USEDEFAULT,
|
||||
r.right - r.left, r.bottom - r.top, parent, NULL,
|
||||
GetModuleHandleW(NULL), w);
|
||||
free(title);
|
||||
profileFree(title);
|
||||
if (!hwnd) {
|
||||
free(w);
|
||||
profileFree(w);
|
||||
return osError();
|
||||
}
|
||||
ImmAssociateContextEx(hwnd, NULL, 0);
|
||||
|
|
@ -1668,7 +1689,7 @@ uhdk_status z_native_execute(uhdk_app *app, const z_command *cmd, uint64_t reque
|
|||
if (!title)
|
||||
return UHDK_STATUS_OUT_OF_MEMORY;
|
||||
BOOL ok = SetWindowTextW(w->hwnd, title);
|
||||
free(title);
|
||||
profileFree(title);
|
||||
return ok ? UHDK_STATUS_OK : osError();
|
||||
}
|
||||
case Z_SIZE: {
|
||||
|
|
@ -1831,11 +1852,11 @@ uhdk_status z_native_execute(uhdk_app *app, const z_command *cmd, uint64_t reque
|
|||
if (!url)
|
||||
return UHDK_STATUS_OUT_OF_MEMORY;
|
||||
if (!UrlIsW(url, URLIS_URL)) {
|
||||
free(url);
|
||||
profileFree(url);
|
||||
return UHDK_STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
INT_PTR status = (INT_PTR)ShellExecuteW(NULL, L"open", url, NULL, NULL, SW_SHOWNORMAL);
|
||||
free(url);
|
||||
profileFree(url);
|
||||
return status > 32 ? UHDK_STATUS_OK : UHDK_STATUS_OS_ERROR;
|
||||
}
|
||||
case Z_WATCH: {
|
||||
|
|
|
|||
Loading…
Reference in New Issue