terminal size

This commit is contained in:
Will McGugan
2023-08-27 08:39:04 +01:00
parent 6b489c12cf
commit e802f6d013
7 changed files with 72 additions and 17 deletions

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "textual_web"
version = "0.3.0"
version = "0.4.0"
description = "Serve Textual apps"
authors = ["Will McGugan <will@textualize.io>"]
license = "MIT"

View File

@@ -126,13 +126,15 @@ class AppSession(Session):
if self._process is not None:
yield "returncode", self._process.returncode, None
async def open(self) -> None:
async def open(self, width: int = 80, height: int = 24) -> None:
"""Open the process."""
environment = dict(os.environ.copy())
environment["TEXTUAL_DRIVER"] = "textual.drivers.web_driver:WebDriver"
environment["TEXTUAL_FILTERS"] = "dim"
environment["TEXTUAL_FPS"] = "60"
environment["TEXTUAL_COLOR_SYSTEM"] = "truecolor"
environment["COLUMNS"] = str(width)
environment["ROWS"] = str(height)
if self.devtools:
environment["TEXTUAL"] = "debug,devtools"
environment["TEXTUAL_LOG"] = "textual.log"
@@ -149,6 +151,7 @@ class AppSession(Session):
)
finally:
os.chdir(cwd)
await self.set_terminal_size(width, height)
log.debug("opened %r; %r", self.command, self._process)
self.start_time = monotonic()
@@ -221,7 +224,6 @@ class AppSession(Session):
if line == b"__GANGLION__\n":
ready = True
break
if ready:
while True:
type_bytes = await readexactly(1)

View File

@@ -345,6 +345,7 @@ class GanglionClient(Handlers):
SessionID(packet.session_id),
RouteKey(packet.route_key),
devtools=self._devtools,
size=(packet.width, packet.height),
)
if session_process is None:
log.debug("Failed to create session")

View File

@@ -1,7 +1,7 @@
"""
This file is auto-generated from packets.yml and packets.py.template
Time: Thu Jul 27 15:47:42 2023
Time: Sun Aug 27 07:38:29 2023
Version: 1
To regenerate run `make packets.py` (in src directory)
@@ -328,6 +328,8 @@ class SessionOpen(Packet):
app_id (str): Application identity.
application_slug (str): Application slug.
route_key (str): Route key
width (int): Terminal width.
height (int): Terminal height.
"""
@@ -343,21 +345,43 @@ class SessionOpen(Packet):
("app_id", str),
("application_slug", str),
("route_key", str),
("width", int),
("height", int),
]
_attribute_count = 4
_attribute_count = 6
_get_handler = attrgetter("on_session_open")
def __new__(
cls, session_id: str, app_id: str, application_slug: str, route_key: str
cls,
session_id: str,
app_id: str,
application_slug: str,
route_key: str,
width: int,
height: int,
) -> "SessionOpen":
return tuple.__new__(
cls,
(PacketType.SESSION_OPEN, session_id, app_id, application_slug, route_key),
(
PacketType.SESSION_OPEN,
session_id,
app_id,
application_slug,
route_key,
width,
height,
),
)
@classmethod
def build(
cls, session_id: str, app_id: str, application_slug: str, route_key: str
cls,
session_id: str,
app_id: str,
application_slug: str,
route_key: str,
width: int,
height: int,
) -> "SessionOpen":
"""Build and validate a packet from its attributes."""
if not isinstance(session_id, str):
@@ -376,20 +400,38 @@ class SessionOpen(Packet):
raise TypeError(
f'packets.SessionOpen Type of "route_key" incorrect; expected str, found {type(route_key)}'
)
if not isinstance(width, int):
raise TypeError(
f'packets.SessionOpen Type of "width" incorrect; expected int, found {type(width)}'
)
if not isinstance(height, int):
raise TypeError(
f'packets.SessionOpen Type of "height" incorrect; expected int, found {type(height)}'
)
return tuple.__new__(
cls,
(PacketType.SESSION_OPEN, session_id, app_id, application_slug, route_key),
(
PacketType.SESSION_OPEN,
session_id,
app_id,
application_slug,
route_key,
width,
height,
),
)
def __repr__(self) -> str:
_type, session_id, app_id, application_slug, route_key = self
return f"SessionOpen({abbreviate_repr(session_id)}, {abbreviate_repr(app_id)}, {abbreviate_repr(application_slug)}, {abbreviate_repr(route_key)})"
_type, session_id, app_id, application_slug, route_key, width, height = self
return f"SessionOpen({abbreviate_repr(session_id)}, {abbreviate_repr(app_id)}, {abbreviate_repr(application_slug)}, {abbreviate_repr(route_key)}, {abbreviate_repr(width)}, {abbreviate_repr(height)})"
def __rich_repr__(self) -> rich.repr.Result:
yield "session_id", self.session_id
yield "app_id", self.app_id
yield "application_slug", self.application_slug
yield "route_key", self.route_key
yield "width", self.width
yield "height", self.height
@property
def session_id(self) -> str:
@@ -411,6 +453,16 @@ class SessionOpen(Packet):
"""Route key"""
return self[4]
@property
def width(self) -> int:
"""Terminal width."""
return self[5]
@property
def height(self) -> int:
"""Terminal height."""
return self[6]
# PacketType.SESSION_CLOSE (7)
class SessionClose(Packet):

View File

@@ -33,7 +33,7 @@ class Session(ABC):
self._connector = SessionConnector()
@abstractmethod
async def open(self) -> None:
async def open(self, width: int = 80, height: int = 24) -> None:
"""Open the session."""
...

View File

@@ -98,6 +98,7 @@ class SessionManager:
session_id: SessionID,
route_key: RouteKey,
devtools: bool = False,
size: tuple[int, int] = (80, 24),
) -> Session | None:
"""Create a new seession.
@@ -137,7 +138,7 @@ class SessionManager:
self.sessions[session_id] = session_process
self.routes[route_key] = session_id
await session_process.open()
await session_process.open(*size)
return session_process

View File

@@ -43,17 +43,17 @@ class TerminalSession(Session):
yield "session_id", self.session_id
yield "command", self.command
async def open(self, argv=None) -> None:
async def open(self, width: int = 80, height: int = 24) -> None:
pid, master_fd = pty.fork()
self.pid = pid
self.master_fd = master_fd
if pid == pty.CHILD:
if argv is None:
argv = [self.command]
argv = [self.command]
try:
os.execlp(argv[0], *argv) ## Exits the app
except Exception:
os._exit(0)
self._set_terminal_size(width, height)
def _set_terminal_size(self, width: int, height: int) -> None:
buf = array.array("h", [height, width, 0, 0])
@@ -76,7 +76,6 @@ class TerminalSession(Session):
on_data = self._connector.on_data
on_close = self._connector.on_close
try:
self._set_terminal_size(80, 24)
while True:
data = await queue.get() or None
if data is None: