mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
Merge pull request #2173 from ghostty-org/crashlog
Initial Crash Logging
This commit is contained in:
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -1,5 +1,6 @@
|
|||||||
vendor/** linguist-vendored
|
vendor/** linguist-vendored
|
||||||
website/** linguist-documentation
|
website/** linguist-documentation
|
||||||
|
pkg/breakpad/vendor/** linguist-vendored
|
||||||
pkg/cimgui/vendor/** linguist-vendored
|
pkg/cimgui/vendor/** linguist-vendored
|
||||||
pkg/simdutf/vendor/** linguist-vendored
|
pkg/simdutf/vendor/** linguist-vendored
|
||||||
src/terminal/res/** linguist-vendored
|
src/terminal/res/** linguist-vendored
|
||||||
|
32
README.md
32
README.md
@ -445,6 +445,38 @@ and more use cases. At the time of writing this, `libghostty` is very
|
|||||||
Mac-centric -- particularly around rendering -- and we have work to do to
|
Mac-centric -- particularly around rendering -- and we have work to do to
|
||||||
expand this to other platforms.
|
expand this to other platforms.
|
||||||
|
|
||||||
|
## Crash Reports
|
||||||
|
|
||||||
|
Ghostty has a built-in crash reporter that will generate and save crash
|
||||||
|
reports to disk. The crash reports are saved to the `$XDG_STATE_HOME/ghostty/crash`
|
||||||
|
directory. If `$XDG_STATE_HOME` is not set, the default is `~/.local/state`.
|
||||||
|
**Crash reports are _not_ automatically sent anywhere off your machine.**
|
||||||
|
|
||||||
|
Crash reports are only generated the next time Ghostty is started after a
|
||||||
|
crash. If Ghostty crashes and you want to generate a crash report, you must
|
||||||
|
restart Ghostty at least once. You should see a message in the log that a
|
||||||
|
crash report was generated.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
>
|
||||||
|
> A future version of Ghostty will make the crash reports more easily
|
||||||
|
> viewable through the CLI and GUI. For now, you must manually check the
|
||||||
|
> directory.
|
||||||
|
|
||||||
|
Crash reports end in the `.ghosttycrash` extension. The crash reports are
|
||||||
|
in [Sentry envelope format](https://develop.sentry.dev/sdk/envelopes/). You
|
||||||
|
can upload these to your own Sentry account to view their contents, but the
|
||||||
|
format is also publicly documented so any other available tools can also
|
||||||
|
be used. A future version of Ghostty will show you the contents of the
|
||||||
|
crash report directly in the terminal.
|
||||||
|
|
||||||
|
If Ghostty crashed, you can help the project by attaching the crash report
|
||||||
|
to a GitHub issue. The crash report doesn't contain any purposefully
|
||||||
|
sensitive information, but it may contain paths to files on your system,
|
||||||
|
information about your OS, or other details you may not want to share.
|
||||||
|
If you are concerned about this, you're welcome to transfer the crash report
|
||||||
|
privately to me.
|
||||||
|
|
||||||
## Developing Ghostty
|
## Developing Ghostty
|
||||||
|
|
||||||
To build Ghostty, you need [Zig 0.13](https://ziglang.org/) installed.
|
To build Ghostty, you need [Zig 0.13](https://ziglang.org/) installed.
|
||||||
|
19
build.zig
19
build.zig
@ -1012,6 +1012,11 @@ fn addDeps(
|
|||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
|
const sentry_dep = b.dependency("sentry", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.backend = .breakpad,
|
||||||
|
});
|
||||||
const zlib_dep = b.dependency("zlib", .{
|
const zlib_dep = b.dependency("zlib", .{
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
@ -1115,6 +1120,7 @@ fn addDeps(
|
|||||||
step.root_module.addImport("xev", libxev_dep.module("xev"));
|
step.root_module.addImport("xev", libxev_dep.module("xev"));
|
||||||
step.root_module.addImport("opengl", opengl_dep.module("opengl"));
|
step.root_module.addImport("opengl", opengl_dep.module("opengl"));
|
||||||
step.root_module.addImport("pixman", pixman_dep.module("pixman"));
|
step.root_module.addImport("pixman", pixman_dep.module("pixman"));
|
||||||
|
step.root_module.addImport("sentry", sentry_dep.module("sentry"));
|
||||||
step.root_module.addImport("ziglyph", ziglyph_dep.module("ziglyph"));
|
step.root_module.addImport("ziglyph", ziglyph_dep.module("ziglyph"));
|
||||||
step.root_module.addImport("vaxis", vaxis_dep.module("vaxis"));
|
step.root_module.addImport("vaxis", vaxis_dep.module("vaxis"));
|
||||||
|
|
||||||
@ -1162,6 +1168,19 @@ fn addDeps(
|
|||||||
step.linkLibrary(spirv_cross_dep.artifact("spirv_cross"));
|
step.linkLibrary(spirv_cross_dep.artifact("spirv_cross"));
|
||||||
try static_libs.append(spirv_cross_dep.artifact("spirv_cross").getEmittedBin());
|
try static_libs.append(spirv_cross_dep.artifact("spirv_cross").getEmittedBin());
|
||||||
|
|
||||||
|
if (target.result.os.tag != .windows) {
|
||||||
|
// Sentry
|
||||||
|
step.linkLibrary(sentry_dep.artifact("sentry"));
|
||||||
|
try static_libs.append(sentry_dep.artifact("sentry").getEmittedBin());
|
||||||
|
|
||||||
|
// We also need to include breakpad in the static libs.
|
||||||
|
const breakpad_dep = sentry_dep.builder.dependency("breakpad", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
try static_libs.append(breakpad_dep.artifact("breakpad").getEmittedBin());
|
||||||
|
}
|
||||||
|
|
||||||
// Dynamic link
|
// Dynamic link
|
||||||
if (!config.static) {
|
if (!config.static) {
|
||||||
step.addIncludePath(freetype_dep.path(""));
|
step.addIncludePath(freetype_dep.path(""));
|
||||||
|
@ -37,6 +37,7 @@
|
|||||||
.oniguruma = .{ .path = "./pkg/oniguruma" },
|
.oniguruma = .{ .path = "./pkg/oniguruma" },
|
||||||
.opengl = .{ .path = "./pkg/opengl" },
|
.opengl = .{ .path = "./pkg/opengl" },
|
||||||
.pixman = .{ .path = "./pkg/pixman" },
|
.pixman = .{ .path = "./pkg/pixman" },
|
||||||
|
.sentry = .{ .path = "./pkg/sentry" },
|
||||||
.simdutf = .{ .path = "./pkg/simdutf" },
|
.simdutf = .{ .path = "./pkg/simdutf" },
|
||||||
.utfcpp = .{ .path = "./pkg/utfcpp" },
|
.utfcpp = .{ .path = "./pkg/utfcpp" },
|
||||||
.zlib = .{ .path = "./pkg/zlib" },
|
.zlib = .{ .path = "./pkg/zlib" },
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
# This file is auto-generated! check build-support/check-zig-cache-hash.sh for
|
# This file is auto-generated! check build-support/check-zig-cache-hash.sh for
|
||||||
# more details.
|
# more details.
|
||||||
"sha256-WvHgckWfWLXyvmz8alpqwyAhPaCYe+HdshpcTwrOaf8="
|
"sha256-mIUl5j3JxtydoV7ayy3aNrt/jR8+a68lQw6lQimLZEw="
|
||||||
|
157
pkg/breakpad/build.zig
Normal file
157
pkg/breakpad/build.zig
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) !void {
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
const upstream = b.dependency("breakpad", .{});
|
||||||
|
|
||||||
|
const lib = b.addStaticLibrary(.{
|
||||||
|
.name = "breakpad",
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
lib.linkLibCpp();
|
||||||
|
lib.addIncludePath(upstream.path("src"));
|
||||||
|
lib.addIncludePath(b.path("vendor"));
|
||||||
|
if (target.result.isDarwin()) {
|
||||||
|
const apple_sdk = @import("apple_sdk");
|
||||||
|
try apple_sdk.addPaths(b, &lib.root_module);
|
||||||
|
}
|
||||||
|
|
||||||
|
var flags = std.ArrayList([]const u8).init(b.allocator);
|
||||||
|
defer flags.deinit();
|
||||||
|
try flags.appendSlice(&.{});
|
||||||
|
|
||||||
|
lib.addCSourceFiles(.{
|
||||||
|
.root = upstream.path(""),
|
||||||
|
.files = common,
|
||||||
|
.flags = flags.items,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (target.result.isDarwin()) {
|
||||||
|
lib.addCSourceFiles(.{
|
||||||
|
.root = upstream.path(""),
|
||||||
|
.files = common_apple,
|
||||||
|
.flags = flags.items,
|
||||||
|
});
|
||||||
|
lib.addCSourceFiles(.{
|
||||||
|
.root = upstream.path(""),
|
||||||
|
.files = client_apple,
|
||||||
|
.flags = flags.items,
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (target.result.os.tag) {
|
||||||
|
.macos => {
|
||||||
|
lib.addCSourceFiles(.{
|
||||||
|
.root = upstream.path(""),
|
||||||
|
.files = common_mac,
|
||||||
|
.flags = flags.items,
|
||||||
|
});
|
||||||
|
lib.addCSourceFiles(.{
|
||||||
|
.root = upstream.path(""),
|
||||||
|
.files = client_mac,
|
||||||
|
.flags = flags.items,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
.ios => lib.addCSourceFiles(.{
|
||||||
|
.root = upstream.path(""),
|
||||||
|
.files = client_ios,
|
||||||
|
.flags = flags.items,
|
||||||
|
}),
|
||||||
|
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.result.os.tag == .linux) {
|
||||||
|
lib.addCSourceFiles(.{
|
||||||
|
.root = upstream.path(""),
|
||||||
|
.files = common_linux,
|
||||||
|
.flags = flags.items,
|
||||||
|
});
|
||||||
|
lib.addCSourceFiles(.{
|
||||||
|
.root = upstream.path(""),
|
||||||
|
.files = client_linux,
|
||||||
|
.flags = flags.items,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
lib.installHeadersDirectory(
|
||||||
|
upstream.path("src"),
|
||||||
|
"",
|
||||||
|
.{ .include_extensions = &.{".h"} },
|
||||||
|
);
|
||||||
|
|
||||||
|
b.installArtifact(lib);
|
||||||
|
}
|
||||||
|
|
||||||
|
const common: []const []const u8 = &.{
|
||||||
|
"src/common/convert_UTF.cc",
|
||||||
|
"src/common/md5.cc",
|
||||||
|
"src/common/string_conversion.cc",
|
||||||
|
};
|
||||||
|
|
||||||
|
const common_linux: []const []const u8 = &.{
|
||||||
|
"src/common/linux/elf_core_dump.cc",
|
||||||
|
"src/common/linux/elfutils.cc",
|
||||||
|
"src/common/linux/file_id.cc",
|
||||||
|
"src/common/linux/guid_creator.cc",
|
||||||
|
"src/common/linux/linux_libc_support.cc",
|
||||||
|
"src/common/linux/memory_mapped_file.cc",
|
||||||
|
"src/common/linux/safe_readlink.cc",
|
||||||
|
"src/common/linux/scoped_pipe.cc",
|
||||||
|
"src/common/linux/scoped_tmpfile.cc",
|
||||||
|
"src/common/linux/breakpad_getcontext.S",
|
||||||
|
};
|
||||||
|
|
||||||
|
const common_apple: []const []const u8 = &.{
|
||||||
|
"src/common/mac/arch_utilities.cc",
|
||||||
|
"src/common/mac/file_id.cc",
|
||||||
|
"src/common/mac/macho_id.cc",
|
||||||
|
"src/common/mac/macho_utilities.cc",
|
||||||
|
"src/common/mac/macho_walker.cc",
|
||||||
|
"src/common/mac/string_utilities.cc",
|
||||||
|
};
|
||||||
|
|
||||||
|
const common_mac: []const []const u8 = &.{
|
||||||
|
"src/common/mac/MachIPC.mm",
|
||||||
|
"src/common/mac/bootstrap_compat.cc",
|
||||||
|
};
|
||||||
|
|
||||||
|
const client_linux: []const []const u8 = &.{
|
||||||
|
"src/client/minidump_file_writer.cc",
|
||||||
|
"src/client/linux/crash_generation/crash_generation_client.cc",
|
||||||
|
"src/client/linux/crash_generation/crash_generation_server.cc",
|
||||||
|
"src/client/linux/dump_writer_common/thread_info.cc",
|
||||||
|
"src/client/linux/dump_writer_common/ucontext_reader.cc",
|
||||||
|
"src/client/linux/handler/exception_handler.cc",
|
||||||
|
"src/client/linux/handler/minidump_descriptor.cc",
|
||||||
|
"src/client/linux/log/log.cc",
|
||||||
|
"src/client/linux/microdump_writer/microdump_writer.cc",
|
||||||
|
"src/client/linux/minidump_writer/linux_core_dumper.cc",
|
||||||
|
"src/client/linux/minidump_writer/linux_dumper.cc",
|
||||||
|
"src/client/linux/minidump_writer/linux_ptrace_dumper.cc",
|
||||||
|
"src/client/linux/minidump_writer/minidump_writer.cc",
|
||||||
|
"src/client/linux/minidump_writer/pe_file.cc",
|
||||||
|
};
|
||||||
|
|
||||||
|
const client_apple: []const []const u8 = &.{
|
||||||
|
"src/client/minidump_file_writer.cc",
|
||||||
|
"src/client/mac/handler/breakpad_nlist_64.cc",
|
||||||
|
"src/client/mac/handler/dynamic_images.cc",
|
||||||
|
"src/client/mac/handler/minidump_generator.cc",
|
||||||
|
};
|
||||||
|
|
||||||
|
const client_mac: []const []const u8 = &.{
|
||||||
|
"src/client/mac/handler/exception_handler.cc",
|
||||||
|
"src/client/mac/crash_generation/crash_generation_client.cc",
|
||||||
|
};
|
||||||
|
|
||||||
|
const client_ios: []const []const u8 = &.{
|
||||||
|
"src/client/ios/exception_handler_no_mach.cc",
|
||||||
|
"src/client/ios/handler/ios_exception_minidump_generator.mm",
|
||||||
|
"src/client/mac/crash_generation/ConfigFile.mm",
|
||||||
|
"src/client/mac/handler/protected_memory_allocator.cc",
|
||||||
|
};
|
13
pkg/breakpad/build.zig.zon
Normal file
13
pkg/breakpad/build.zig.zon
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
.{
|
||||||
|
.name = "breakpad",
|
||||||
|
.version = "0.1.0",
|
||||||
|
.paths = .{""},
|
||||||
|
.dependencies = .{
|
||||||
|
.breakpad = .{
|
||||||
|
.url = "https://github.com/getsentry/breakpad/archive/b99f444ba5f6b98cac261cbb391d8766b34a5918.tar.gz",
|
||||||
|
.hash = "12207fd37bb8251919c112dcdd8f616a491857b34a451f7e4486490077206dc2a1ea",
|
||||||
|
},
|
||||||
|
|
||||||
|
.apple_sdk = .{ .path = "../apple-sdk" },
|
||||||
|
},
|
||||||
|
}
|
5375
pkg/breakpad/vendor/third_party/lss/linux_syscall_support.h
vendored
Normal file
5375
pkg/breakpad/vendor/third_party/lss/linux_syscall_support.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
241
pkg/sentry/build.zig
Normal file
241
pkg/sentry/build.zig
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) !void {
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
const backend = b.option(Backend, "backend", "Backend") orelse .inproc;
|
||||||
|
const transport = b.option(Transport, "transport", "Transport") orelse .none;
|
||||||
|
|
||||||
|
const upstream = b.dependency("sentry", .{});
|
||||||
|
|
||||||
|
const module = b.addModule("sentry", .{
|
||||||
|
.root_source_file = b.path("main.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
module.addIncludePath(upstream.path("include"));
|
||||||
|
|
||||||
|
const lib = b.addStaticLibrary(.{
|
||||||
|
.name = "sentry",
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
lib.linkLibC();
|
||||||
|
lib.addIncludePath(upstream.path("include"));
|
||||||
|
lib.addIncludePath(upstream.path("src"));
|
||||||
|
if (target.result.isDarwin()) {
|
||||||
|
const apple_sdk = @import("apple_sdk");
|
||||||
|
try apple_sdk.addPaths(b, &lib.root_module);
|
||||||
|
try apple_sdk.addPaths(b, module);
|
||||||
|
}
|
||||||
|
|
||||||
|
var flags = std.ArrayList([]const u8).init(b.allocator);
|
||||||
|
defer flags.deinit();
|
||||||
|
try flags.appendSlice(&.{});
|
||||||
|
if (target.result.os.tag == .windows) {
|
||||||
|
try flags.appendSlice(&.{
|
||||||
|
"-DSENTRY_WITH_UNWINDER_DBGHELP",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
try flags.appendSlice(&.{
|
||||||
|
"-DSENTRY_WITH_UNWINDER_LIBBACKTRACE",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
switch (backend) {
|
||||||
|
.crashpad => try flags.append("-DSENTRY_BACKEND_CRASHPAD"),
|
||||||
|
.breakpad => try flags.append("-DSENTRY_BACKEND_BREAKPAD"),
|
||||||
|
.inproc => try flags.append("-DSENTRY_BACKEND_INPROC"),
|
||||||
|
.none => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
lib.addCSourceFiles(.{
|
||||||
|
.root = upstream.path(""),
|
||||||
|
.files = srcs,
|
||||||
|
.flags = flags.items,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Linux-only
|
||||||
|
if (target.result.os.tag == .linux) {
|
||||||
|
lib.addCSourceFiles(.{
|
||||||
|
.root = upstream.path(""),
|
||||||
|
.files = &.{
|
||||||
|
"vendor/stb_sprintf.c",
|
||||||
|
},
|
||||||
|
.flags = flags.items,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Symbolizer + Unwinder
|
||||||
|
if (target.result.os.tag == .windows) {
|
||||||
|
lib.addCSourceFiles(.{
|
||||||
|
.root = upstream.path(""),
|
||||||
|
.files = &.{
|
||||||
|
"src/sentry_windows_dbghelp.c",
|
||||||
|
"src/path/sentry_path_windows.c",
|
||||||
|
"src/symbolizer/sentry_symbolizer_windows.c",
|
||||||
|
"src/unwinder/sentry_unwinder_dbghelp.c",
|
||||||
|
},
|
||||||
|
.flags = flags.items,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
lib.addCSourceFiles(.{
|
||||||
|
.root = upstream.path(""),
|
||||||
|
.files = &.{
|
||||||
|
"src/sentry_unix_pageallocator.c",
|
||||||
|
"src/path/sentry_path_unix.c",
|
||||||
|
"src/symbolizer/sentry_symbolizer_unix.c",
|
||||||
|
"src/unwinder/sentry_unwinder_libbacktrace.c",
|
||||||
|
},
|
||||||
|
.flags = flags.items,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Module finder
|
||||||
|
switch (target.result.os.tag) {
|
||||||
|
.windows => lib.addCSourceFiles(.{
|
||||||
|
.root = upstream.path(""),
|
||||||
|
.files = &.{
|
||||||
|
"src/modulefinder/sentry_modulefinder_windows.c",
|
||||||
|
},
|
||||||
|
.flags = flags.items,
|
||||||
|
}),
|
||||||
|
|
||||||
|
.macos, .ios => lib.addCSourceFiles(.{
|
||||||
|
.root = upstream.path(""),
|
||||||
|
.files = &.{
|
||||||
|
"src/modulefinder/sentry_modulefinder_apple.c",
|
||||||
|
},
|
||||||
|
.flags = flags.items,
|
||||||
|
}),
|
||||||
|
|
||||||
|
.linux => lib.addCSourceFiles(.{
|
||||||
|
.root = upstream.path(""),
|
||||||
|
.files = &.{
|
||||||
|
"src/modulefinder/sentry_modulefinder_linux.c",
|
||||||
|
},
|
||||||
|
.flags = flags.items,
|
||||||
|
}),
|
||||||
|
|
||||||
|
.freestanding => {},
|
||||||
|
|
||||||
|
else => {
|
||||||
|
std.log.warn("target={} not supported", .{target.result.os.tag});
|
||||||
|
return error.UnsupportedTarget;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transport
|
||||||
|
switch (transport) {
|
||||||
|
.curl => lib.addCSourceFiles(.{
|
||||||
|
.root = upstream.path(""),
|
||||||
|
.files = &.{
|
||||||
|
"src/transports/sentry_transport_curl.c",
|
||||||
|
},
|
||||||
|
.flags = flags.items,
|
||||||
|
}),
|
||||||
|
|
||||||
|
.winhttp => lib.addCSourceFiles(.{
|
||||||
|
.root = upstream.path(""),
|
||||||
|
.files = &.{
|
||||||
|
"src/transports/sentry_transport_winhttp.c",
|
||||||
|
},
|
||||||
|
.flags = flags.items,
|
||||||
|
}),
|
||||||
|
|
||||||
|
.none => lib.addCSourceFiles(.{
|
||||||
|
.root = upstream.path(""),
|
||||||
|
.files = &.{
|
||||||
|
"src/transports/sentry_transport_none.c",
|
||||||
|
},
|
||||||
|
.flags = flags.items,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backend
|
||||||
|
switch (backend) {
|
||||||
|
.crashpad => lib.addCSourceFiles(.{
|
||||||
|
.root = upstream.path(""),
|
||||||
|
.files = &.{
|
||||||
|
"src/backends/sentry_backend_crashpad.cpp",
|
||||||
|
},
|
||||||
|
.flags = flags.items,
|
||||||
|
}),
|
||||||
|
|
||||||
|
.breakpad => {
|
||||||
|
lib.addCSourceFiles(.{
|
||||||
|
.root = upstream.path(""),
|
||||||
|
.files = &.{
|
||||||
|
"src/backends/sentry_backend_breakpad.cpp",
|
||||||
|
},
|
||||||
|
.flags = flags.items,
|
||||||
|
});
|
||||||
|
|
||||||
|
const breakpad_dep = b.dependency("breakpad", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
lib.linkLibrary(breakpad_dep.artifact("breakpad"));
|
||||||
|
|
||||||
|
// We need to add this because Sentry includes some breakpad
|
||||||
|
// headers that include this vendored file...
|
||||||
|
lib.addIncludePath(breakpad_dep.path("vendor"));
|
||||||
|
},
|
||||||
|
|
||||||
|
.inproc => lib.addCSourceFiles(.{
|
||||||
|
.root = upstream.path(""),
|
||||||
|
.files = &.{
|
||||||
|
"src/backends/sentry_backend_inproc.c",
|
||||||
|
},
|
||||||
|
.flags = flags.items,
|
||||||
|
}),
|
||||||
|
|
||||||
|
.none => lib.addCSourceFiles(.{
|
||||||
|
.root = upstream.path(""),
|
||||||
|
.files = &.{
|
||||||
|
"src/backends/sentry_backend_none.c",
|
||||||
|
},
|
||||||
|
.flags = flags.items,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
lib.installHeadersDirectory(
|
||||||
|
upstream.path("include"),
|
||||||
|
"",
|
||||||
|
.{ .include_extensions = &.{".h"} },
|
||||||
|
);
|
||||||
|
|
||||||
|
b.installArtifact(lib);
|
||||||
|
}
|
||||||
|
|
||||||
|
const srcs: []const []const u8 = &.{
|
||||||
|
"src/sentry_alloc.c",
|
||||||
|
"src/sentry_backend.c",
|
||||||
|
"src/sentry_core.c",
|
||||||
|
"src/sentry_database.c",
|
||||||
|
"src/sentry_envelope.c",
|
||||||
|
"src/sentry_info.c",
|
||||||
|
"src/sentry_json.c",
|
||||||
|
"src/sentry_logger.c",
|
||||||
|
"src/sentry_options.c",
|
||||||
|
"src/sentry_os.c",
|
||||||
|
"src/sentry_random.c",
|
||||||
|
"src/sentry_ratelimiter.c",
|
||||||
|
"src/sentry_scope.c",
|
||||||
|
"src/sentry_session.c",
|
||||||
|
"src/sentry_slice.c",
|
||||||
|
"src/sentry_string.c",
|
||||||
|
"src/sentry_sync.c",
|
||||||
|
"src/sentry_transport.c",
|
||||||
|
"src/sentry_utils.c",
|
||||||
|
"src/sentry_uuid.c",
|
||||||
|
"src/sentry_value.c",
|
||||||
|
"src/sentry_tracing.c",
|
||||||
|
"src/path/sentry_path.c",
|
||||||
|
"src/transports/sentry_disk_transport.c",
|
||||||
|
"src/transports/sentry_function_transport.c",
|
||||||
|
"src/unwinder/sentry_unwinder.c",
|
||||||
|
"vendor/mpack.c",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Backend = enum { crashpad, breakpad, inproc, none };
|
||||||
|
pub const Transport = enum { curl, winhttp, none };
|
14
pkg/sentry/build.zig.zon
Normal file
14
pkg/sentry/build.zig.zon
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
.{
|
||||||
|
.name = "sentry",
|
||||||
|
.version = "0.7.8",
|
||||||
|
.paths = .{""},
|
||||||
|
.dependencies = .{
|
||||||
|
.sentry = .{
|
||||||
|
.url = "https://github.com/getsentry/sentry-native/archive/refs/tags/0.7.8.tar.gz",
|
||||||
|
.hash = "1220446be831adcca918167647c06c7b825849fa3fba5f22da394667974537a9c77e",
|
||||||
|
},
|
||||||
|
|
||||||
|
.apple_sdk = .{ .path = "../apple-sdk" },
|
||||||
|
.breakpad = .{ .path = "../breakpad" },
|
||||||
|
},
|
||||||
|
}
|
3
pkg/sentry/c.zig
Normal file
3
pkg/sentry/c.zig
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub const c = @cImport({
|
||||||
|
@cInclude("sentry.h");
|
||||||
|
});
|
31
pkg/sentry/envelope.zig
Normal file
31
pkg/sentry/envelope.zig
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const c = @import("c.zig").c;
|
||||||
|
const Value = @import("value.zig").Value;
|
||||||
|
|
||||||
|
/// sentry_envelope_t
|
||||||
|
pub const Envelope = opaque {
|
||||||
|
pub fn deinit(self: *Envelope) void {
|
||||||
|
c.sentry_envelope_free(@ptrCast(self));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeToFile(self: *Envelope, path: []const u8) !void {
|
||||||
|
if (c.sentry_envelope_write_to_file_n(
|
||||||
|
@ptrCast(self),
|
||||||
|
path.ptr,
|
||||||
|
path.len,
|
||||||
|
) != 0) return error.WriteFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize(self: *Envelope) []u8 {
|
||||||
|
var len: usize = 0;
|
||||||
|
const ptr = c.sentry_envelope_serialize(@ptrCast(self), &len).?;
|
||||||
|
return ptr[0..len];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn event(self: *Envelope) ?Value {
|
||||||
|
const val: Value = .{ .value = c.sentry_envelope_get_event(@ptrCast(self)) };
|
||||||
|
if (val.isNull()) return null;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
};
|
8
pkg/sentry/level.zig
Normal file
8
pkg/sentry/level.zig
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/// sentry_level_t
|
||||||
|
pub const Level = enum(c_int) {
|
||||||
|
debug = -1,
|
||||||
|
info = 0,
|
||||||
|
warning = 1,
|
||||||
|
err = 2,
|
||||||
|
fatal = 3,
|
||||||
|
};
|
35
pkg/sentry/main.zig
Normal file
35
pkg/sentry/main.zig
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
pub const c = @import("c.zig").c;
|
||||||
|
|
||||||
|
const transport = @import("transport.zig");
|
||||||
|
|
||||||
|
pub const Envelope = @import("envelope.zig").Envelope;
|
||||||
|
pub const Level = @import("level.zig").Level;
|
||||||
|
pub const Transport = transport.Transport;
|
||||||
|
pub const Value = @import("value.zig").Value;
|
||||||
|
pub const UUID = @import("uuid.zig").UUID;
|
||||||
|
|
||||||
|
pub fn captureEvent(value: Value) ?UUID {
|
||||||
|
const uuid: UUID = .{ .value = c.sentry_capture_event(value.value) };
|
||||||
|
if (uuid.isNil()) return null;
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setContext(key: []const u8, value: Value) void {
|
||||||
|
c.sentry_set_context_n(key.ptr, key.len, value.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeContext(key: []const u8) void {
|
||||||
|
c.sentry_remove_context_n(key.ptr, key.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setTag(key: []const u8, value: []const u8) void {
|
||||||
|
c.sentry_set_tag_n(key.ptr, key.len, value.ptr, value.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn free(ptr: *anyopaque) void {
|
||||||
|
c.sentry_free(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
@import("std").testing.refAllDecls(@This());
|
||||||
|
}
|
26
pkg/sentry/transport.zig
Normal file
26
pkg/sentry/transport.zig
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const c = @import("c.zig").c;
|
||||||
|
const Envelope = @import("envelope.zig").Envelope;
|
||||||
|
|
||||||
|
/// sentry_transport_t
|
||||||
|
pub const Transport = opaque {
|
||||||
|
pub const SendFunc = *const fn (envelope: *Envelope, state: ?*anyopaque) callconv(.C) void;
|
||||||
|
pub const FreeFunc = *const fn (state: ?*anyopaque) callconv(.C) void;
|
||||||
|
|
||||||
|
pub fn init(f: SendFunc) *Transport {
|
||||||
|
return @ptrCast(c.sentry_transport_new(@ptrCast(f)).?);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Transport) void {
|
||||||
|
c.sentry_transport_free(@ptrCast(self));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setState(self: *Transport, state: ?*anyopaque) void {
|
||||||
|
c.sentry_transport_set_state(@ptrCast(self), state);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setStateFreeFunc(self: *Transport, f: FreeFunc) void {
|
||||||
|
c.sentry_transport_set_free_func(@ptrCast(self), f);
|
||||||
|
}
|
||||||
|
};
|
22
pkg/sentry/uuid.zig
Normal file
22
pkg/sentry/uuid.zig
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const c = @import("c.zig").c;
|
||||||
|
|
||||||
|
/// sentry_uuid_t
|
||||||
|
pub const UUID = struct {
|
||||||
|
value: c.sentry_uuid_t,
|
||||||
|
|
||||||
|
pub fn init() UUID {
|
||||||
|
return .{ .value = c.sentry_uuid_new_v4() };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isNil(self: UUID) bool {
|
||||||
|
return c.sentry_uuid_is_nil(&self.value) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn string(self: UUID) [36:0]u8 {
|
||||||
|
var buf: [36:0]u8 = undefined;
|
||||||
|
c.sentry_uuid_as_string(&self.value, &buf);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
};
|
75
pkg/sentry/value.zig
Normal file
75
pkg/sentry/value.zig
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const c = @import("c.zig").c;
|
||||||
|
const Level = @import("level.zig").Level;
|
||||||
|
|
||||||
|
/// sentry_value_t
|
||||||
|
pub const Value = struct {
|
||||||
|
/// The underlying value. This is a union that could be represented with
|
||||||
|
/// an extern union but I don't want to risk C ABI issues so we wrap it
|
||||||
|
/// in a struct.
|
||||||
|
value: c.sentry_value_t,
|
||||||
|
|
||||||
|
pub fn initMessageEvent(
|
||||||
|
level: Level,
|
||||||
|
logger: ?[]const u8,
|
||||||
|
message: []const u8,
|
||||||
|
) Value {
|
||||||
|
return .{ .value = c.sentry_value_new_message_event_n(
|
||||||
|
@intFromEnum(level),
|
||||||
|
if (logger) |v| v.ptr else null,
|
||||||
|
if (logger) |v| v.len else 0,
|
||||||
|
message.ptr,
|
||||||
|
message.len,
|
||||||
|
) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initObject() Value {
|
||||||
|
return .{ .value = c.sentry_value_new_object() };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initString(value: []const u8) Value {
|
||||||
|
return .{ .value = c.sentry_value_new_string_n(value.ptr, value.len) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initBool(value: bool) Value {
|
||||||
|
return .{ .value = c.sentry_value_new_bool(@intFromBool(value)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initInt32(value: i32) Value {
|
||||||
|
return .{ .value = c.sentry_value_new_int32(value) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decref(self: Value) void {
|
||||||
|
c.sentry_value_decref(self.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn incref(self: Value) Value {
|
||||||
|
c.sentry_value_incref(self.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isNull(self: Value) bool {
|
||||||
|
return c.sentry_value_is_null(self.value) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// sentry_value_set_by_key_n
|
||||||
|
pub fn set(self: Value, key: []const u8, value: Value) void {
|
||||||
|
_ = c.sentry_value_set_by_key_n(
|
||||||
|
self.value,
|
||||||
|
key.ptr,
|
||||||
|
key.len,
|
||||||
|
value.value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// sentry_value_set_by_key_n
|
||||||
|
pub fn get(self: Value, key: []const u8) ?Value {
|
||||||
|
const val: Value = .{ .value = c.sentry_value_get_by_key_n(
|
||||||
|
self.value,
|
||||||
|
key.ptr,
|
||||||
|
key.len,
|
||||||
|
) };
|
||||||
|
if (val.isNull()) return null;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
};
|
@ -22,6 +22,7 @@ const Allocator = std.mem.Allocator;
|
|||||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
const global_state = &@import("global.zig").state;
|
const global_state = &@import("global.zig").state;
|
||||||
const oni = @import("oniguruma");
|
const oni = @import("oniguruma");
|
||||||
|
const crash = @import("crash/main.zig");
|
||||||
const unicode = @import("unicode/main.zig");
|
const unicode = @import("unicode/main.zig");
|
||||||
const renderer = @import("renderer.zig");
|
const renderer = @import("renderer.zig");
|
||||||
const termio = @import("termio.zig");
|
const termio = @import("termio.zig");
|
||||||
@ -1218,6 +1219,10 @@ fn queueRender(self: *Surface) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn sizeCallback(self: *Surface, size: apprt.SurfaceSize) !void {
|
pub fn sizeCallback(self: *Surface, size: apprt.SurfaceSize) !void {
|
||||||
|
// Crash metadata in case we crash in here
|
||||||
|
crash.sentry.thread_state = self.crashThreadState();
|
||||||
|
defer crash.sentry.thread_state = null;
|
||||||
|
|
||||||
const new_screen_size: renderer.ScreenSize = .{
|
const new_screen_size: renderer.ScreenSize = .{
|
||||||
.width = size.width,
|
.width = size.width,
|
||||||
.height = size.height,
|
.height = size.height,
|
||||||
@ -1274,6 +1279,10 @@ fn resize(self: *Surface, size: renderer.ScreenSize) !void {
|
|||||||
pub fn preeditCallback(self: *Surface, preedit_: ?[]const u8) !void {
|
pub fn preeditCallback(self: *Surface, preedit_: ?[]const u8) !void {
|
||||||
// log.debug("text preeditCallback value={any}", .{preedit_});
|
// log.debug("text preeditCallback value={any}", .{preedit_});
|
||||||
|
|
||||||
|
// Crash metadata in case we crash in here
|
||||||
|
crash.sentry.thread_state = self.crashThreadState();
|
||||||
|
defer crash.sentry.thread_state = null;
|
||||||
|
|
||||||
self.renderer_state.mutex.lock();
|
self.renderer_state.mutex.lock();
|
||||||
defer self.renderer_state.mutex.unlock();
|
defer self.renderer_state.mutex.unlock();
|
||||||
|
|
||||||
@ -1335,6 +1344,10 @@ pub fn keyCallback(
|
|||||||
) !InputEffect {
|
) !InputEffect {
|
||||||
// log.debug("text keyCallback event={}", .{event});
|
// log.debug("text keyCallback event={}", .{event});
|
||||||
|
|
||||||
|
// Crash metadata in case we crash in here
|
||||||
|
crash.sentry.thread_state = self.crashThreadState();
|
||||||
|
defer crash.sentry.thread_state = null;
|
||||||
|
|
||||||
// Setup our inspector event if we have an inspector.
|
// Setup our inspector event if we have an inspector.
|
||||||
var insp_ev: ?inspector.key.Event = if (self.inspector != null) ev: {
|
var insp_ev: ?inspector.key.Event = if (self.inspector != null) ev: {
|
||||||
var copy = event;
|
var copy = event;
|
||||||
@ -1729,6 +1742,10 @@ fn encodeKey(
|
|||||||
/// if bracketed mode is on this will do a bracketed paste. Otherwise,
|
/// if bracketed mode is on this will do a bracketed paste. Otherwise,
|
||||||
/// this will filter newlines to '\r'.
|
/// this will filter newlines to '\r'.
|
||||||
pub fn textCallback(self: *Surface, text: []const u8) !void {
|
pub fn textCallback(self: *Surface, text: []const u8) !void {
|
||||||
|
// Crash metadata in case we crash in here
|
||||||
|
crash.sentry.thread_state = self.crashThreadState();
|
||||||
|
defer crash.sentry.thread_state = null;
|
||||||
|
|
||||||
try self.completeClipboardPaste(text, true);
|
try self.completeClipboardPaste(text, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1736,6 +1753,10 @@ pub fn textCallback(self: *Surface, text: []const u8) !void {
|
|||||||
/// of focus state. This is used to pause rendering when the surface
|
/// of focus state. This is used to pause rendering when the surface
|
||||||
/// is not visible, and also re-render when it becomes visible again.
|
/// is not visible, and also re-render when it becomes visible again.
|
||||||
pub fn occlusionCallback(self: *Surface, visible: bool) !void {
|
pub fn occlusionCallback(self: *Surface, visible: bool) !void {
|
||||||
|
// Crash metadata in case we crash in here
|
||||||
|
crash.sentry.thread_state = self.crashThreadState();
|
||||||
|
defer crash.sentry.thread_state = null;
|
||||||
|
|
||||||
_ = self.renderer_thread.mailbox.push(.{
|
_ = self.renderer_thread.mailbox.push(.{
|
||||||
.visible = visible,
|
.visible = visible,
|
||||||
}, .{ .forever = {} });
|
}, .{ .forever = {} });
|
||||||
@ -1743,6 +1764,10 @@ pub fn occlusionCallback(self: *Surface, visible: bool) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn focusCallback(self: *Surface, focused: bool) !void {
|
pub fn focusCallback(self: *Surface, focused: bool) !void {
|
||||||
|
// Crash metadata in case we crash in here
|
||||||
|
crash.sentry.thread_state = self.crashThreadState();
|
||||||
|
defer crash.sentry.thread_state = null;
|
||||||
|
|
||||||
// Notify our render thread of the new state
|
// Notify our render thread of the new state
|
||||||
_ = self.renderer_thread.mailbox.push(.{
|
_ = self.renderer_thread.mailbox.push(.{
|
||||||
.focus = focused,
|
.focus = focused,
|
||||||
@ -1813,6 +1838,10 @@ pub fn focusCallback(self: *Surface, focused: bool) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn refreshCallback(self: *Surface) !void {
|
pub fn refreshCallback(self: *Surface) !void {
|
||||||
|
// Crash metadata in case we crash in here
|
||||||
|
crash.sentry.thread_state = self.crashThreadState();
|
||||||
|
defer crash.sentry.thread_state = null;
|
||||||
|
|
||||||
// The point of this callback is to schedule a render, so do that.
|
// The point of this callback is to schedule a render, so do that.
|
||||||
try self.queueRender();
|
try self.queueRender();
|
||||||
}
|
}
|
||||||
@ -1825,6 +1854,10 @@ pub fn scrollCallback(
|
|||||||
) !void {
|
) !void {
|
||||||
// log.info("SCROLL: xoff={} yoff={} mods={}", .{ xoff, yoff, scroll_mods });
|
// log.info("SCROLL: xoff={} yoff={} mods={}", .{ xoff, yoff, scroll_mods });
|
||||||
|
|
||||||
|
// Crash metadata in case we crash in here
|
||||||
|
crash.sentry.thread_state = self.crashThreadState();
|
||||||
|
defer crash.sentry.thread_state = null;
|
||||||
|
|
||||||
// Always show the mouse again if it is hidden
|
// Always show the mouse again if it is hidden
|
||||||
if (self.mouse.hidden) self.showMouse();
|
if (self.mouse.hidden) self.showMouse();
|
||||||
|
|
||||||
@ -1980,6 +2013,10 @@ pub fn scrollCallback(
|
|||||||
/// This is called when the content scale of the surface changes. The surface
|
/// This is called when the content scale of the surface changes. The surface
|
||||||
/// can then update any DPI-sensitive state.
|
/// can then update any DPI-sensitive state.
|
||||||
pub fn contentScaleCallback(self: *Surface, content_scale: apprt.ContentScale) !void {
|
pub fn contentScaleCallback(self: *Surface, content_scale: apprt.ContentScale) !void {
|
||||||
|
// Crash metadata in case we crash in here
|
||||||
|
crash.sentry.thread_state = self.crashThreadState();
|
||||||
|
defer crash.sentry.thread_state = null;
|
||||||
|
|
||||||
// Calculate the new DPI
|
// Calculate the new DPI
|
||||||
const x_dpi = content_scale.x * font.face.default_dpi;
|
const x_dpi = content_scale.x * font.face.default_dpi;
|
||||||
const y_dpi = content_scale.y * font.face.default_dpi;
|
const y_dpi = content_scale.y * font.face.default_dpi;
|
||||||
@ -2293,6 +2330,10 @@ pub fn mouseButtonCallback(
|
|||||||
button: input.MouseButton,
|
button: input.MouseButton,
|
||||||
mods: input.Mods,
|
mods: input.Mods,
|
||||||
) !bool {
|
) !bool {
|
||||||
|
// Crash metadata in case we crash in here
|
||||||
|
crash.sentry.thread_state = self.crashThreadState();
|
||||||
|
defer crash.sentry.thread_state = null;
|
||||||
|
|
||||||
// log.debug("mouse action={} button={} mods={}", .{ action, button, mods });
|
// log.debug("mouse action={} button={} mods={}", .{ action, button, mods });
|
||||||
|
|
||||||
// If we have an inspector, we always queue a render
|
// If we have an inspector, we always queue a render
|
||||||
@ -2790,6 +2831,10 @@ pub fn mousePressureCallback(
|
|||||||
stage: input.MousePressureStage,
|
stage: input.MousePressureStage,
|
||||||
pressure: f64,
|
pressure: f64,
|
||||||
) !void {
|
) !void {
|
||||||
|
// Crash metadata in case we crash in here
|
||||||
|
crash.sentry.thread_state = self.crashThreadState();
|
||||||
|
defer crash.sentry.thread_state = null;
|
||||||
|
|
||||||
// We don't currently use the pressure value for anything. In the
|
// We don't currently use the pressure value for anything. In the
|
||||||
// future, we could report this to applications using new mouse
|
// future, we could report this to applications using new mouse
|
||||||
// events or utilize it for some custom UI.
|
// events or utilize it for some custom UI.
|
||||||
@ -2824,6 +2869,10 @@ pub fn cursorPosCallback(
|
|||||||
self: *Surface,
|
self: *Surface,
|
||||||
pos: apprt.CursorPos,
|
pos: apprt.CursorPos,
|
||||||
) !void {
|
) !void {
|
||||||
|
// Crash metadata in case we crash in here
|
||||||
|
crash.sentry.thread_state = self.crashThreadState();
|
||||||
|
defer crash.sentry.thread_state = null;
|
||||||
|
|
||||||
// Always show the mouse again if it is hidden
|
// Always show the mouse again if it is hidden
|
||||||
if (self.mouse.hidden) self.showMouse();
|
if (self.mouse.hidden) self.showMouse();
|
||||||
|
|
||||||
@ -3229,6 +3278,10 @@ fn dragLeftClickBefore(
|
|||||||
/// Call to notify Ghostty that the color scheme for the terminal has
|
/// Call to notify Ghostty that the color scheme for the terminal has
|
||||||
/// changed.
|
/// changed.
|
||||||
pub fn colorSchemeCallback(self: *Surface, scheme: apprt.ColorScheme) !void {
|
pub fn colorSchemeCallback(self: *Surface, scheme: apprt.ColorScheme) !void {
|
||||||
|
// Crash metadata in case we crash in here
|
||||||
|
crash.sentry.thread_state = self.crashThreadState();
|
||||||
|
defer crash.sentry.thread_state = null;
|
||||||
|
|
||||||
// If our scheme didn't change, then we don't do anything.
|
// If our scheme didn't change, then we don't do anything.
|
||||||
if (self.color_scheme == scheme) return;
|
if (self.color_scheme == scheme) return;
|
||||||
|
|
||||||
@ -3673,6 +3726,20 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
|||||||
|
|
||||||
.quit => try self.app.setQuit(),
|
.quit => try self.app.setQuit(),
|
||||||
|
|
||||||
|
.crash => |location| switch (location) {
|
||||||
|
.main => @panic("crash binding action, crashing intentionally"),
|
||||||
|
|
||||||
|
.render => {
|
||||||
|
_ = self.renderer_thread.mailbox.push(.{ .crash = {} }, .{ .forever = {} });
|
||||||
|
self.queueRender() catch |err| {
|
||||||
|
// Not a big deal if this fails.
|
||||||
|
log.warn("failed to notify renderer of crash message err={}", .{err});
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
.io => self.io.queueMessage(.{ .crash = {} }, .unlocked),
|
||||||
|
},
|
||||||
|
|
||||||
.adjust_selection => |direction| {
|
.adjust_selection => |direction| {
|
||||||
self.renderer_state.mutex.lock();
|
self.renderer_state.mutex.lock();
|
||||||
defer self.renderer_state.mutex.unlock();
|
defer self.renderer_state.mutex.unlock();
|
||||||
@ -4083,6 +4150,13 @@ fn showDesktopNotification(self: *Surface, title: [:0]const u8, body: [:0]const
|
|||||||
try self.rt_surface.showDesktopNotification(title, body);
|
try self.rt_surface.showDesktopNotification(title, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn crashThreadState(self: *Surface) crash.sentry.ThreadState {
|
||||||
|
return .{
|
||||||
|
.type = .main,
|
||||||
|
.surface = self,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub const face_ttf = @embedFile("font/res/JetBrainsMono-Regular.ttf");
|
pub const face_ttf = @embedFile("font/res/JetBrainsMono-Regular.ttf");
|
||||||
pub const face_bold_ttf = @embedFile("font/res/JetBrainsMono-Bold.ttf");
|
pub const face_bold_ttf = @embedFile("font/res/JetBrainsMono-Bold.ttf");
|
||||||
pub const face_emoji_ttf = @embedFile("font/res/NotoColorEmoji.ttf");
|
pub const face_emoji_ttf = @embedFile("font/res/NotoColorEmoji.ttf");
|
||||||
|
16
src/crash/main.zig
Normal file
16
src/crash/main.zig
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
//! The crash package contains all the logic around crash handling,
|
||||||
|
//! whether that's setting up the system to catch crashes (Sentry client),
|
||||||
|
//! introspecting crash reports, writing crash reports to disk, etc.
|
||||||
|
|
||||||
|
const sentry_envelope = @import("sentry_envelope.zig");
|
||||||
|
|
||||||
|
pub const sentry = @import("sentry.zig");
|
||||||
|
pub const Envelope = sentry_envelope.Envelope;
|
||||||
|
|
||||||
|
// The main init/deinit functions for global state.
|
||||||
|
pub const init = sentry.init;
|
||||||
|
pub const deinit = sentry.deinit;
|
||||||
|
|
||||||
|
test {
|
||||||
|
@import("std").testing.refAllDecls(@This());
|
||||||
|
}
|
279
src/crash/sentry.zig
Normal file
279
src/crash/sentry.zig
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const build_config = @import("../build_config.zig");
|
||||||
|
const sentry = @import("sentry");
|
||||||
|
const internal_os = @import("../os/main.zig");
|
||||||
|
const crash = @import("main.zig");
|
||||||
|
const state = &@import("../global.zig").state;
|
||||||
|
const Surface = @import("../Surface.zig");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.sentry);
|
||||||
|
|
||||||
|
/// The global state for the Sentry SDK. This is unavoidable since crash
|
||||||
|
/// handling is a global process-wide thing.
|
||||||
|
var init_thread: ?std.Thread = null;
|
||||||
|
|
||||||
|
/// Thread-local state that can be set by thread main functions so that
|
||||||
|
/// crashes have more context.
|
||||||
|
///
|
||||||
|
/// This is a hack over Sentry native SDK limitations. The native SDK has
|
||||||
|
/// one global scope for all threads and no support for thread-local scopes.
|
||||||
|
/// This means that if we want to set thread-specific data we have to do it
|
||||||
|
/// on our own in the on crash callback.
|
||||||
|
pub const ThreadState = struct {
|
||||||
|
/// Thread type, used to tag the crash
|
||||||
|
type: Type,
|
||||||
|
|
||||||
|
/// The surface that this thread is attached to.
|
||||||
|
surface: *Surface,
|
||||||
|
|
||||||
|
pub const Type = enum { main, renderer, io };
|
||||||
|
};
|
||||||
|
|
||||||
|
/// See ThreadState. This should only ever be set by the owner of the
|
||||||
|
/// thread entry function.
|
||||||
|
pub threadlocal var thread_state: ?ThreadState = null;
|
||||||
|
|
||||||
|
/// Process-wide initialization of our Sentry client.
|
||||||
|
///
|
||||||
|
/// This should only be called from one thread, and deinit should be called
|
||||||
|
/// from the same thread that calls init to avoid data races.
|
||||||
|
///
|
||||||
|
/// PRIVACY NOTE: I want to make it very clear that Ghostty by default does
|
||||||
|
/// NOT send any data over the network. We use the Sentry native SDK to collect
|
||||||
|
/// crash reports and logs, but we only store them locally (see Transport).
|
||||||
|
/// It is up to the user to grab the logs and manually send them to us
|
||||||
|
/// (or they own Sentry instance) if they want to.
|
||||||
|
pub fn init(gpa: Allocator) !void {
|
||||||
|
// Not supported on Windows currently, doesn't build.
|
||||||
|
if (comptime builtin.os.tag == .windows) return;
|
||||||
|
|
||||||
|
// const start = try std.time.Instant.now();
|
||||||
|
// const start_micro = std.time.microTimestamp();
|
||||||
|
// defer {
|
||||||
|
// const end = std.time.Instant.now() catch unreachable;
|
||||||
|
// // "[updateFrame critical time] <START us>\t<TIME_TAKEN us>"
|
||||||
|
// std.log.err("[sentry init time] start={}us duration={}ns", .{ start_micro, end.since(start) / std.time.ns_per_us });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Must only start once
|
||||||
|
assert(init_thread == null);
|
||||||
|
|
||||||
|
// We use a thread for initializing Sentry because initialization takes
|
||||||
|
// ~2k ns on my M3 Max. That's not a LOT of time but it's enough to be
|
||||||
|
// 90% of our pre-App startup time. Everything Sentry is doing initially
|
||||||
|
// is safe to do on a separate thread and fast enough that its very
|
||||||
|
// likely to be done before a crash occurs.
|
||||||
|
const thr = try std.Thread.spawn(
|
||||||
|
.{},
|
||||||
|
initThread,
|
||||||
|
.{gpa},
|
||||||
|
);
|
||||||
|
thr.setName("sentry-init") catch {};
|
||||||
|
init_thread = thr;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initThread(gpa: Allocator) !void {
|
||||||
|
var arena = std.heap.ArenaAllocator.init(gpa);
|
||||||
|
defer arena.deinit();
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
|
const transport = sentry.Transport.init(&Transport.send);
|
||||||
|
errdefer transport.deinit();
|
||||||
|
|
||||||
|
const opts = sentry.c.sentry_options_new();
|
||||||
|
errdefer sentry.c.sentry_options_free(opts);
|
||||||
|
sentry.c.sentry_options_set_release_n(
|
||||||
|
opts,
|
||||||
|
build_config.version_string.ptr,
|
||||||
|
build_config.version_string.len,
|
||||||
|
);
|
||||||
|
sentry.c.sentry_options_set_transport(opts, @ptrCast(transport));
|
||||||
|
|
||||||
|
// Set our crash callback. See beforeSend for more details on what we
|
||||||
|
// do here and why we use this.
|
||||||
|
sentry.c.sentry_options_set_before_send(opts, beforeSend, null);
|
||||||
|
|
||||||
|
// Determine the Sentry cache directory.
|
||||||
|
const cache_dir = try internal_os.xdg.cache(alloc, .{ .subdir = "ghostty/sentry" });
|
||||||
|
sentry.c.sentry_options_set_database_path_n(
|
||||||
|
opts,
|
||||||
|
cache_dir.ptr,
|
||||||
|
cache_dir.len,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (comptime builtin.mode == .Debug) {
|
||||||
|
// Debug logging for Sentry
|
||||||
|
sentry.c.sentry_options_set_debug(opts, @intFromBool(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
if (sentry.c.sentry_init(opts) != 0) return error.SentryInitFailed;
|
||||||
|
|
||||||
|
// Setup some basic tags that we always want present
|
||||||
|
sentry.setTag("build-mode", build_config.mode_string);
|
||||||
|
sentry.setTag("app-runtime", @tagName(build_config.app_runtime));
|
||||||
|
sentry.setTag("font-backend", @tagName(build_config.font_backend));
|
||||||
|
sentry.setTag("renderer", @tagName(build_config.renderer));
|
||||||
|
|
||||||
|
// Log some information about sentry
|
||||||
|
log.debug("sentry initialized database={s}", .{cache_dir});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process-wide deinitialization of our Sentry client. This ensures all
|
||||||
|
/// our data is flushed.
|
||||||
|
pub fn deinit() void {
|
||||||
|
if (comptime builtin.os.tag == .windows) return;
|
||||||
|
|
||||||
|
// If we're still initializing then wait for init to finish. This
|
||||||
|
// is highly unlikely since init is a very fast operation but we want
|
||||||
|
// to avoid the possibility.
|
||||||
|
const thr = init_thread orelse return;
|
||||||
|
thr.join();
|
||||||
|
_ = sentry.c.sentry_close();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn beforeSend(
|
||||||
|
event_val: sentry.c.sentry_value_t,
|
||||||
|
_: ?*anyopaque,
|
||||||
|
_: ?*anyopaque,
|
||||||
|
) callconv(.C) sentry.c.sentry_value_t {
|
||||||
|
// The native SDK at the time of writing doesn't support thread-local
|
||||||
|
// scopes. The full SDK has one global scope. So we use the beforeSend
|
||||||
|
// handler to set thread-specific data such as window size, grid size,
|
||||||
|
// etc. that we can use to debug crashes.
|
||||||
|
|
||||||
|
// If we don't have thread state we can't reliably determine
|
||||||
|
// metadata such as surface dimensions. In the future we can probably
|
||||||
|
// drop full app state (all surfaces, all windows, etc.).
|
||||||
|
const thr_state = thread_state orelse {
|
||||||
|
log.debug("no thread state, skipping crash metadata", .{});
|
||||||
|
return event_val;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get our event contexts. At this point Sentry has already merged
|
||||||
|
// all the contexts so we should have this key. If not, we create it.
|
||||||
|
const event: sentry.Value = .{ .value = event_val };
|
||||||
|
const contexts = event.get("contexts") orelse contexts: {
|
||||||
|
const obj = sentry.Value.initObject();
|
||||||
|
event.set("contexts", obj);
|
||||||
|
break :contexts obj;
|
||||||
|
};
|
||||||
|
const tags = event.get("tags") orelse tags: {
|
||||||
|
const obj = sentry.Value.initObject();
|
||||||
|
event.set("tags", obj);
|
||||||
|
break :tags obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Store our thread type
|
||||||
|
tags.set("thread-type", sentry.Value.initString(@tagName(thr_state.type)));
|
||||||
|
|
||||||
|
// Read the surface data. This is likely unsafe because on a crash
|
||||||
|
// other threads can continue running. We don't have race-safe way to
|
||||||
|
// access this data so this might be corrupted but it's most likely fine.
|
||||||
|
{
|
||||||
|
const obj = sentry.Value.initObject();
|
||||||
|
errdefer obj.decref();
|
||||||
|
const surface = thr_state.surface;
|
||||||
|
obj.set(
|
||||||
|
"screen-width",
|
||||||
|
sentry.Value.initInt32(std.math.cast(i32, surface.screen_size.width) orelse -1),
|
||||||
|
);
|
||||||
|
obj.set(
|
||||||
|
"screen-height",
|
||||||
|
sentry.Value.initInt32(std.math.cast(i32, surface.screen_size.height) orelse -1),
|
||||||
|
);
|
||||||
|
obj.set(
|
||||||
|
"grid-columns",
|
||||||
|
sentry.Value.initInt32(std.math.cast(i32, surface.grid_size.columns) orelse -1),
|
||||||
|
);
|
||||||
|
obj.set(
|
||||||
|
"grid-rows",
|
||||||
|
sentry.Value.initInt32(std.math.cast(i32, surface.grid_size.rows) orelse -1),
|
||||||
|
);
|
||||||
|
obj.set(
|
||||||
|
"cell-width",
|
||||||
|
sentry.Value.initInt32(std.math.cast(i32, surface.cell_size.width) orelse -1),
|
||||||
|
);
|
||||||
|
obj.set(
|
||||||
|
"cell-height",
|
||||||
|
sentry.Value.initInt32(std.math.cast(i32, surface.cell_size.height) orelse -1),
|
||||||
|
);
|
||||||
|
|
||||||
|
contexts.set("Dimensions", obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
return event_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Transport = struct {
|
||||||
|
pub fn send(envelope: *sentry.Envelope, ud: ?*anyopaque) callconv(.C) void {
|
||||||
|
_ = ud;
|
||||||
|
defer envelope.deinit();
|
||||||
|
|
||||||
|
// Call our internal impl. If it fails there is nothing we can do
|
||||||
|
// but log to the user.
|
||||||
|
sendInternal(envelope) catch |err| {
|
||||||
|
log.warn("failed to persist crash report err={}", .{err});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implementation of send but we can use Zig errors.
|
||||||
|
fn sendInternal(envelope: *sentry.Envelope) !void {
|
||||||
|
var arena = std.heap.ArenaAllocator.init(state.alloc);
|
||||||
|
defer arena.deinit();
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
|
// Parse into an envelope structure
|
||||||
|
const json = envelope.serialize();
|
||||||
|
defer sentry.free(@ptrCast(json.ptr));
|
||||||
|
var parsed: crash.Envelope = parsed: {
|
||||||
|
var fbs = std.io.fixedBufferStream(json);
|
||||||
|
break :parsed try crash.Envelope.parse(alloc, fbs.reader());
|
||||||
|
};
|
||||||
|
defer parsed.deinit();
|
||||||
|
|
||||||
|
// If our envelope doesn't have an event then we don't do anything.
|
||||||
|
// To figure this out we first encode it into a string, parse it,
|
||||||
|
// and check if it has an event. Kind of wasteful but the best
|
||||||
|
// option we have at the time of writing this since the C API doesn't
|
||||||
|
// expose this information.
|
||||||
|
if (try shouldDiscard(&parsed)) {
|
||||||
|
log.info("sentry envelope does not contain crash, discarding", .{});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a UUID for this envelope. The envelope DOES have an event_id
|
||||||
|
// header but I don't think there is any public API way to get it
|
||||||
|
// afaict so we generate a new UUID for the filename just so we don't
|
||||||
|
// conflict.
|
||||||
|
const uuid = sentry.UUID.init();
|
||||||
|
|
||||||
|
// Get our XDG state directory where we'll store the crash reports.
|
||||||
|
// This directory must exist for writing to work.
|
||||||
|
const crash_dir = try internal_os.xdg.state(alloc, .{ .subdir = "ghostty/crash" });
|
||||||
|
try std.fs.cwd().makePath(crash_dir);
|
||||||
|
|
||||||
|
// Build our final path and write to it.
|
||||||
|
const path = try std.fs.path.join(alloc, &.{
|
||||||
|
crash_dir,
|
||||||
|
try std.fmt.allocPrint(alloc, "{s}.ghosttycrash", .{uuid.string()}),
|
||||||
|
});
|
||||||
|
const file = try std.fs.cwd().createFile(path, .{});
|
||||||
|
defer file.close();
|
||||||
|
try file.writer().writeAll(json);
|
||||||
|
|
||||||
|
log.warn("crash report written to disk path={s}", .{path});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shouldDiscard(envelope: *const crash.Envelope) !bool {
|
||||||
|
// If we have an event item then we're good.
|
||||||
|
for (envelope.items) |item| {
|
||||||
|
if (item.type == .event) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
284
src/crash/sentry_envelope.zig
Normal file
284
src/crash/sentry_envelope.zig
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
/// The Sentry Envelope format: https://develop.sentry.dev/sdk/envelopes/
|
||||||
|
///
|
||||||
|
/// The envelope is our primary crash report format since use the Sentry
|
||||||
|
/// client. It is designed and created by Sentry but is an open format
|
||||||
|
/// in that it is publicly documented and can be used by any system. This
|
||||||
|
/// lets us utilize the Sentry client for crash capture but also gives us
|
||||||
|
/// the opportunity to migrate to another system if we need to, and doesn't
|
||||||
|
/// force any user or developer to use Sentry the SaaS if they don't want
|
||||||
|
/// to.
|
||||||
|
///
|
||||||
|
/// This struct implements reading the envelope format (writing is not needed
|
||||||
|
/// currently but can be added later). It is incomplete; I only implemented
|
||||||
|
/// what I needed at the time.
|
||||||
|
pub const Envelope = struct {
|
||||||
|
// Developer note: this struct is really geared towards decoding an
|
||||||
|
// already-encoded envelope vs. building up an envelope from rich
|
||||||
|
// data types. I think it can be used for both I just didn't have
|
||||||
|
// the latter need.
|
||||||
|
//
|
||||||
|
// If I were to make that ability more enjoyable I'd probably change
|
||||||
|
// Item below a tagged union of either an "EncodedItem" (which is the
|
||||||
|
// current Item type) or a "DecodedItem" which is a union(ItemType)
|
||||||
|
// to its rich data type. This would allow the user to cheaply append
|
||||||
|
// items to the envelope without paying the encoding cost until
|
||||||
|
// serialization time.
|
||||||
|
//
|
||||||
|
// The way it is now, the user has to encode every entry as they build
|
||||||
|
// the envelope, which is probably fine but I wanted to write this down
|
||||||
|
// for my future self or some future contributor since it is fresh
|
||||||
|
// in my mind. Cheers.
|
||||||
|
|
||||||
|
/// The arena that the envelope is allocated in.
|
||||||
|
arena: std.heap.ArenaAllocator,
|
||||||
|
|
||||||
|
/// The headers of the envelope decoded into a json ObjectMap.
|
||||||
|
headers: std.json.ObjectMap,
|
||||||
|
|
||||||
|
/// The items in the envelope in the order they're encoded.
|
||||||
|
items: []const Item,
|
||||||
|
|
||||||
|
/// An encoded item. It is "encoded" in the sense that the payload
|
||||||
|
/// is a byte slice. The headers are "decoded" into a json ObjectMap
|
||||||
|
/// but that's still a pretty low-level representation.
|
||||||
|
pub const Item = struct {
|
||||||
|
headers: std.json.ObjectMap,
|
||||||
|
type: ItemType,
|
||||||
|
payload: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Parse an envelope from a reader.
|
||||||
|
///
|
||||||
|
/// The full envelope must fit in memory for this to succeed. This
|
||||||
|
/// will always copy the data from the reader into memory, even if the
|
||||||
|
/// reader is already in-memory (i.e. a FixedBufferStream). This
|
||||||
|
/// simplifies memory lifetimes at the expense of a copy, but envelope
|
||||||
|
/// parsing in our use case is not a hot path.
|
||||||
|
pub fn parse(
|
||||||
|
alloc_gpa: Allocator,
|
||||||
|
reader: anytype,
|
||||||
|
) !Envelope {
|
||||||
|
// We use an arena allocator to read from reader. We pair this
|
||||||
|
// with `alloc_if_needed` when parsing json to allow the json
|
||||||
|
// to reference the arena-allocated memory if it can. That way both
|
||||||
|
// our temp and perm memory is part of the same arena. This slightly
|
||||||
|
// bloats our memory requirements but reduces allocations.
|
||||||
|
var arena = std.heap.ArenaAllocator.init(alloc_gpa);
|
||||||
|
errdefer arena.deinit();
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
|
// Parse our elements. We do this outside of the struct assignment
|
||||||
|
// below to avoid the issue where order matters in struct assignment.
|
||||||
|
const headers = try parseHeader(alloc, reader);
|
||||||
|
const items = try parseItems(alloc, reader);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.headers = headers,
|
||||||
|
.items = items,
|
||||||
|
.arena = arena,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseHeader(
|
||||||
|
alloc: Allocator,
|
||||||
|
reader: anytype,
|
||||||
|
) !std.json.ObjectMap {
|
||||||
|
var buf: std.ArrayListUnmanaged(u8) = .{};
|
||||||
|
reader.streamUntilDelimiter(
|
||||||
|
buf.writer(alloc),
|
||||||
|
'\n',
|
||||||
|
1024 * 1024, // 1MB, arbitrary choice
|
||||||
|
) catch |err| switch (err) {
|
||||||
|
// Envelope can be header-only.
|
||||||
|
error.EndOfStream => {},
|
||||||
|
else => |v| return v,
|
||||||
|
};
|
||||||
|
|
||||||
|
const value = try std.json.parseFromSliceLeaky(
|
||||||
|
std.json.Value,
|
||||||
|
alloc,
|
||||||
|
buf.items,
|
||||||
|
.{ .allocate = .alloc_if_needed },
|
||||||
|
);
|
||||||
|
|
||||||
|
return switch (value) {
|
||||||
|
.object => |map| map,
|
||||||
|
else => error.EnvelopeMalformedHeaders,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseItems(
|
||||||
|
alloc: Allocator,
|
||||||
|
reader: anytype,
|
||||||
|
) ![]const Item {
|
||||||
|
var items = std.ArrayList(Item).init(alloc);
|
||||||
|
defer items.deinit();
|
||||||
|
while (try parseOneItem(alloc, reader)) |item| try items.append(item);
|
||||||
|
return try items.toOwnedSlice();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseOneItem(
|
||||||
|
alloc: Allocator,
|
||||||
|
reader: anytype,
|
||||||
|
) !?Item {
|
||||||
|
// Get the next item which must start with a header.
|
||||||
|
var buf: std.ArrayListUnmanaged(u8) = .{};
|
||||||
|
reader.streamUntilDelimiter(
|
||||||
|
buf.writer(alloc),
|
||||||
|
'\n',
|
||||||
|
1024 * 1024, // 1MB, arbitrary choice
|
||||||
|
) catch |err| switch (err) {
|
||||||
|
error.EndOfStream => return null,
|
||||||
|
else => |v| return v,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse the header JSON
|
||||||
|
const headers: std.json.ObjectMap = headers: {
|
||||||
|
const line = std.mem.trim(u8, buf.items, " \t");
|
||||||
|
if (line.len == 0) return null;
|
||||||
|
|
||||||
|
const value = try std.json.parseFromSliceLeaky(
|
||||||
|
std.json.Value,
|
||||||
|
alloc,
|
||||||
|
line,
|
||||||
|
.{ .allocate = .alloc_if_needed },
|
||||||
|
);
|
||||||
|
|
||||||
|
break :headers switch (value) {
|
||||||
|
.object => |map| map,
|
||||||
|
else => return error.EnvelopeItemMalformedHeaders,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get the event type
|
||||||
|
const typ: ItemType = if (headers.get("type")) |v| switch (v) {
|
||||||
|
.string => |str| std.meta.stringToEnum(
|
||||||
|
ItemType,
|
||||||
|
str,
|
||||||
|
) orelse .unknown,
|
||||||
|
else => return error.EnvelopeItemTypeMissing,
|
||||||
|
} else return error.EnvelopeItemTypeMissing;
|
||||||
|
|
||||||
|
// Get the payload length. The length is not required. If the length
|
||||||
|
// is not specified then it is the next line ending in `\n`.
|
||||||
|
const len_: ?u64 = if (headers.get("length")) |v| switch (v) {
|
||||||
|
.integer => |int| std.math.cast(
|
||||||
|
u64,
|
||||||
|
int,
|
||||||
|
) orelse return error.EnvelopeItemLengthMalformed,
|
||||||
|
else => return error.EnvelopeItemLengthMalformed,
|
||||||
|
} else null;
|
||||||
|
|
||||||
|
// Get the payload
|
||||||
|
const payload: []const u8 = if (len_) |len| payload: {
|
||||||
|
// The payload length is specified so read the exact length.
|
||||||
|
var payload = std.ArrayList(u8).init(alloc);
|
||||||
|
defer payload.deinit();
|
||||||
|
for (0..len) |_| {
|
||||||
|
const byte = reader.readByte() catch |err| switch (err) {
|
||||||
|
error.EndOfStream => return error.EnvelopeItemPayloadTooShort,
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
try payload.append(byte);
|
||||||
|
}
|
||||||
|
break :payload try payload.toOwnedSlice();
|
||||||
|
} else payload: {
|
||||||
|
// The payload is the next line ending in `\n`. It is required.
|
||||||
|
var payload = std.ArrayList(u8).init(alloc);
|
||||||
|
defer payload.deinit();
|
||||||
|
reader.streamUntilDelimiter(
|
||||||
|
payload.writer(),
|
||||||
|
'\n',
|
||||||
|
1024 * 1024 * 50, // 50MB, arbitrary choice
|
||||||
|
) catch |err| switch (err) {
|
||||||
|
error.EndOfStream => return error.EnvelopeItemPayloadTooShort,
|
||||||
|
else => |v| return v,
|
||||||
|
};
|
||||||
|
break :payload try payload.toOwnedSlice();
|
||||||
|
};
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.headers = headers,
|
||||||
|
.type = typ,
|
||||||
|
.payload = payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Envelope) void {
|
||||||
|
self.arena.deinit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The various item types that can be in an envelope. This is a point
|
||||||
|
/// in time snapshot of the types that are known whenever this is edited.
|
||||||
|
/// Event types can be introduced at any time and unknown types will
|
||||||
|
/// take the "unknown" enum value.
|
||||||
|
///
|
||||||
|
/// https://develop.sentry.dev/sdk/envelopes/#data-model
|
||||||
|
pub const ItemType = enum {
|
||||||
|
/// Special event type for when the item type is unknown.
|
||||||
|
unknown,
|
||||||
|
|
||||||
|
/// Documented event types
|
||||||
|
event,
|
||||||
|
transaction,
|
||||||
|
attachment,
|
||||||
|
session,
|
||||||
|
sessions,
|
||||||
|
statsd,
|
||||||
|
metric_meta,
|
||||||
|
user_feedback,
|
||||||
|
client_report,
|
||||||
|
replay_event,
|
||||||
|
replay_recording,
|
||||||
|
profile,
|
||||||
|
check_in,
|
||||||
|
};
|
||||||
|
|
||||||
|
test "Envelope parse" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var fbs = std.io.fixedBufferStream(
|
||||||
|
\\{}
|
||||||
|
);
|
||||||
|
var v = try Envelope.parse(alloc, fbs.reader());
|
||||||
|
defer v.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Envelope parse session" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var fbs = std.io.fixedBufferStream(
|
||||||
|
\\{}
|
||||||
|
\\{"type":"session","length":218}
|
||||||
|
\\{"init":true,"sid":"c148cc2f-5f9f-4231-575c-2e85504d6434","status":"abnormal","errors":0,"started":"2024-08-29T02:38:57.607016Z","duration":0.000343,"attrs":{"release":"0.1.0-HEAD+d37b7d09","environment":"production"}}
|
||||||
|
);
|
||||||
|
var v = try Envelope.parse(alloc, fbs.reader());
|
||||||
|
defer v.deinit();
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(usize, 1), v.items.len);
|
||||||
|
try testing.expectEqual(ItemType.session, v.items[0].type);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Envelope parse end in new line" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var fbs = std.io.fixedBufferStream(
|
||||||
|
\\{}
|
||||||
|
\\{"type":"session","length":218}
|
||||||
|
\\{"init":true,"sid":"c148cc2f-5f9f-4231-575c-2e85504d6434","status":"abnormal","errors":0,"started":"2024-08-29T02:38:57.607016Z","duration":0.000343,"attrs":{"release":"0.1.0-HEAD+d37b7d09","environment":"production"}}
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
var v = try Envelope.parse(alloc, fbs.reader());
|
||||||
|
defer v.deinit();
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(usize, 1), v.items.len);
|
||||||
|
try testing.expectEqual(ItemType.session, v.items[0].type);
|
||||||
|
}
|
@ -7,6 +7,7 @@ const fontconfig = @import("fontconfig");
|
|||||||
const glslang = @import("glslang");
|
const glslang = @import("glslang");
|
||||||
const harfbuzz = @import("harfbuzz");
|
const harfbuzz = @import("harfbuzz");
|
||||||
const oni = @import("oniguruma");
|
const oni = @import("oniguruma");
|
||||||
|
const crash = @import("crash/main.zig");
|
||||||
const renderer = @import("renderer.zig");
|
const renderer = @import("renderer.zig");
|
||||||
const xev = @import("xev");
|
const xev = @import("xev");
|
||||||
|
|
||||||
@ -39,6 +40,14 @@ pub const GlobalState = struct {
|
|||||||
|
|
||||||
/// Initialize the global state.
|
/// Initialize the global state.
|
||||||
pub fn init(self: *GlobalState) !void {
|
pub fn init(self: *GlobalState) !void {
|
||||||
|
// const start = try std.time.Instant.now();
|
||||||
|
// const start_micro = std.time.microTimestamp();
|
||||||
|
// defer {
|
||||||
|
// const end = std.time.Instant.now() catch unreachable;
|
||||||
|
// // "[updateFrame critical time] <START us>\t<TIME_TAKEN us>"
|
||||||
|
// std.log.err("[global init time] start={}us duration={}ns", .{ start_micro, end.since(start) / std.time.ns_per_us });
|
||||||
|
// }
|
||||||
|
|
||||||
// Initialize ourself to nothing so we don't have any extra state.
|
// Initialize ourself to nothing so we don't have any extra state.
|
||||||
// IMPORTANT: this MUST be initialized before any log output because
|
// IMPORTANT: this MUST be initialized before any log output because
|
||||||
// the log function uses the global state.
|
// the log function uses the global state.
|
||||||
@ -117,6 +126,18 @@ pub const GlobalState = struct {
|
|||||||
// First things first, we fix our file descriptors
|
// First things first, we fix our file descriptors
|
||||||
internal_os.fixMaxFiles();
|
internal_os.fixMaxFiles();
|
||||||
|
|
||||||
|
// Initialize our crash reporting.
|
||||||
|
try crash.init(self.alloc);
|
||||||
|
|
||||||
|
// const sentrylib = @import("sentry");
|
||||||
|
// if (sentrylib.captureEvent(sentrylib.Value.initMessageEvent(
|
||||||
|
// .info,
|
||||||
|
// null,
|
||||||
|
// "hello, world",
|
||||||
|
// ))) |uuid| {
|
||||||
|
// std.log.warn("uuid={s}", .{uuid.string()});
|
||||||
|
// } else std.log.warn("failed to capture event", .{});
|
||||||
|
|
||||||
// We need to make sure the process locale is set properly. Locale
|
// We need to make sure the process locale is set properly. Locale
|
||||||
// affects a lot of behaviors in a shell.
|
// affects a lot of behaviors in a shell.
|
||||||
try internal_os.ensureLocale(self.alloc);
|
try internal_os.ensureLocale(self.alloc);
|
||||||
@ -138,6 +159,9 @@ pub const GlobalState = struct {
|
|||||||
pub fn deinit(self: *GlobalState) void {
|
pub fn deinit(self: *GlobalState) void {
|
||||||
if (self.resources_dir) |dir| self.alloc.free(dir);
|
if (self.resources_dir) |dir| self.alloc.free(dir);
|
||||||
|
|
||||||
|
// Flush our crash logs
|
||||||
|
crash.deinit();
|
||||||
|
|
||||||
if (self.gpa) |*value| {
|
if (self.gpa) |*value| {
|
||||||
// We want to ensure that we deinit the GPA because this is
|
// We want to ensure that we deinit the GPA because this is
|
||||||
// the point at which it will output if there were safety violations.
|
// the point at which it will output if there were safety violations.
|
||||||
|
@ -300,6 +300,28 @@ pub const Action = union(enum) {
|
|||||||
/// Quit ghostty.
|
/// Quit ghostty.
|
||||||
quit: void,
|
quit: void,
|
||||||
|
|
||||||
|
/// Crash ghostty in the desired thread for the focused surface.
|
||||||
|
///
|
||||||
|
/// WARNING: This is a hard crash (panic) and data can be lost.
|
||||||
|
///
|
||||||
|
/// The purpose of this action is to test crash handling. For some
|
||||||
|
/// users, it may be useful to test crash reporting functionality in
|
||||||
|
/// order to determine if it all works as expected.
|
||||||
|
///
|
||||||
|
/// The value determines the crash location:
|
||||||
|
///
|
||||||
|
/// - "main" - crash on the main (GUI) thread.
|
||||||
|
/// - "io" - crash on the IO thread for the focused surface.
|
||||||
|
/// - "render" - crash on the render thread for the focused surface.
|
||||||
|
///
|
||||||
|
crash: CrashThread,
|
||||||
|
|
||||||
|
pub const CrashThread = enum {
|
||||||
|
main,
|
||||||
|
io,
|
||||||
|
render,
|
||||||
|
};
|
||||||
|
|
||||||
pub const CursorKey = struct {
|
pub const CursorKey = struct {
|
||||||
normal: []const u8,
|
normal: []const u8,
|
||||||
application: []const u8,
|
application: []const u8,
|
||||||
|
@ -181,6 +181,7 @@ test {
|
|||||||
|
|
||||||
// Libraries
|
// Libraries
|
||||||
_ = @import("segmented_pool.zig");
|
_ = @import("segmented_pool.zig");
|
||||||
|
_ = @import("crash/main.zig");
|
||||||
_ = @import("inspector/main.zig");
|
_ = @import("inspector/main.zig");
|
||||||
_ = @import("terminal/main.zig");
|
_ = @import("terminal/main.zig");
|
||||||
_ = @import("terminfo/main.zig");
|
_ = @import("terminfo/main.zig");
|
||||||
|
@ -21,17 +21,54 @@ pub const Options = struct {
|
|||||||
|
|
||||||
/// Get the XDG user config directory. The returned value is allocated.
|
/// Get the XDG user config directory. The returned value is allocated.
|
||||||
pub fn config(alloc: Allocator, opts: Options) ![]u8 {
|
pub fn config(alloc: Allocator, opts: Options) ![]u8 {
|
||||||
|
return try dir(alloc, opts, .{
|
||||||
|
.env = "XDG_CONFIG_HOME",
|
||||||
|
.windows_env = "LOCALAPPDATA",
|
||||||
|
.default_subdir = ".config",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the XDG cache directory. The returned value is allocated.
|
||||||
|
pub fn cache(alloc: Allocator, opts: Options) ![]u8 {
|
||||||
|
return try dir(alloc, opts, .{
|
||||||
|
.env = "XDG_CACHE_HOME",
|
||||||
|
.windows_env = "LOCALAPPDATA",
|
||||||
|
.default_subdir = ".cache",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the XDG state directory. The returned value is allocated.
|
||||||
|
pub fn state(alloc: Allocator, opts: Options) ![]u8 {
|
||||||
|
return try dir(alloc, opts, .{
|
||||||
|
.env = "XDG_STATE_HOME",
|
||||||
|
.windows_env = "LOCALAPPDATA",
|
||||||
|
.default_subdir = ".local/state",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const InternalOptions = struct {
|
||||||
|
env: []const u8,
|
||||||
|
windows_env: []const u8,
|
||||||
|
default_subdir: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Unified helper to get XDG directories that follow a common pattern.
|
||||||
|
fn dir(
|
||||||
|
alloc: Allocator,
|
||||||
|
opts: Options,
|
||||||
|
internal_opts: InternalOptions,
|
||||||
|
) ![]u8 {
|
||||||
// First check the env var. On Windows we have to allocate so this tracks
|
// First check the env var. On Windows we have to allocate so this tracks
|
||||||
// both whether we have the env var and whether we own it.
|
// both whether we have the env var and whether we own it.
|
||||||
// on Windows we treat `LOCALAPPDATA` as a fallback for `XDG_CONFIG_HOME`
|
// on Windows we treat `LOCALAPPDATA` as a fallback for `XDG_CONFIG_HOME`
|
||||||
const env_, const owned = switch (builtin.os.tag) {
|
const env_, const owned = switch (builtin.os.tag) {
|
||||||
else => .{ posix.getenv("XDG_CONFIG_HOME"), false },
|
else => .{ posix.getenv(internal_opts.env), false },
|
||||||
.windows => windows: {
|
.windows => windows: {
|
||||||
if (std.process.getEnvVarOwned(alloc, "XDG_CONFIG_HOME")) |env| {
|
if (std.process.getEnvVarOwned(alloc, internal_opts.env)) |env| {
|
||||||
break :windows .{ env, true };
|
break :windows .{ env, true };
|
||||||
} else |err| switch (err) {
|
} else |err| switch (err) {
|
||||||
error.EnvironmentVariableNotFound => {
|
error.EnvironmentVariableNotFound => {
|
||||||
if (std.process.getEnvVarOwned(alloc, "LOCALAPPDATA")) |env| {
|
if (std.process.getEnvVarOwned(alloc, internal_opts.windows_env)) |env| {
|
||||||
break :windows .{ env, true };
|
break :windows .{ env, true };
|
||||||
} else |err2| switch (err2) {
|
} else |err2| switch (err2) {
|
||||||
error.EnvironmentVariableNotFound => break :windows .{ null, false },
|
error.EnvironmentVariableNotFound => break :windows .{ null, false },
|
||||||
@ -60,7 +97,7 @@ pub fn config(alloc: Allocator, opts: Options) ![]u8 {
|
|||||||
if (opts.home) |home| {
|
if (opts.home) |home| {
|
||||||
return try std.fs.path.join(alloc, &[_][]const u8{
|
return try std.fs.path.join(alloc, &[_][]const u8{
|
||||||
home,
|
home,
|
||||||
".config",
|
internal_opts.default_subdir,
|
||||||
opts.subdir orelse "",
|
opts.subdir orelse "",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -70,7 +107,7 @@ pub fn config(alloc: Allocator, opts: Options) ![]u8 {
|
|||||||
if (try homedir.home(&buf)) |home| {
|
if (try homedir.home(&buf)) |home| {
|
||||||
return try std.fs.path.join(alloc, &[_][]const u8{
|
return try std.fs.path.join(alloc, &[_][]const u8{
|
||||||
home,
|
home,
|
||||||
".config",
|
internal_opts.default_subdir,
|
||||||
opts.subdir orelse "",
|
opts.subdir orelse "",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
19
src/pty.zig
19
src/pty.zig
@ -137,6 +137,25 @@ const PosixPty = struct {
|
|||||||
/// This should be called prior to exec in the forked child process
|
/// This should be called prior to exec in the forked child process
|
||||||
/// in order to setup the tty properly.
|
/// in order to setup the tty properly.
|
||||||
pub fn childPreExec(self: Pty) !void {
|
pub fn childPreExec(self: Pty) !void {
|
||||||
|
// Reset our signals
|
||||||
|
var sa: posix.Sigaction = .{
|
||||||
|
.handler = .{ .handler = posix.SIG.DFL },
|
||||||
|
.mask = posix.empty_sigset,
|
||||||
|
.flags = 0,
|
||||||
|
};
|
||||||
|
try posix.sigaction(posix.SIG.ABRT, &sa, null);
|
||||||
|
try posix.sigaction(posix.SIG.ALRM, &sa, null);
|
||||||
|
try posix.sigaction(posix.SIG.BUS, &sa, null);
|
||||||
|
try posix.sigaction(posix.SIG.CHLD, &sa, null);
|
||||||
|
try posix.sigaction(posix.SIG.FPE, &sa, null);
|
||||||
|
try posix.sigaction(posix.SIG.HUP, &sa, null);
|
||||||
|
try posix.sigaction(posix.SIG.ILL, &sa, null);
|
||||||
|
try posix.sigaction(posix.SIG.INT, &sa, null);
|
||||||
|
try posix.sigaction(posix.SIG.SEGV, &sa, null);
|
||||||
|
try posix.sigaction(posix.SIG.TRAP, &sa, null);
|
||||||
|
try posix.sigaction(posix.SIG.TERM, &sa, null);
|
||||||
|
try posix.sigaction(posix.SIG.QUIT, &sa, null);
|
||||||
|
|
||||||
// Create a new process group
|
// Create a new process group
|
||||||
if (setsid() < 0) return error.ProcessGroupFailed;
|
if (setsid() < 0) return error.ProcessGroupFailed;
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ pub const Thread = @This();
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const xev = @import("xev");
|
const xev = @import("xev");
|
||||||
|
const crash = @import("../crash/main.zig");
|
||||||
const renderer = @import("../renderer.zig");
|
const renderer = @import("../renderer.zig");
|
||||||
const apprt = @import("../apprt.zig");
|
const apprt = @import("../apprt.zig");
|
||||||
const configpkg = @import("../config.zig");
|
const configpkg = @import("../config.zig");
|
||||||
@ -191,6 +192,13 @@ pub fn threadMain(self: *Thread) void {
|
|||||||
fn threadMain_(self: *Thread) !void {
|
fn threadMain_(self: *Thread) !void {
|
||||||
defer log.debug("renderer thread exited", .{});
|
defer log.debug("renderer thread exited", .{});
|
||||||
|
|
||||||
|
// Setup our crash metadata
|
||||||
|
crash.sentry.thread_state = .{
|
||||||
|
.type = .renderer,
|
||||||
|
.surface = self.renderer.surface_mailbox.surface,
|
||||||
|
};
|
||||||
|
defer crash.sentry.thread_state = null;
|
||||||
|
|
||||||
// Run our loop start/end callbacks if the renderer cares.
|
// Run our loop start/end callbacks if the renderer cares.
|
||||||
const has_loop = @hasDecl(renderer.Renderer, "loopEnter");
|
const has_loop = @hasDecl(renderer.Renderer, "loopEnter");
|
||||||
if (has_loop) try self.renderer.loopEnter(self);
|
if (has_loop) try self.renderer.loopEnter(self);
|
||||||
@ -263,6 +271,8 @@ fn drainMailbox(self: *Thread) !void {
|
|||||||
while (self.mailbox.pop()) |message| {
|
while (self.mailbox.pop()) |message| {
|
||||||
log.debug("mailbox message={}", .{message});
|
log.debug("mailbox message={}", .{message});
|
||||||
switch (message) {
|
switch (message) {
|
||||||
|
.crash => @panic("crash request, crashing intentionally"),
|
||||||
|
|
||||||
.visible => |v| {
|
.visible => |v| {
|
||||||
// Set our visible state
|
// Set our visible state
|
||||||
self.flags.visible = v;
|
self.flags.visible = v;
|
||||||
|
@ -8,6 +8,10 @@ const terminal = @import("../terminal/main.zig");
|
|||||||
|
|
||||||
/// The messages that can be sent to a renderer thread.
|
/// The messages that can be sent to a renderer thread.
|
||||||
pub const Message = union(enum) {
|
pub const Message = union(enum) {
|
||||||
|
/// Purposely crash the renderer. This is used for testing and debugging.
|
||||||
|
/// See the "crash" binding action.
|
||||||
|
crash: void,
|
||||||
|
|
||||||
/// A change in state in the window focus that this renderer is
|
/// A change in state in the window focus that this renderer is
|
||||||
/// rendering within. This is only sent when a change is detected so
|
/// rendering within. This is only sent when a change is detected so
|
||||||
/// the renderer is expected to handle all of these.
|
/// the renderer is expected to handle all of these.
|
||||||
|
@ -15,6 +15,7 @@ const std = @import("std");
|
|||||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const xev = @import("xev");
|
const xev = @import("xev");
|
||||||
|
const crash = @import("../crash/main.zig");
|
||||||
const termio = @import("../termio.zig");
|
const termio = @import("../termio.zig");
|
||||||
const BlockingQueue = @import("../blocking_queue.zig").BlockingQueue;
|
const BlockingQueue = @import("../blocking_queue.zig").BlockingQueue;
|
||||||
|
|
||||||
@ -200,6 +201,13 @@ pub fn threadMain(self: *Thread, io: *termio.Termio) void {
|
|||||||
fn threadMain_(self: *Thread, io: *termio.Termio) !void {
|
fn threadMain_(self: *Thread, io: *termio.Termio) !void {
|
||||||
defer log.debug("IO thread exited", .{});
|
defer log.debug("IO thread exited", .{});
|
||||||
|
|
||||||
|
// Setup our crash metadata
|
||||||
|
crash.sentry.thread_state = .{
|
||||||
|
.type = .io,
|
||||||
|
.surface = io.surface_mailbox.surface,
|
||||||
|
};
|
||||||
|
defer crash.sentry.thread_state = null;
|
||||||
|
|
||||||
// Get the mailbox. This must be an SPSC mailbox for threading.
|
// Get the mailbox. This must be an SPSC mailbox for threading.
|
||||||
const mailbox = switch (io.mailbox) {
|
const mailbox = switch (io.mailbox) {
|
||||||
.spsc => |*v| v,
|
.spsc => |*v| v,
|
||||||
@ -261,6 +269,7 @@ fn drainMailbox(
|
|||||||
|
|
||||||
log.debug("mailbox message={}", .{message});
|
log.debug("mailbox message={}", .{message});
|
||||||
switch (message) {
|
switch (message) {
|
||||||
|
.crash => @panic("crash request, crashing intentionally"),
|
||||||
.change_config => |config| {
|
.change_config => |config| {
|
||||||
defer config.alloc.destroy(config.ptr);
|
defer config.alloc.destroy(config.ptr);
|
||||||
try io.changeConfig(data, config.ptr);
|
try io.changeConfig(data, config.ptr);
|
||||||
|
@ -29,6 +29,10 @@ pub const Message = union(enum) {
|
|||||||
padding: renderer.Padding,
|
padding: renderer.Padding,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Purposely crash the renderer. This is used for testing and debugging.
|
||||||
|
/// See the "crash" binding action.
|
||||||
|
crash: void,
|
||||||
|
|
||||||
/// The derived configuration to update the implementation with. This
|
/// The derived configuration to update the implementation with. This
|
||||||
/// is allocated via the allocator and is expected to be freed when done.
|
/// is allocated via the allocator and is expected to be freed when done.
|
||||||
change_config: struct {
|
change_config: struct {
|
||||||
|
Reference in New Issue
Block a user