configurable kill signals
This commit is contained in:
@@ -29,6 +29,7 @@ public record AppConfig(
|
||||
double windowHeight,
|
||||
boolean kittyGraphics,
|
||||
String scrollbackEditorCommand,
|
||||
String closeSignal,
|
||||
Map<String, String> envOverride,
|
||||
Map<String, KeyBinding> keybindings
|
||||
) {
|
||||
@@ -71,6 +72,7 @@ public record AppConfig(
|
||||
doubleValue(document, "window.height", defaults.windowHeight),
|
||||
booleanValue(document, "kitty_graphics.enabled", defaults.kittyGraphics),
|
||||
stringValue(document, "scrollback.editor_command", defaults.scrollbackEditorCommand),
|
||||
closeSignalValue(document, defaults.closeSignal),
|
||||
envOverride(document, defaults.envOverride),
|
||||
keybindings(document, defaults)
|
||||
);
|
||||
@@ -92,6 +94,7 @@ public record AppConfig(
|
||||
760.0,
|
||||
true,
|
||||
defaultScrollbackEditorCommand(),
|
||||
"SIGTERM",
|
||||
Map.of(),
|
||||
Map.ofEntries(
|
||||
Map.entry("navigate_left", KeyBinding.parse("ALT+H")),
|
||||
@@ -125,11 +128,22 @@ public record AppConfig(
|
||||
windowHeight,
|
||||
kittyGraphics,
|
||||
scrollbackEditorCommand,
|
||||
closeSignal,
|
||||
envOverride,
|
||||
keybindings
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link #closeSignal} as a Linux signal number, sent to a pane's shell process when the
|
||||
* pane is closed (e.g. via the close-pane key). Falls back to SIGTERM (15) if the configured
|
||||
* name is somehow unresolvable.
|
||||
*/
|
||||
public int closeSignalNumber() {
|
||||
int number = LinuxPty.signalNumber(closeSignal);
|
||||
return number < 0 ? 15 : number;
|
||||
}
|
||||
|
||||
public void save() {
|
||||
save(configPath(), this);
|
||||
}
|
||||
@@ -156,6 +170,23 @@ public record AppConfig(
|
||||
return editor.trim() + " {file}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads {@code terminal.close_signal}, normalising it to a canonical {@code SIG*} name. An
|
||||
* unknown or unset value keeps {@code fallback} so a typo can't leave a pane unkillable.
|
||||
*/
|
||||
private static String closeSignalValue(TomlTable table, String fallback) {
|
||||
String value = stringValue(table, "terminal.close_signal", null);
|
||||
if (value == null) {
|
||||
return fallback;
|
||||
}
|
||||
if (LinuxPty.signalNumber(value) < 0) {
|
||||
System.err.println("Unknown terminal.close_signal '" + value + "', using " + fallback);
|
||||
return fallback;
|
||||
}
|
||||
String normalized = value.trim().toUpperCase(java.util.Locale.ROOT);
|
||||
return normalized.startsWith("SIG") ? normalized : "SIG" + normalized;
|
||||
}
|
||||
|
||||
private static Map<String, KeyBinding> keybindings(TomlTable table, AppConfig defaults) {
|
||||
Map<String, KeyBinding> parsed = new LinkedHashMap<>();
|
||||
for (String key : KEYBINDING_KEYS) {
|
||||
@@ -193,6 +224,7 @@ public record AppConfig(
|
||||
builder.append("rows = ").append(rows).append('\n');
|
||||
builder.append("max_scrollback = ").append(maxScrollback).append('\n');
|
||||
builder.append("shell = ").append(quotedList(shell)).append('\n');
|
||||
builder.append("close_signal = ").append(quoted(closeSignal)).append('\n');
|
||||
builder.append("font_family = ").append(quoted(fontFamily)).append('\n');
|
||||
builder.append("font_size = ").append(trimDouble(fontSize)).append("\n\n");
|
||||
builder.append("[window]\n");
|
||||
|
||||
@@ -63,7 +63,10 @@ public final class LinuxPty implements AutoCloseable {
|
||||
private static final long TIOCSWINSZ = 0x5414L;
|
||||
private static final short POSIX_SPAWN_SETSID = 0x80;
|
||||
private static final int SIGHUP = 1;
|
||||
private static final int SIGINT = 2;
|
||||
private static final int SIGQUIT = 3;
|
||||
private static final int SIGKILL = 9;
|
||||
private static final int SIGTERM = 15;
|
||||
private static final int WNOHANG = 1;
|
||||
|
||||
// struct winsize { unsigned short ws_row, ws_col, ws_xpixel, ws_ypixel; }
|
||||
@@ -105,11 +108,36 @@ public final class LinuxPty implements AutoCloseable {
|
||||
private final Object writeLock = new Object();
|
||||
private final int masterFd;
|
||||
private final int pid;
|
||||
private final int closeSignal;
|
||||
private volatile boolean closed;
|
||||
|
||||
private LinuxPty(int masterFd, int pid) {
|
||||
private LinuxPty(int masterFd, int pid, int closeSignal) {
|
||||
this.masterFd = masterFd;
|
||||
this.pid = pid;
|
||||
this.closeSignal = closeSignal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a signal name (e.g. {@code "SIGTERM"}, {@code "TERM"}, {@code "SIGKILL"}) to its
|
||||
* Linux signal number, or {@code -1} if the name is not one we recognise. Case-insensitive and
|
||||
* tolerant of a missing {@code SIG} prefix.
|
||||
*/
|
||||
public static int signalNumber(String name) {
|
||||
if (name == null) {
|
||||
return -1;
|
||||
}
|
||||
String normalized = name.trim().toUpperCase(java.util.Locale.ROOT);
|
||||
if (normalized.startsWith("SIG")) {
|
||||
normalized = normalized.substring(3);
|
||||
}
|
||||
return switch (normalized) {
|
||||
case "HUP" -> SIGHUP;
|
||||
case "INT" -> SIGINT;
|
||||
case "QUIT" -> SIGQUIT;
|
||||
case "KILL" -> SIGKILL;
|
||||
case "TERM" -> SIGTERM;
|
||||
default -> -1;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,8 +146,10 @@ public final class LinuxPty implements AutoCloseable {
|
||||
* @param argv command and arguments (e.g. {@code {"/bin/zsh", "-i"}})
|
||||
* @param environment environment for the child, as KEY=VALUE pairs
|
||||
* @param workingDirectory directory the child starts in, or {@code null} to inherit
|
||||
* @param closeSignal signal number sent to the child on {@link #close()} (e.g. SIGTERM)
|
||||
*/
|
||||
public static LinuxPty spawn(String[] argv, Map<String, String> environment, String workingDirectory) {
|
||||
public static LinuxPty spawn(String[] argv, Map<String, String> environment, String workingDirectory,
|
||||
int closeSignal) {
|
||||
Arena setup = Arena.ofConfined();
|
||||
try {
|
||||
int master = check(callInt(POSIX_OPENPT, O_RDWR | O_NOCTTY), "posix_openpt");
|
||||
@@ -158,7 +188,7 @@ public final class LinuxPty implements AutoCloseable {
|
||||
if (rc != 0) {
|
||||
throw new IllegalStateException("posix_spawnp failed for " + argv[0] + " (rc=" + rc + ")");
|
||||
}
|
||||
return new LinuxPty(master, pidOut.get(C_INT, 0));
|
||||
return new LinuxPty(master, pidOut.get(C_INT, 0), closeSignal);
|
||||
} finally {
|
||||
callInt(ATTR_DESTROY, attr);
|
||||
callInt(FA_DESTROY, actions);
|
||||
@@ -249,7 +279,7 @@ public final class LinuxPty implements AutoCloseable {
|
||||
return;
|
||||
}
|
||||
closed = true;
|
||||
callKill(pid, SIGHUP);
|
||||
callKill(pid, closeSignal);
|
||||
callInt(CLOSE, masterFd);
|
||||
reap();
|
||||
arena.close();
|
||||
|
||||
@@ -27,9 +27,10 @@ public final class ShellSession implements AutoCloseable {
|
||||
* config, not assumed here.
|
||||
*/
|
||||
public static ShellSession start(List<String> shellCommand, Map<String, String> envOverride, TerminalPane pane,
|
||||
int columns, int rows, String workingDirectory) {
|
||||
int columns, int rows, String workingDirectory, int closeSignal) {
|
||||
try {
|
||||
return spawn(shellCommand.toArray(new String[0]), envOverride, columns, rows, workingDirectory);
|
||||
return spawn(shellCommand.toArray(new String[0]), envOverride, columns, rows, workingDirectory,
|
||||
closeSignal);
|
||||
} catch (RuntimeException ex) {
|
||||
pane.write("failed to start shell: " + ex.getMessage() + "\r\n");
|
||||
throw new IllegalStateException("Could not start shell " + String.join(" ", shellCommand), ex);
|
||||
@@ -45,9 +46,10 @@ public final class ShellSession implements AutoCloseable {
|
||||
* flags. {@code command} must not be null.
|
||||
*/
|
||||
public static ShellSession startCommand(Map<String, String> envOverride, TerminalPane pane,
|
||||
int columns, int rows, String workingDirectory, String command) {
|
||||
int columns, int rows, String workingDirectory, String command, int closeSignal) {
|
||||
try {
|
||||
return spawn(new String[] {"/bin/sh", "-c", command}, envOverride, columns, rows, workingDirectory);
|
||||
return spawn(new String[] {"/bin/sh", "-c", command}, envOverride, columns, rows, workingDirectory,
|
||||
closeSignal);
|
||||
} catch (RuntimeException ex) {
|
||||
pane.write("failed to run command: " + ex.getMessage() + "\r\n");
|
||||
throw new IllegalStateException("Could not run command: " + command, ex);
|
||||
@@ -55,7 +57,7 @@ public final class ShellSession implements AutoCloseable {
|
||||
}
|
||||
|
||||
private static ShellSession spawn(String[] argv, Map<String, String> envOverride,
|
||||
int columns, int rows, String workingDirectory) {
|
||||
int columns, int rows, String workingDirectory, int closeSignal) {
|
||||
Map<String, String> environment = new HashMap<>(System.getenv());
|
||||
environment.put("TERM", "xterm-kitty");
|
||||
environment.put("COLORTERM", "truecolor");
|
||||
@@ -65,7 +67,8 @@ public final class ShellSession implements AutoCloseable {
|
||||
LinuxPty pty = LinuxPty.spawn(
|
||||
argv,
|
||||
environment,
|
||||
workingDirectory != null ? workingDirectory : System.getProperty("user.home"));
|
||||
workingDirectory != null ? workingDirectory : System.getProperty("user.home"),
|
||||
closeSignal);
|
||||
ShellSession session = new ShellSession(pty);
|
||||
session.resize(columns, rows);
|
||||
return session;
|
||||
|
||||
@@ -81,7 +81,7 @@ public final class TerminalPane implements AutoCloseable, RenderTarget {
|
||||
double widthPx, double heightPx, String workingDirectory) {
|
||||
TerminalPane pane = newPane(config, metrics, onContentChange, widthPx, heightPx);
|
||||
pane.attach(ShellSession.start(config.shell(), config.envOverride(), pane, pane.columns, pane.rows,
|
||||
workingDirectory));
|
||||
workingDirectory, config.closeSignalNumber()));
|
||||
return pane;
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ public final class TerminalPane implements AutoCloseable, RenderTarget {
|
||||
double widthPx, double heightPx, String workingDirectory, String command) {
|
||||
TerminalPane pane = newPane(config, metrics, onContentChange, widthPx, heightPx);
|
||||
pane.attach(ShellSession.startCommand(config.envOverride(), pane, pane.columns, pane.rows,
|
||||
workingDirectory, command));
|
||||
workingDirectory, command, config.closeSignalNumber()));
|
||||
return pane;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user