add TTY fallback
This commit is contained in:
parent
58020dea3a
commit
ff0004a7a2
9 changed files with 337 additions and 83 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -10,3 +10,6 @@
|
|||
[submodule "deps/zig-fcft"]
|
||||
path = deps/zig-fcft
|
||||
url = https://git.sr.ht/~novakane/zig-fcft
|
||||
[submodule "deps/zig-spoon"]
|
||||
path = deps/zig-spoon
|
||||
url = https://git.sr.ht/~leon_plickat/zig-spoon
|
||||
|
|
|
@ -26,6 +26,8 @@ pub fn build(b: *zbs.Builder) !void {
|
|||
wayprompt.setBuildMode(mode);
|
||||
wayprompt.addOptions("build_options", options);
|
||||
|
||||
wayprompt.addPackagePath("spoon", "deps/zig-spoon/import.zig");
|
||||
|
||||
const pixman = std.build.Pkg{
|
||||
.name = "pixman",
|
||||
.path = .{ .path = "deps/zig-pixman/pixman.zig" },
|
||||
|
|
1
deps/zig-spoon
vendored
Submodule
1
deps/zig-spoon
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit a1ea3574526cdc51aaa3d324d86c533cde56acd0
|
47
src/Utf8String.zig
Normal file
47
src/Utf8String.zig
Normal file
|
@ -0,0 +1,47 @@
|
|||
const std = @import("std");
|
||||
const ascii = std.ascii;
|
||||
const unicode = std.unicode;
|
||||
const debug = std.debug;
|
||||
|
||||
const context = &@import("wayprompt.zig").context;
|
||||
|
||||
const Self = @This();
|
||||
|
||||
buffer: std.ArrayListUnmanaged(u8) = .{},
|
||||
len: usize = 0,
|
||||
|
||||
pub fn appendSlice(self: *Self, str: []const u8) !void {
|
||||
const len = try unicode.utf8CountCodepoints(str);
|
||||
const alloc = context.gpa.allocator();
|
||||
try self.buffer.appendSlice(alloc, str);
|
||||
self.len += len;
|
||||
}
|
||||
|
||||
pub fn deleteBackwards(self: *Self) void {
|
||||
if (self.buffer.items.len == 0) return;
|
||||
const alloc = context.gpa.allocator();
|
||||
var i: usize = self.buffer.items.len - 1;
|
||||
while (i >= 0) : (i -= 1) {
|
||||
_ = unicode.utf8ByteSequenceLength(self.buffer.items[i]) catch continue;
|
||||
self.buffer.shrinkAndFree(alloc, i);
|
||||
self.len -= 1;
|
||||
return;
|
||||
}
|
||||
unreachable;
|
||||
}
|
||||
|
||||
pub fn toOwnedSlice(self: *Self) ?[]const u8 {
|
||||
const alloc = context.gpa.allocator();
|
||||
defer self.* = .{};
|
||||
if (self.buffer.items.len > 0) {
|
||||
return self.buffer.toOwnedSlice(alloc);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
const alloc = context.gpa.allocator();
|
||||
self.buffer.deinit(alloc);
|
||||
self.* = .{};
|
||||
}
|
|
@ -9,6 +9,7 @@ const fmt = std.fmt;
|
|||
const debug = std.debug;
|
||||
|
||||
const wayland = @import("wayland.zig");
|
||||
const tty = @import("tty.zig");
|
||||
|
||||
const context = &@import("wayprompt.zig").context;
|
||||
|
||||
|
@ -136,6 +137,9 @@ fn parseInput(writer: io.BufferedWriter(4096, fs.File.Writer).Writer, line: []co
|
|||
if (getOption("putenv=WAYLAND_DISPLAY=", option, line)) |o| {
|
||||
if (context.wayland_display) |w| alloc.free(w);
|
||||
context.wayland_display = try alloc.dupeZ(u8, o);
|
||||
} else if (getOption("ttyname=", option, line)) |o| {
|
||||
if (context.tty_name) |w| alloc.free(w);
|
||||
context.tty_name = try alloc.dupeZ(u8, o);
|
||||
} else if (getOption("default-ok=", option, line)) |o| {
|
||||
if (default_ok) |w| alloc.free(w);
|
||||
default_ok = try pinentryDupe(o, true);
|
||||
|
@ -221,9 +225,10 @@ fn getPin(writer: anytype) !void {
|
|||
default_cancel = null;
|
||||
}
|
||||
|
||||
const alloc = context.gpa.allocator();
|
||||
|
||||
if (wayland.run(true)) |pin| {
|
||||
if (pin) |p| {
|
||||
const alloc = context.gpa.allocator();
|
||||
defer alloc.free(p);
|
||||
try writer.print("D {s}\nEND\nOK\n", .{p});
|
||||
} else {
|
||||
|
@ -231,6 +236,7 @@ fn getPin(writer: anytype) !void {
|
|||
try writer.writeAll("OK\n");
|
||||
}
|
||||
} else |err| {
|
||||
// TODO error.UserNotOk should be handled here as well
|
||||
// The client will ignore all messages starting with #, however they
|
||||
// should still be logged by the gpg-agent, given that the right
|
||||
// debug options are enabled. This means we can use this to insert
|
||||
|
@ -242,16 +248,25 @@ fn getPin(writer: anytype) !void {
|
|||
// treats both equally. We do it properly of course, because we're
|
||||
// pedantic. Anyway, that's why error.UserAbort exists and why we
|
||||
// don't print it because it's not /really/ an error.
|
||||
if (err != error.UserAbort) {
|
||||
try writer.print("# Error: {s}\n", .{@errorName(err)});
|
||||
if (err == error.NoWaylandDisplay or err == error.ConnectFailed) {
|
||||
if (tty.run(true)) |_pin| {
|
||||
if (_pin) |p| {
|
||||
defer alloc.free(p);
|
||||
try writer.print("D {s}\nEND\nOK\n", .{p});
|
||||
} else {
|
||||
try writer.writeAll("OK\n");
|
||||
}
|
||||
} else |e| {
|
||||
try errMessage(writer, e);
|
||||
}
|
||||
} else {
|
||||
try errMessage(writer, err);
|
||||
}
|
||||
try writer.writeAll("ERR 83886179 Operation cancelled\n");
|
||||
}
|
||||
|
||||
// The errormessage must automatically reset after every GETPIN or
|
||||
// CONFIRM action.
|
||||
if (context.errmessage) |e| {
|
||||
const alloc = context.gpa.allocator();
|
||||
alloc.free(e);
|
||||
context.errmessage = null;
|
||||
}
|
||||
|
@ -267,8 +282,16 @@ fn message(writer: anytype) !void {
|
|||
debug.assert(ret == null);
|
||||
try writer.writeAll("OK\n");
|
||||
} else |err| {
|
||||
try writer.print("# Error: {s}\n", .{@errorName(err)});
|
||||
try writer.writeAll("ERR 83886179 cancelled\n");
|
||||
if (err == error.NoWaylandDisplay or err == error.ConnectFailed) {
|
||||
if (tty.run(false)) |r| {
|
||||
debug.assert(r == null);
|
||||
try writer.writeAll("OK\n");
|
||||
} else |e| {
|
||||
try errMessage(writer, e);
|
||||
}
|
||||
} else {
|
||||
try errMessage(writer, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -289,13 +312,15 @@ fn confirm(writer: anytype) !void {
|
|||
debug.assert(ret == null);
|
||||
try writer.writeAll("OK\n");
|
||||
} else |err| {
|
||||
switch (err) {
|
||||
error.UserAbort => try writer.writeAll("ERR 83886179 cancelled\n"),
|
||||
error.UserNotOk => try writer.writeAll("ERR 83886194 not confirmed\n"),
|
||||
else => {
|
||||
try writer.print("# Error: {s}\n", .{@errorName(err)});
|
||||
try writer.writeAll("ERR 83886179 cancelled\n");
|
||||
},
|
||||
if (err == error.NoWaylandDisplay or err == error.ConnectFailed) {
|
||||
if (tty.run(false)) |r| {
|
||||
debug.assert(r == null);
|
||||
try writer.writeAll("OK\n");
|
||||
} else |e| {
|
||||
try errMessage(writer, e);
|
||||
}
|
||||
} else {
|
||||
try errMessage(writer, err);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -308,6 +333,17 @@ fn confirm(writer: anytype) !void {
|
|||
}
|
||||
}
|
||||
|
||||
fn errMessage(writer: anytype, err: anyerror) !void {
|
||||
switch (err) {
|
||||
error.UserAbort => try writer.writeAll("ERR 83886179 Operation cancelled\n"),
|
||||
error.UserNotOk => try writer.writeAll("ERR 83886194 not confirmed\n"),
|
||||
else => {
|
||||
try writer.print("# Error: {s}\n", .{@errorName(err)});
|
||||
try writer.writeAll("ERR 83886179 Operation cancelled\n");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn setString(writer: anytype, comptime name: []const u8, value: []const u8) !void {
|
||||
const alloc = context.gpa.allocator();
|
||||
if (@field(context.*, name)) |f| {
|
||||
|
|
219
src/tty.zig
Normal file
219
src/tty.zig
Normal file
|
@ -0,0 +1,219 @@
|
|||
const std = @import("std");
|
||||
const debug = std.debug;
|
||||
const os = std.os;
|
||||
const io = std.io;
|
||||
const math = std.math;
|
||||
const unicode = std.unicode;
|
||||
const spoon = @import("spoon");
|
||||
|
||||
const context = &@import("wayprompt.zig").context;
|
||||
|
||||
const Utf8String = @import("Utf8String.zig");
|
||||
|
||||
const LineIterator = struct {
|
||||
in: ?[]const u8,
|
||||
|
||||
pub fn from(input: []const u8) LineIterator {
|
||||
return .{ .in = input };
|
||||
}
|
||||
|
||||
pub fn next(self: *LineIterator) ?[]const u8 {
|
||||
if (self.in == null) return null;
|
||||
if (self.in.?.len == 0) return null;
|
||||
if (self.in.?.len == 1 and self.in.?[0] == '\n') return null;
|
||||
var i: usize = 0;
|
||||
for (self.in.?) |byte| {
|
||||
if (byte == '\n') {
|
||||
defer self.in = self.in.?[i + 1 ..];
|
||||
return self.in.?[0..i];
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
defer self.in = null;
|
||||
return self.in.?;
|
||||
}
|
||||
};
|
||||
|
||||
const TtyContext = struct {
|
||||
term: spoon.Term = undefined,
|
||||
loop: bool = true,
|
||||
getpin: bool = false,
|
||||
exit_reason: ?anyerror = null,
|
||||
|
||||
pin: Utf8String = .{},
|
||||
|
||||
pub fn run(self: *TtyContext, getpin: bool) !?[]const u8 {
|
||||
errdefer self.pin.deinit();
|
||||
|
||||
self.getpin = getpin;
|
||||
|
||||
// Only try to fall back to TTY mode when a TTY is set.
|
||||
if (context.tty_name) |name| {
|
||||
try self.term.init(.{ .tty_name = name });
|
||||
} else {
|
||||
return error.NoTTYNameSet;
|
||||
}
|
||||
defer self.term.deinit();
|
||||
|
||||
os.sigaction(os.SIG.WINCH, &os.Sigaction{
|
||||
.handler = .{ .handler = handleSigWinch },
|
||||
.mask = os.empty_sigset,
|
||||
.flags = 0,
|
||||
}, null);
|
||||
try self.term.uncook(.{});
|
||||
defer self.term.cook() catch {};
|
||||
|
||||
try self.term.fetchSize();
|
||||
if (context.title) |t| {
|
||||
try self.term.setWindowTitle("wayprompt TTY fallback: {s}", .{t});
|
||||
} else {
|
||||
try self.term.setWindowTitle("wayprompt TTY fallback", .{});
|
||||
}
|
||||
try self.render();
|
||||
|
||||
var fds: [1]os.pollfd = undefined;
|
||||
fds[0] = .{
|
||||
.fd = self.term.tty.handle,
|
||||
.events = os.POLL.IN,
|
||||
.revents = undefined,
|
||||
};
|
||||
|
||||
var buf: [32]u8 = undefined;
|
||||
while (self.loop) {
|
||||
_ = try os.poll(&fds, -1);
|
||||
const read = try self.term.readInput(&buf);
|
||||
var it = spoon.inputParser(buf[0..read]);
|
||||
while (it.next()) |in| {
|
||||
if (in.eqlDescription("enter")) {
|
||||
self.loop = false;
|
||||
} else if (in.eqlDescription("C-c")) {
|
||||
if (context.notok == null) {
|
||||
self.abort(error.UserAbort);
|
||||
} else {
|
||||
self.abort(error.UserNotOk);
|
||||
}
|
||||
} else if (in.eqlDescription("backspace")) {
|
||||
self.pin.deleteBackwards();
|
||||
try self.render();
|
||||
} else if (in.eqlDescription("escape")) {
|
||||
self.abort(error.UserAbort);
|
||||
} else if (self.getpin and in.content == .codepoint) {
|
||||
if (in.mod_alt or in.mod_ctrl or in.mod_super) continue;
|
||||
const cp = in.content.codepoint;
|
||||
|
||||
// We can safely reuse the buffer here.
|
||||
const len = unicode.utf8Encode(cp, &buf) catch continue;
|
||||
self.pin.appendSlice(buf[0..len]) catch {};
|
||||
|
||||
try self.render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (self.exit_reason) |reason| {
|
||||
return reason;
|
||||
} else if (self.getpin) {
|
||||
return self.pin.toOwnedSlice();
|
||||
} else {
|
||||
debug.assert(self.pin.len == 0);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
fn abort(self: *TtyContext, reason: anyerror) void {
|
||||
self.loop = false;
|
||||
self.exit_reason = reason;
|
||||
}
|
||||
|
||||
fn render(self: *TtyContext) !void {
|
||||
var rc = try self.term.getRenderContext();
|
||||
defer rc.done() catch {};
|
||||
try rc.clear();
|
||||
|
||||
if (self.term.width < 5 or self.term.height < 5) {
|
||||
try rc.setAttribute(.{ .fg = .red, .bold = true });
|
||||
try rc.writeAllWrapping("Terminal too small!");
|
||||
return;
|
||||
}
|
||||
|
||||
var line: usize = 0;
|
||||
|
||||
if (context.title) |title| try self.renderContent(&rc, title, .{ .bg = .green, .bold = true, .fg = .black }, &line);
|
||||
if (context.description) |description| try self.renderContent(&rc, description, .{}, &line);
|
||||
if (context.prompt) |prompt| try self.renderContent(&rc, prompt, .{ .bold = true }, &line);
|
||||
|
||||
if (self.getpin) {
|
||||
try rc.setAttribute(.{ .bold = true });
|
||||
try rc.moveCursorTo(line, 0);
|
||||
var rpw = rc.restrictedPaddingWriter(self.term.width);
|
||||
const writer = rpw.writer();
|
||||
try writer.writeAll(" > ");
|
||||
try writer.writeByteNTimes('*', math.min(context.pin_square_amount, self.pin.len));
|
||||
try writer.writeByteNTimes('_', context.pin_square_amount -| self.pin.len);
|
||||
try rpw.finish();
|
||||
line += 2;
|
||||
}
|
||||
|
||||
if (context.errmessage) |errmessage| try self.renderContent(&rc, errmessage, .{ .bold = true, .fg = .red }, &line);
|
||||
|
||||
if (context.ok) |ok| try self.renderButton(&rc, "enter", ok, &line);
|
||||
if (context.notok) |notok| try self.renderButton(&rc, "C-c", notok, &line);
|
||||
if (context.cancel) |cancel| try self.renderButton(&rc, "escape", cancel, &line);
|
||||
}
|
||||
|
||||
fn renderContent(self: *TtyContext, rc: *spoon.Term.RenderContext, str: []const u8, attr: spoon.Attribute, line: *usize) !void {
|
||||
try rc.setAttribute(attr);
|
||||
var it = LineIterator.from(str);
|
||||
while (it.next()) |l| {
|
||||
if (line.* >= self.term.height) return;
|
||||
try rc.moveCursorTo(line.*, 0);
|
||||
var rpw = rc.restrictedPaddingWriter(self.term.width);
|
||||
const writer = rpw.writer();
|
||||
try writer.writeByte(' ');
|
||||
try writer.writeAll(l);
|
||||
if (attr.bg != .none) {
|
||||
try rpw.pad();
|
||||
} else {
|
||||
try rpw.finish();
|
||||
}
|
||||
line.* += 1;
|
||||
}
|
||||
line.* += 1;
|
||||
}
|
||||
|
||||
fn renderButton(self: *TtyContext, rc: *spoon.Term.RenderContext, comptime button: []const u8, str: []const u8, line: *usize) !void {
|
||||
var first = line.*;
|
||||
try rc.setAttribute(.{});
|
||||
try rc.moveCursorTo(line.*, 0);
|
||||
var it = LineIterator.from(str);
|
||||
while (it.next()) |l| {
|
||||
if (line.* >= self.term.height) return;
|
||||
try rc.moveCursorTo(line.*, 0);
|
||||
var rpw = rc.restrictedPaddingWriter(self.term.width);
|
||||
const writer = rpw.writer();
|
||||
try writer.writeByte(' ');
|
||||
if (line.* == first) {
|
||||
try writer.writeAll(button);
|
||||
try writer.writeAll(": ");
|
||||
} else {
|
||||
try writer.writeByteNTimes(' ', button.len + ": ".len);
|
||||
}
|
||||
try writer.writeAll(l);
|
||||
try rpw.finish();
|
||||
line.* += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn handleSigWinch(_: c_int) callconv(.C) void {
|
||||
tty_context.term.fetchSize() catch {};
|
||||
tty_context.render() catch {};
|
||||
}
|
||||
};
|
||||
|
||||
var tty_context: TtyContext = undefined;
|
||||
|
||||
/// Returned pin is owned by context.gpa.
|
||||
pub fn run(getpin: bool) !?[]const u8 {
|
||||
tty_context = .{};
|
||||
return (try tty_context.run(getpin));
|
||||
}
|
10
src/util.zig
10
src/util.zig
|
@ -1,10 +0,0 @@
|
|||
const std = @import("std");
|
||||
const unicode = std.unicode;
|
||||
|
||||
pub fn unicodeLen(bytes: []const u8) !usize {
|
||||
var view = try unicode.Utf8View.init(bytes);
|
||||
var len: usize = 0;
|
||||
var it = view.iterator();
|
||||
while (it.nextCodepointSlice()) |_| : (len += 1) {}
|
||||
return len;
|
||||
}
|
|
@ -15,50 +15,9 @@ const wayland = @import("wayland");
|
|||
const wl = wayland.client.wl;
|
||||
const zwlr = wayland.client.zwlr;
|
||||
|
||||
const util = @import("util.zig");
|
||||
|
||||
const context = &@import("wayprompt.zig").context;
|
||||
|
||||
const Utf8String = struct {
|
||||
buffer: std.ArrayListUnmanaged(u8) = .{},
|
||||
len: usize = 0,
|
||||
|
||||
pub fn appendSlice(self: *Utf8String, str: []const u8) !void {
|
||||
const len = try unicode.utf8CountCodepoints(str);
|
||||
const alloc = context.gpa.allocator();
|
||||
try self.buffer.appendSlice(alloc, str);
|
||||
self.len += len;
|
||||
}
|
||||
|
||||
pub fn deleteBackwards(self: *Utf8String) void {
|
||||
if (self.buffer.items.len == 0) return;
|
||||
const alloc = context.gpa.allocator();
|
||||
var i: usize = self.buffer.items.len - 1;
|
||||
while (i >= 0) : (i -= 1) {
|
||||
_ = unicode.utf8ByteSequenceLength(self.buffer.items[i]) catch continue;
|
||||
self.buffer.shrinkAndFree(alloc, i);
|
||||
self.len -= 1;
|
||||
return;
|
||||
}
|
||||
unreachable;
|
||||
}
|
||||
|
||||
pub fn toOwnedSlice(self: *Utf8String) ?[]const u8 {
|
||||
const alloc = context.gpa.allocator();
|
||||
defer self.* = .{};
|
||||
if (self.buffer.items.len > 0) {
|
||||
return self.buffer.toOwnedSlice(alloc);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Utf8String) void {
|
||||
const alloc = context.gpa.allocator();
|
||||
self.buffer.deinit(alloc);
|
||||
self.* = .{};
|
||||
}
|
||||
};
|
||||
const Utf8String = @import("Utf8String.zig");
|
||||
|
||||
const HotSpot = struct {
|
||||
const Effect = enum { cancel, ok, notok };
|
||||
|
@ -931,7 +890,7 @@ const Buffer = struct {
|
|||
}
|
||||
};
|
||||
|
||||
pub const WaylandContext = struct {
|
||||
const WaylandContext = struct {
|
||||
title: ?TextView = null,
|
||||
description: ?TextView = null,
|
||||
prompt: ?TextView = null,
|
||||
|
@ -957,8 +916,6 @@ pub const WaylandContext = struct {
|
|||
pin: Utf8String = .{},
|
||||
|
||||
pub fn abort(self: *WaylandContext, reason: anyerror) void {
|
||||
self.pin.deinit();
|
||||
self.pin = .{};
|
||||
self.loop = false;
|
||||
self.exit_reason = reason;
|
||||
}
|
||||
|
@ -966,6 +923,12 @@ pub const WaylandContext = struct {
|
|||
pub fn run(self: *WaylandContext, getpin: bool) !?[]const u8 {
|
||||
self.getpin = getpin;
|
||||
|
||||
const wayland_display = blk: {
|
||||
if (context.wayland_display) |wd| break :blk wd;
|
||||
if (os.getenv("WAYLAND_DISPLAY")) |wd| break :blk wd;
|
||||
return error.NoWaylandDisplay;
|
||||
};
|
||||
|
||||
_ = fcft.init(.never, false, .none);
|
||||
defer fcft.fini();
|
||||
|
||||
|
@ -992,12 +955,6 @@ pub const WaylandContext = struct {
|
|||
if (context.cancel) |cancel| self.cancel = try TextView.new(mem.trim(u8, cancel, &ascii.spaces), font_regular);
|
||||
defer if (self.cancel) |cancel| cancel.deinit();
|
||||
|
||||
const wayland_display = blk: {
|
||||
if (context.wayland_display) |wd| break :blk wd;
|
||||
if (os.getenv("WAYLAND_DISPLAY")) |wd| break :blk wd;
|
||||
return error.NoWaylandDisplay;
|
||||
};
|
||||
|
||||
const display = try wl.Display.connect(@ptrCast([*:0]const u8, wayland_display.ptr));
|
||||
defer display.disconnect();
|
||||
|
||||
|
@ -1028,10 +985,7 @@ pub const WaylandContext = struct {
|
|||
alloc.destroy(node);
|
||||
}
|
||||
}
|
||||
errdefer {
|
||||
self.pin.deinit();
|
||||
self.pin = .{};
|
||||
}
|
||||
errdefer self.pin.deinit();
|
||||
|
||||
// Per pinentry protocol documentation, the client may not send us anything
|
||||
// while it is waiting for a data response. So it's fine to just jump into
|
||||
|
@ -1041,11 +995,8 @@ pub const WaylandContext = struct {
|
|||
}
|
||||
|
||||
if (self.exit_reason) |reason| {
|
||||
debug.assert(self.pin.len == 0);
|
||||
return reason;
|
||||
}
|
||||
|
||||
if (self.getpin) {
|
||||
} else if (self.getpin) {
|
||||
return self.pin.toOwnedSlice();
|
||||
} else {
|
||||
debug.assert(self.pin.len == 0);
|
||||
|
|
|
@ -50,6 +50,7 @@ const Context = struct {
|
|||
// We may not have WAYLAND_DISPLAY in our env when we get started, or maybe
|
||||
// even a bad one. However the gpg-agent will likely send us its own.
|
||||
wayland_display: ?[:0]const u8 = null,
|
||||
tty_name: ?[:0]const u8 = null,
|
||||
|
||||
/// Release all allocated objects.
|
||||
pub fn reset(self: *Context) void {
|
||||
|
@ -86,6 +87,10 @@ const Context = struct {
|
|||
alloc.free(t);
|
||||
self.wayland_display = null;
|
||||
}
|
||||
if (self.tty_name) |t| {
|
||||
alloc.free(t);
|
||||
self.tty_name = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue