mirror of
https://github.com/Textualize/textual-web.git
synced 2025-10-17 02:36:40 +03:00
terminal size
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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."""
|
||||
...
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user