Skip to content
Permalink
Browse files

Implemented frame handling (for landslide)

  • Loading branch information...
mitotic committed Sep 21, 2012
1 parent c342837 commit 79b608c6f815b0a53c039c963623c3e12c3992d4
Showing with 75 additions and 54 deletions.
  1. +9 −2 graphterm/bin/giframe
  2. +10 −10 graphterm/gtermhost.py
  3. +13 −13 graphterm/gtermserver.py
  4. +43 −29 graphterm/lineterm.py
@@ -20,6 +20,9 @@ parser = OptionParser(usage=usage)
parser.add_option("", "--opacity", dest="opacity", default=1.0,
help="Feed opacity (default: 1.0)")

parser.add_option("-n", "--nofooter", action="store_true", dest="nofooter", default=False,
help="Suppress footer")

(options, args) = parser.parse_args()
if args:
if args[0].startswith("http:") or args[0].startswith("https:"):
@@ -45,9 +48,13 @@ else:

iframe_url = gtermapi.create_blob(content, content_type="text/html")

IFRAMEFORMAT = '<iframe src="%s" width="100%%" height="95%%"></iframe><pre>Click here and type Control-C to exit</pre>'
headers = {"opacity": options.opacity}

IFRAMEFORMAT = '<iframe src="%s" width="100%%" height="'+str(100 if options.nofooter else 95)+'%%"></iframe>'
if not options.nofooter:
IFRAMEFORMAT += '<pre class="gterm-frame-footer">Click here and type Control-C to exit</pre>'

gtermapi.write_html(IFRAMEFORMAT % iframe_url, display="fullscreen", add_headers={"opacity": options.opacity})
gtermapi.write_html(IFRAMEFORMAT % iframe_url, display="fullscreen", add_headers=headers)

try:
while True:
@@ -155,7 +155,7 @@ def shutdown(self):

def handle_connect(self):
normalized_host = get_normalized_host(self.connection_id)
self.remote_response("", [["term_params", {"version": about.version,
self.remote_response("", "", [["term_params", {"version": about.version,
"min_version": about.min_version,
"host_secret": self.host_secret,
"normalized_host": normalized_host,
@@ -219,15 +219,15 @@ def paste_command(self, term_name, command_line):
except Exception, excp:
logging.warning("gtermhost: Error in paste_command: %s", excp)

def screen_callback(self, term_name, command, arg):
def screen_callback(self, term_name, response_id, command, arg):
# Invoked in lineterm thread; schedule callback in ioloop
if command == "create_blob":
self.blob_cache.add_blob(*arg)
else:
self.send_request_threadsafe("response", term_name, [["terminal", command, arg]])
self.send_request_threadsafe("response", term_name, response_id, [["terminal", command, arg]])

def remote_response(self, term_name, message_list):
self.send_request_threadsafe("response", term_name, message_list)
def remote_response(self, term_name, websocket_id, message_list):
self.send_request_threadsafe("response", term_name, websocket_id, message_list)

def remote_request(self, term_name, req_list):
"""
@@ -262,7 +262,7 @@ def remote_request(self, term_name, req_list):

elif action == "reconnect":
if self.lineterm:
self.lineterm.reconnect(term_name)
self.lineterm.reconnect(term_name, cmd[0])

elif action == "set_size":
if term_name != OSHELL_NAME:
@@ -444,12 +444,12 @@ def remote_request(self, term_name, req_list):
logging.warning("remote_request: ERROR %s", cmd[0])
else:
raise Exception("Invalid action: "+action)
self.remote_response(term_name, resp_list);
self.remote_response(term_name, "", resp_list);
except Exception, excp:
import traceback
errmsg = "%s\n%s" % (excp, traceback.format_exc())
print >> sys.stderr, "TerminalClient.remote_request: "+errmsg
self.remote_response(term_name, [["errmsg", errmsg]])
self.remote_response(term_name, "", [["errmsg", errmsg]])
##self.shutdown()


@@ -463,7 +463,7 @@ def set_client(self, oshell_client):
def logmessage(self, log_level, msg, exc_info=None, logtype="", plaintext=""):
# If log_level is None, always display message
if self.oshell_client and (log_level is None or log_level >= self.log_level):
self.oshell_client.remote_response(OSHELL_NAME, [["log", "", [logtype, log_level, msg]]])
self.oshell_client.remote_response(OSHELL_NAME, "", [["log", "", [logtype, log_level, msg]]])

if logtype or log_level is None:
sys.stderr.write((plaintext or msg)+"\n")
@@ -474,7 +474,7 @@ def editback(self, content, filepath="", filetype="", editor="", modify=False):
editor=editor, modify=modify)
params = {"editor": editor, "modify": modify, "command": "edit -f "+filepath if modify else "",
"filepath": filepath, "filetype": filetype}
self.oshell_client.remote_response(OSHELL_NAME, [["edit", params, base64.b64encode(content) if content else ""]])
self.oshell_client.remote_response(OSHELL_NAME, "", [["edit", params, base64.b64encode(content) if content else ""]])
return (None, None)

if otrace:
@@ -410,13 +410,9 @@ def open(self):
self._all_users[user][self.websocket_id] = self

display_splash = self.controller and self._counter[0] <= 2
matchpaths = TerminalConnection.get_matching_paths(self.wildcard) if self.wildcard else [path]
for matchpath in matchpaths:
matchhost, matchterm = matchpath.split("/")
TerminalConnection.send_to_connection(matchhost, "request", matchterm, [["reconnect"]])

normalized_host, host_secret = "", ""
if not self.wildcard:
TerminalConnection.send_to_connection(host, "request", term_name, [["reconnect", self.websocket_id]])
normalized_host = gtermhost.get_normalized_host(host)
if self.authorized["auth_type"] in ("null_auth", "code_auth"):
host_secret = TerminalConnection.host_secrets.get(normalized_host)
@@ -531,19 +527,20 @@ def on_message(self, message):
if msg[1]:
self._webcast_paths[self.remote_path] = time.time()

elif msg[0] == "edit_broadcast":
elif msg[0] == "broadcast":
if self.wildcard:
continue
ws_list = GTSocket._watch_set.get(self.remote_path)
if not ws_list:
continue
for ws_id in ws_list:
if ws_id != self.websocket_id:
# Broadcast to all watchers (excluding self)
# Broadcast to all watchers (excluding originator)
ws = GTSocket._all_websockets.get(ws_id)
if ws:
try:
ws.write_message(json.dumps([msg]))
# Strip broadcast prefix from message
ws.write_message(json.dumps([msg[1:]]))
except Exception, excp:
logging.error("edit_broadcast: ERROR %s", excp)
try:
@@ -638,7 +635,7 @@ def remote_terminal_update(self, term_name=None, add_flag=True):
self.term_set.discard(term_name)
return term_name

def remote_response(self, term_name, msg_list):
def remote_response(self, term_name, websocket_id, msg_list):
fwd_list = []
for msg in msg_list:
if msg[0] == "term_params":
@@ -671,11 +668,14 @@ def remote_response(self, term_name, msg_list):
self.allow_feedback.discard(term_name)

path = self.connection_id + "/" + term_name
ws_list = GTSocket._watch_set.get(path) or set()
if websocket_id:
ws_list = [websocket_id]
else:
ws_list = GTSocket._watch_set.get(path) or set()

for regexp, ws_id in GTSocket._wildcards.itervalues():
if regexp.match(path):
ws_list.add(ws_id)
for regexp, ws_id in GTSocket._wildcards.itervalues():
if regexp.match(path):
ws_list.add(ws_id)

if not ws_list:
return
@@ -420,10 +420,6 @@ def clear_buf(self):
self.scroll_lines = []
self.full_update = True

def reconnect(self):
self.last_scroll_count = self.current_scroll_count - len(self.scroll_lines)
self.full_update = True

def clear_last_entry(self, last_entry_index=None):
if not self.scroll_lines or self.entry_index <= 0:
return
@@ -463,13 +459,12 @@ def scroll_buf_up(self, line, meta, offset=0):
self.scroll_lines.pop(0)

def update(self, active_rows, width, height, cursorx, cursory, main_screen,
alt_screen=None, prompt=[]):
alt_screen=None, prompt=[], reconnecting=False):
""" Returns full_update, update_rows, update_scroll
"""
full_update = self.full_update
self.full_update = False
full_update = self.full_update or reconnecting

if width != self.width or height != self.height:
if not reconnecting and (width != self.width or height != self.height):
self.width = width
self.height = height
full_update = True
@@ -499,15 +494,21 @@ def update(self, active_rows, width, height, cursorx, cursory, main_screen,
offset = prompt_offset(dump(new_row), prompt, screen.meta[j])
update_rows.append([j, offset, "", None, self.dumprichtext(new_row, trim=True)])

self.cursorx = cursorx
self.cursory = cursory
self.main_screen = main_screen.make_copy() if main_screen else None
self.alt_screen = alt_screen.make_copy() if alt_screen else None
if self.last_scroll_count < self.current_scroll_count:
if reconnecting:
update_scroll = self.scroll_lines[:]
elif self.last_scroll_count < self.current_scroll_count:
update_scroll = self.scroll_lines[self.last_scroll_count-self.current_scroll_count:]
else:
update_scroll = []
self.last_scroll_count = self.current_scroll_count

if not reconnecting:
self.last_scroll_count = self.current_scroll_count
self.full_update = False
self.cursorx = cursorx
self.cursory = cursory
self.main_screen = main_screen.make_copy() if main_screen else None
self.alt_screen = alt_screen.make_copy() if alt_screen else None

return full_update, update_rows, update_scroll

def dumprichtext(self, data, trim=False):
@@ -674,6 +675,7 @@ def reset(self, s=""):
self.gterm_buf_size = 0
self.gterm_entry_index = None
self.gterm_validated = False
self.gterm_output_buf = []

def resize(self, height, width):
reset_flag = (self.width != width or self.height != height)
@@ -704,9 +706,9 @@ def clear(self):
self.screen_buf.clear_buf()
self.needs_updating = True

def reconnect(self):
self.screen_buf.reconnect()
self.needs_updating = True
def reconnect(self, response_id=""):
self.update_callback(response_id=response_id)
self.graphterm_output(response_id=response_id, from_buffer=True)

def clear_last_entry(self, last_entry_index=None):
self.screen_buf.clear_last_entry(last_entry_index=last_entry_index)
@@ -753,21 +755,27 @@ def update(self):
self.update_time = time.time()
self.needs_updating = False

alt_screen = self.alt_screen if self.alt_mode else None
if not self.alt_mode:
self.scroll_screen()

self.update_callback()

def update_callback(self, response_id=""):
alt_screen = self.alt_screen if self.alt_mode else None
full_update, update_rows, update_scroll = self.screen_buf.update(self.active_rows, self.width, self.height,
self.cursor_x, self.cursor_y,
self.main_screen,
alt_screen=alt_screen,
prompt=self.prompt)
prompt=self.prompt,
reconnecting=bool(response_id))
pre_offset = len(self.prompt[0]) if self.prompt else 0
self.screen_callback(self.term_name, "row_update",
self.screen_callback(self.term_name, response_id, "row_update",
[self.alt_mode, full_update, self.active_rows,
self.width, self.height,
self.cursor_x, self.cursor_y, pre_offset,
update_rows, update_scroll])
if not response_id and (update_rows or update_scroll):
self.gterm_output_buf = []

def zero(self, y1, x1, y2, x2, screen=None):
if screen is None: screen = self.screen
@@ -1285,28 +1293,34 @@ def gterm_append(self, s):
logging.warning("No content_length specified for create_blob")
else:
# Note: blob content should be Base64 encoded
self.screen_callback(self.term_name, "create_blob",
self.screen_callback(self.term_name, "", "create_blob",
[blob_id, headers, content])
elif self.gterm_validated or plain_text:
headers["content_length"] = len(content)
params = {"validated": self.gterm_validated, "headers": headers}
self.screen_callback(self.term_name, "graphterm_output", [params,
base64.b64encode(content) if content else ""])
self.graphterm_output(params, content)
self.gterm_code = None
self.gterm_buf = None
self.gterm_buf_size = 0
self.gterm_validated = False
self.gterm_entry_index = None
return retval

def graphterm_output(self, params={}, content="", response_id="", from_buffer=False):
if not from_buffer:
self.gterm_output_buf = [params, base64.b64encode(content) if content else ""]
elif not self.gterm_output_buf:
return
self.screen_callback(self.term_name, response_id, "graphterm_output", self.gterm_output_buf)

def save_file(self, filepath, filedata):
status = ""
try:
with open(filepath, "w") as f:
f.write(base64.b64decode(filedata))
except Exception, excp:
status = str(excp)
self.screen_callback(self.term_name, "save_status", [filepath, status])
self.screen_callback(self.term_name, "", "save_status", [filepath, status])

def get_finder(self, kind, directory=""):
test_finder_head = """<table frame=none border=0>
@@ -1337,7 +1351,7 @@ def get_finder(self, kind, directory=""):
"x_gterm_response": "display_finder",
"x_gterm_parameters": {"finder_type": kind, "current_directory": ""}}
params = {"validated": self.gterm_validated, "headers": headers}
self.screen_callback(self.term_name, "graphterm_output", [params, content])
self.graphterm_output(params, content)

def click_paste(self, text, file_url="", options={}):
"""Return text or filename (and command) for pasting into command line.
@@ -1593,7 +1607,7 @@ def terminal(self, term_name=None, command="", height=25, width=80):
self.set_size(term_name, height, width)
if not is_executable(Gls_path) and not Exec_errmsg:
Exec_errmsg = True
self.screen_callback(term_name, "alert", ["File %s is not executable. Did you 'sudo gterm_setup' after 'sudo easy_install graphterm'?" % Gls_path])
self.screen_callback(term_name, "", "alert", ["File %s is not executable. Did you 'sudo gterm_setup' after 'sudo easy_install graphterm'?" % Gls_path])
return term_name, cookie

def term_env(self, term_name, cookie, export=False):
@@ -1769,12 +1783,12 @@ def click_paste(self, term_name, text, file_url="", options={}):
return ""
return term.click_paste(text, file_url=file_url, options=options)

def reconnect(self, term_name):
def reconnect(self, term_name, response_id=""):
with self.lock:
term = self.proc.get(term_name)
if not term:
return
term.reconnect()
term.reconnect(response_id=response_id)

def clear(self, term_name):
with self.lock:
@@ -1844,7 +1858,7 @@ def loop(self):
Prompt = "> "
Log_file = "term.log"
Log_file = ""
def screen_callback(term_name, command, arg):
def screen_callback(term_name, response_id, command, arg):
if command == "row_update":
alt_mode, reset, active_rows, width, height, cursorx, cursory, pre_offset, update_rows, update_scroll = arg
for row_num, row_offset, row_dir, row_markup, row_span in update_rows:

0 comments on commit 79b608c

Please sign in to comment.
You can’t perform that action at this time.