Obtain terminal type (RFC 1091)
authorJeremy Stanley <fungi@yuggoth.org>
Sun, 12 Apr 2020 20:17:57 +0000 (20:17 +0000)
committerJeremy Stanley <fungi@yuggoth.org>
Sun, 12 Apr 2020 20:17:57 +0000 (20:17 +0000)
Implement rudimentary support for determining the terminal type
reported by RFC 1091 TTYPE compatible clients, and store any initial
value returned in the User.ttype attribute. This implementation does
not iterate over SEND TTYPE commands until UNKNOWN is returned, it
only takes the first value returned and assumes this is the default
terminal type for the user's current connection.

mudpy/misc.py
mudpy/telnet.py

index d753559..0543aa1 100644 (file)
@@ -503,6 +503,7 @@ class User:
         self.password_tries = 0
         self.state = "telopt_negotiation"
         self.telopts = {}
+        self.ttype = None
         self.universe = universe
 
     def quit(self):
@@ -805,6 +806,13 @@ class User:
         else:
             self.check_idle()
 
+        # ask the client for their current terminal type (RFC 1091); it's None
+        # if it's not been initialized, the empty string if it has but the
+        # output was indeterminate, "UNKNOWN" if the client specified it has no
+        # terminal types to supply
+        if self.ttype is None:
+            mudpy.telnet.request_ttype(self)
+
         # if output is paused, decrement the counter
         if self.state == "telopt_negotiation":
             if self.negotiation_pause:
index e483c13..35fb1c6 100644 (file)
@@ -1,6 +1,6 @@
 """Telnet functions and constants for the mudpy engine."""
 
-# Copyright (c) 2004-2019 mudpy authors. Permission to use, copy,
+# Copyright (c) 2004-2020 mudpy authors. Permission to use, copy,
 # modify, and distribute this software is granted under terms
 # provided in the LICENSE file distributed with this software.
 
@@ -24,11 +24,11 @@ option_names = {
     TELOPT_LINEMODE: "line mode",
 }
 
-# TODO(fungi): implement support for RFC 1091 terminal type information
 supported = (
     TELOPT_BINARY,
     TELOPT_ECHO,
     TELOPT_SGA,
+    TELOPT_TTYPE,
     TELOPT_EOR,
     TELOPT_NAWS,
     TELOPT_LINEMODE
@@ -80,6 +80,14 @@ party_names = {
     US: "us",
 }
 
+# RFC 1091 commands
+IS = 0
+SEND = 1
+ttype_command_names = {
+    IS: "is",
+    SEND: "send",
+}
+
 
 def log(message, user):
     """Log debugging info for Telnet client/server interactions."""
@@ -163,6 +171,17 @@ def disable(user, telopt, party):
         user.telopts[(telopt, party)] = WANTNO
 
 
+def request_ttype(user):
+    """Clear and request the terminal type."""
+
+    # only actually request if the corresponding telopt is enabled
+    if is_enabled(user, TELOPT_TTYPE, HIM):
+        # set to the empty string to indicate it's been requested
+        user.ttype = ""
+        user.send(telnet_proto(IAC, SB, TELOPT_TTYPE, SEND, IAC, SE), raw=True)
+        log('Sent terminal type request to', user)
+
+
 def negotiate_telnet_options(user):
     """Reply to and remove telnet negotiation options from partial_input."""
 
@@ -245,11 +264,14 @@ def negotiate_telnet_options(user):
         # subnegotiation options
         elif len_text > position + 4 and command is SB:
             telopt = text[position + 2]
-            if telopt is TELOPT_NAWS:
-                user.columns = (
-                    text[position + 3] * 256 + text[position + 4])
             end_subnegotiation = text.find(telnet_proto(IAC, SE), position)
             if end_subnegotiation > 0:
+                if telopt is TELOPT_NAWS:
+                    user.columns = (
+                        text[position + 3] * 256 + text[position + 4])
+                elif telopt is TELOPT_TTYPE and text[position + 3] is IS:
+                    user.ttype = (
+                        text[position + 4:end_subnegotiation]).decode("ascii")
                 text = text[:position] + text[end_subnegotiation + 2:]
             else:
                 position += 1