mirror of
https://github.com/lihop/godot-xterm.git
synced 2025-05-08 05:15:30 +02:00
Refactor PTY
PTY now provides a public interface to an underlying instance of PTYNative. The PTYNative class can be extended as appropriate for each platform and the platform-specific implementation will be selected by PTY at runtime.
This commit is contained in:
parent
d11853e9f6
commit
df32ee3c18
8 changed files with 539 additions and 333 deletions
|
@ -1,131 +1,158 @@
|
|||
# Derived from https://github.com/microsoft/node-pty/blob/main/src/unixTerminal.ts
|
||||
# Derived from https://github.com/microsoft/node-pty/blob/main/src/terminal.ts
|
||||
# Copyright (c) 2012-2015, Christopher Jeffrey (MIT License).
|
||||
# Copyright (c) 2016, Daniel Imms (MIT License).
|
||||
# Copyright (c) 2018, Microsoft Corporation (MIT License).
|
||||
# Copyright (c) 2021, Leroy Hopson (MIT License).
|
||||
|
||||
# Copyright (c) 2021-2022, Leroy Hopson (MIT License).
|
||||
tool
|
||||
extends "./nodes/pty/pty.gd"
|
||||
extends Node
|
||||
|
||||
const PTYUnix = preload("./nodes/pty/unix/pty_unix.gdns")
|
||||
const _LibuvUtils := preload("./nodes/pty/libuv_utils.gd")
|
||||
const _PTYNative := preload("./nodes/pty/pty_native.gd")
|
||||
const _PTYUnix := preload("./nodes/pty/unix/pty_unix.gd")
|
||||
const _Terminal := preload("./terminal.gd")
|
||||
|
||||
const FALLBACK_FILE = "sh"
|
||||
const DEFAULT_NAME := "xterm-256color"
|
||||
const DEFAULT_COLS := 80
|
||||
const DEFAULT_ROWS := 24
|
||||
const DEFAULT_ENV := {TERM = DEFAULT_NAME, COLORTERM = "truecolor"}
|
||||
|
||||
# Security warning: use this option with great caution, as opened file descriptors
|
||||
# with higher privileges might leak to the child program.
|
||||
var uid: int
|
||||
var gid: int
|
||||
# Any signal_number can be sent to the pty's process using the kill() function,
|
||||
# these are just the signals with numbers specified in the POSIX standard.
|
||||
const Signal = _PTYUnix.Signal
|
||||
|
||||
var thread: Thread
|
||||
signal data_received(data)
|
||||
signal exited(exit_code, signum)
|
||||
|
||||
var _fd: int = -1
|
||||
var _exit_cb: FuncRef
|
||||
export(NodePath) var terminal_path := NodePath() setget set_terminal_path
|
||||
|
||||
#static func get_uid() -> int:
|
||||
# return -1 # Not implemented.
|
||||
var _terminal: _Terminal = null setget _set_terminal
|
||||
|
||||
#static func get_gid() -> int:
|
||||
# return -1 # Not implemented.
|
||||
# The column size in characters.
|
||||
export(int) var cols: int = DEFAULT_COLS setget set_cols
|
||||
|
||||
# The row size in characters.
|
||||
export(int) var rows: int = DEFAULT_ROWS setget set_rows
|
||||
|
||||
# Environment to be set for the child program.
|
||||
export(Dictionary) var env := DEFAULT_ENV
|
||||
|
||||
# If true the environment variables in the env Dictionary will be merged with
|
||||
# the environment variables of the operating system (e.g. printenv), with the
|
||||
# former taking precedence in the case of conflicts.
|
||||
export(bool) var use_os_env := true
|
||||
|
||||
var _pty_native: _PTYNative
|
||||
|
||||
|
||||
func _resize(cols: int, rows: int) -> void:
|
||||
if _fd < 0:
|
||||
func _init():
|
||||
var os_name := OS.get_name()
|
||||
match os_name:
|
||||
"X11", "Server", "OSX":
|
||||
_pty_native = _PTYUnix.new()
|
||||
_:
|
||||
push_error("PTY is not support on current platform (%s)." % os_name)
|
||||
|
||||
_pty_native.connect("data_received", self, "_on_pty_native_data_received")
|
||||
_pty_native.connect("exited", self, "_on_pty_native_exited")
|
||||
|
||||
add_child(_pty_native)
|
||||
|
||||
|
||||
func set_cols(value: int):
|
||||
resize(value, rows)
|
||||
|
||||
|
||||
func set_rows(value: int):
|
||||
resize(cols, value)
|
||||
|
||||
|
||||
func set_terminal_path(value := NodePath()):
|
||||
terminal_path = value
|
||||
_set_terminal(get_node_or_null(terminal_path))
|
||||
|
||||
|
||||
func _set_terminal(value: _Terminal):
|
||||
if _terminal == value:
|
||||
return
|
||||
|
||||
PTYUnix.new().resize(_fd, cols, rows)
|
||||
# Disconect the current terminal, if any.
|
||||
if _terminal:
|
||||
disconnect("data_received", _terminal, "write")
|
||||
_terminal.disconnect("data_sent", self, "write")
|
||||
_terminal.disconnect("size_changed", self, "resizev")
|
||||
|
||||
_terminal = value
|
||||
|
||||
if not _terminal:
|
||||
return
|
||||
|
||||
# Connect the new terminal.
|
||||
# FIXME! resize(terminal.get_cols(), terminal.get_rows())
|
||||
if not _terminal.is_connected("size_changed", self, "resizev"):
|
||||
_terminal.connect("size_changed", self, "resizev")
|
||||
if not _terminal.is_connected("data_sent", self, "write"):
|
||||
_terminal.connect("data_sent", self, "write")
|
||||
if not is_connected("data_received", _terminal, "write"):
|
||||
connect("data_received", _terminal, "write")
|
||||
|
||||
|
||||
func _fork_thread(args):
|
||||
var result = preload("./nodes/pty/unix/pty_unix.gdns").new().callv("fork", args)
|
||||
return result
|
||||
# Writes data to the socket.
|
||||
# data: The data to write.
|
||||
func write(data) -> void:
|
||||
_pty_native.write(data)
|
||||
|
||||
|
||||
# Resizes the dimensions of the pty.
|
||||
# cols: The number of columns.
|
||||
# rows: The number of rows.
|
||||
func resize(cols, rows = null) -> void:
|
||||
_pty_native.resize(cols, rows)
|
||||
|
||||
|
||||
# Same as resize() but takes a Vector2.
|
||||
func resizev(size: Vector2) -> void:
|
||||
resize(size.x, size.y)
|
||||
|
||||
|
||||
# Kill the pty.
|
||||
# sigint: The signal to send. By default this is SIGHUP.
|
||||
# This is not supported on Windows.
|
||||
func kill(signum: int = Signal.SIGHUP) -> void:
|
||||
_pty_native.kill(signum)
|
||||
|
||||
|
||||
func _notification(what: int):
|
||||
match what:
|
||||
NOTIFICATION_PARENTED:
|
||||
var parent = get_parent()
|
||||
if parent is _Terminal:
|
||||
set_terminal_path(get_path_to(parent))
|
||||
|
||||
|
||||
func fork(
|
||||
file: String = OS.get_environment("SHELL"),
|
||||
args: PoolStringArray = PoolStringArray(),
|
||||
cwd = LibuvUtils.get_cwd(),
|
||||
cwd = _LibuvUtils.get_cwd(),
|
||||
p_cols: int = DEFAULT_COLS,
|
||||
p_rows: int = DEFAULT_ROWS,
|
||||
uid: int = -1,
|
||||
gid: int = -1,
|
||||
utf8 = true
|
||||
) -> int:
|
||||
# File.
|
||||
if file.empty():
|
||||
file = FALLBACK_FILE
|
||||
|
||||
# Environment variables.
|
||||
# If we are using OS env vars, sanitize them to remove variables that might confuse our terminal.
|
||||
var final_env := _sanitize_env(LibuvUtils.get_os_environ()) if use_os_env else {}
|
||||
for key in env.keys():
|
||||
final_env[key] = env[key]
|
||||
var parsed_env: PoolStringArray = _parse_env(final_env)
|
||||
|
||||
# Exit callback.
|
||||
_exit_cb = FuncRef.new()
|
||||
_exit_cb.set_instance(self)
|
||||
_exit_cb.set_function("_on_exit")
|
||||
|
||||
# Actual fork.
|
||||
var result = PTYUnix.new().fork( # VERY IMPORTANT: The must be set null or 0, otherwise will get an ENOTSOCK error after connecting our pipe to the fd.
|
||||
file, null, args, parsed_env, cwd, cols, rows, uid, gid, utf8, _exit_cb
|
||||
)
|
||||
|
||||
if result[0] != OK:
|
||||
push_error("Fork failed.")
|
||||
return FAILED
|
||||
|
||||
_fd = result[1].fd
|
||||
if _fd < 0:
|
||||
push_error("File descriptor must be a non-negative integer value.")
|
||||
return FAILED
|
||||
|
||||
_pid = result[1].pid
|
||||
|
||||
_pipe = Pipe.new()
|
||||
_pipe.open(_fd)
|
||||
|
||||
# Must connect to signal AFTER opening, otherwise we will get error ENOTSOCK.
|
||||
_pipe.connect("data_received", self, "_on_pipe_data_received")
|
||||
|
||||
return OK
|
||||
return _pty_native.fork(file, args, cwd, p_cols, p_rows, uid, gid, utf8)
|
||||
|
||||
|
||||
func open(cols: int = DEFAULT_COLS, rows: int = DEFAULT_ROWS) -> Array:
|
||||
return PTYUnix.new().open(cols, rows)
|
||||
return _pty_native.open(cols, rows)
|
||||
|
||||
|
||||
func _exit_tree():
|
||||
if _pid > 1:
|
||||
LibuvUtils.kill(_pid, Signal.SIGHUP)
|
||||
while _pipe.get_status() != 0:
|
||||
continue
|
||||
func get_master():
|
||||
return _pty_native.get_master()
|
||||
|
||||
|
||||
func _on_pipe_data_received(data):
|
||||
func _on_pty_native_data_received(data):
|
||||
emit_signal("data_received", data)
|
||||
|
||||
|
||||
func _on_exit(exit_code: int, signum: int) -> void:
|
||||
if is_instance_valid(self):
|
||||
_pid = -1
|
||||
emit_signal("exited", exit_code, signum)
|
||||
|
||||
|
||||
func _sanitize_env(env: Dictionary) -> Dictionary:
|
||||
# Make sure we didn't start our server from inside tmux.
|
||||
env.erase("TMUX")
|
||||
env.erase("TMUX_PANE")
|
||||
|
||||
# Make sure we didn't start our server from inside screen.
|
||||
# http://web.mit.edu/gnu/doc/html/screen_20.html
|
||||
env.erase("STY")
|
||||
env.erase("WINDOW")
|
||||
|
||||
# Delete some variables that might confuse our terminal.
|
||||
env.erase("WINDOWID")
|
||||
env.erase("TERMCAP")
|
||||
env.erase("COLUMNS")
|
||||
env.erase("LINES")
|
||||
|
||||
return env
|
||||
func _on_pty_native_exited(exit_code: int, signum: int) -> void:
|
||||
emit_signal("exited", exit_code, signum)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue