From 3d14bce2313f71235ffe22e177d99deb996eb04f Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Thu, 30 Apr 2015 12:04:14 +0200 Subject: [PATCH] Add help, normalize binaries, allow custom motd, Fix #46 Fix #52 Fix#67 --- bin/{16Mtest => b16M} | 0 bin/{month => bcal} | 0 bin/{hr => bhr} | 0 bin/bopen | 13 +++++++++ bin/butterfly_help | 27 ++++++++++++------ bin/nt | 4 --- butterfly.server.py | 1 + butterfly/routes.py | 61 +++++++++++++--------------------------- butterfly/templates/motd | 22 +++++++++++++++ butterfly/utils.py | 38 +++++++++++++++++++++++-- setup.py | 3 +- 11 files changed, 112 insertions(+), 57 deletions(-) rename bin/{16Mtest => b16M} (100%) rename bin/{month => bcal} (100%) rename bin/{hr => bhr} (100%) create mode 100755 bin/bopen delete mode 100755 bin/nt create mode 100644 butterfly/templates/motd diff --git a/bin/16Mtest b/bin/b16M similarity index 100% rename from bin/16Mtest rename to bin/b16M diff --git a/bin/month b/bin/bcal similarity index 100% rename from bin/month rename to bin/bcal diff --git a/bin/hr b/bin/bhr similarity index 100% rename from bin/hr rename to bin/bhr diff --git a/bin/bopen b/bin/bopen new file mode 100755 index 0000000..d9305d7 --- /dev/null +++ b/bin/bopen @@ -0,0 +1,13 @@ +#!/usr/bin/env python +import os +import webbrowser +import argparse + +parser = argparse.ArgumentParser(description='Butterfly tab opener.') +parser.add_argument( + 'location', + default=os.getcwd(), + help='Directory to open the new tab in. (Defaults to current)') +args = parser.parse_args() + +webbrowser.open('%swd%s' % (os.getenv('LOCATION'), args.location)) diff --git a/bin/butterfly_help b/bin/butterfly_help index 51ba325..fdbf1d6 100755 --- a/bin/butterfly_help +++ b/bin/butterfly_help @@ -1,9 +1,10 @@ #!/usr/bin/env python from butterfly.escapes import image +from butterfly.utils import ansi_colors import os import base64 -print("Welcome to the butterfly help.") +print(ansi_colors.white + "Welcome to the butterfly help." + ansi_colors.reset) with image('image/png'): with open( os.path.join( @@ -13,10 +14,20 @@ with image('image/png'): print(""" Butterfly is a xterm compliant terminal built with python and javascript. -Terminal functionalities: -[Alt] + [a] : Set an alarm which sends a notification when a modification is detected. -[Ctrl] + [Shift] + [Up] : Trigger visual selection mode. Hitting [Enter] inserts the selection in the prompt. -[ScrollLock] : Lock the scrolling to the current position. Press again to release. -[Alt] + [z] : Escape: don't catch the next pressed key. Useful for using native search for example. ([Alt] + [z] then [Ctrl] + [f]). -[Ctrl] + [c] <> : Cut the output when [Ctrl] + [c] is not enough. -""") +{title}Terminal functionalities:{reset} + {strong}[Alt] + [a] : {reset}Set an alarm which sends a notification when a modification is detected. + {strong}[Ctrl] + [Shift] + [Up] : {reset}Trigger visual selection mode. Hitting [Enter] inserts the selection in the prompt. + {strong}[ScrollLock] : {reset}Lock the scrolling to the current position. Press again to release. + {strong}[Alt] + [z] : {reset}Escape: don't catch the next pressed key. Useful for using native search for example. ([Alt] + [z] then [Ctrl] + [f]). + {strong}[Ctrl] + [c] <> : {reset}Cut the output when [Ctrl] + [c] is not enough. + +{title}Butterfly programs:{reset} + {strong}bcat : {reset}A wrapper around cat allowing to display images as instead of binary. + {strong}bopen : {reset}Open a new terminal at specified location. + {strong}b16M : {reset}Test the 16M colors support in terminal. + {strong}bhr : {reset}Put a html hr. This is a test and needs --allow-html-escapes flag. + {strong}bcal : {reset}Display current month using html. This is a test and needs --allow-html-escapes flag. +""".format( + title=ansi_colors.light_blue, + strong=ansi_colors.white, + reset=ansi_colors.reset)) diff --git a/bin/nt b/bin/nt deleted file mode 100755 index cd6b0a5..0000000 --- a/bin/nt +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env python -import os -import webbrowser -webbrowser.open('%swd%s' % (os.getenv('LOCATION'), os.getcwd())) diff --git a/butterfly.server.py b/butterfly.server.py index c639304..beee978 100755 --- a/butterfly.server.py +++ b/butterfly.server.py @@ -36,6 +36,7 @@ tornado.options.define("more", default=False, tornado.options.define("host", default='localhost', help="Server host") tornado.options.define("port", default=57575, type=int, help="Server port") tornado.options.define("shell", help="Shell to execute at login") +tornado.options.define("motd", default='motd', help="Path to the motd file.") tornado.options.define("cmd", help="Command to run instead of shell, f.i.: 'ls -l'") tornado.options.define("unsecure", default=False, diff --git a/butterfly/routes.py b/butterfly/routes.py index 28ab78e..259f60e 100644 --- a/butterfly/routes.py +++ b/butterfly/routes.py @@ -49,44 +49,6 @@ def u(s): return s -def motd(socket): - return ( -''' -B ` ' - ;,,, ` ' ,,,; - `Y888888bo. : : .od888888Y' - 8888888888b. : : .d8888888888 - 88888Y' `Y8b. ` ' .d8Y' `Y88888 - j88888 R.db.B Yb. ' ' .dY R.db.B 88888k - `888 RY88YB `b ( ) d' RY88YB 888' - 888b R'"B ,', R"'B d888 - j888888bd8gf"' ':' `"?g8bd888888k - R'Y'B .8' d' 'b '8. R'Y'X - R!B .8' RdbB d'; ;`b RdbB '8. R!B - d88 R`'B 8 ; ; 8 R`'B 88b Rbutterfly Zv %sB - d888b .g8 ',' 8g. d888b - :888888888Y' 'Y888888888: AConnecting to:B - '! 8888888' `8888888 !' G%sB - '8Y R`Y Y'B Y8' -R Y Y AFrom:R - ! ! G%sX - -For more information type: $ butterfly_help - -''' - .replace('G', '\x1b[3%d;1m' % ( - 1 if tornado.options.options.unsecure else 2)) - .replace('B', '\x1b[34;1m') - .replace('R', '\x1b[37;1m') - .replace('Z', '\x1b[33;1m') - .replace('A', '\x1b[37;0m') - .replace('X', '\x1b[0m') - .replace('\n', '\r\n') - % (__version__, - '%s:%d' % (socket.local_addr, socket.local_port), - '%s:%d' % (socket.remote_addr, socket.remote_port))) - - @url(r'/(?:user/(.+))?/?(?:wd/(.+))?') class Index(Route): def get(self, user, path): @@ -260,6 +222,7 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler): def open(self, user, path): self.fd = None + self.closed = False if self.request.headers['Origin'] not in ( 'http://%s' % self.request.headers['Host'], 'https://%s' % self.request.headers['Host']): @@ -304,7 +267,16 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler): TermWebSocket.terminals.add(self) - self.write_message(motd(self.socket)) + motd = (self.render_string( + tornado.options.options.motd, + butterfly=self, + version=__version__, + opts=tornado.options.options, + colors=utils.ansi_colors) + .decode('utf-8') + .replace('\r', '') + .replace('\n', '\r\n')) + self.write_message(motd) self.pty() def on_message(self, message): @@ -318,7 +290,7 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler): fcntl.ioctl(self.fd, termios.TIOCSWINSZ, s) self.log.info('SIZE (%d, %d)' % (cols, rows)) elif message[0] == 'S': - self.log.info('WRIT<%r' % message) + self.log.debug('WRIT<%r' % message) self.writer.write(message[1:]) self.writer.flush() @@ -329,7 +301,7 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler): except IOError: read = '' - self.log.info('READ>%r' % read) + self.log.debug('READ>%r' % read) if read and len(read) != 0 and self.ws_connection: self.write_message(read.decode('utf-8', 'replace')) else: @@ -338,10 +310,13 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler): if events & ioloop.ERROR: self.log.info('Error on fd %d, closing' % fd) # Terminated + self.on_close() self.close() def on_close(self): - utils.rm_user_info(self.fd, self.pid, self.callee.name) + if self.closed: + return + self.closed = True if self.fd is not None: self.log.info('Closing fd %d' % self.fd) @@ -349,6 +324,8 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler): self.log.info('pid is 0') return + utils.rm_user_info(self.fd, self.pid, self.callee.name) + try: ioloop.remove_handler(self.fd) except Exception: diff --git a/butterfly/templates/motd b/butterfly/templates/motd new file mode 100644 index 0000000..774e915 --- /dev/null +++ b/butterfly/templates/motd @@ -0,0 +1,22 @@ + +{{ colors.blue }} ` ' + ;,,, ` ' ,,,; + `Y888888bo. : : .od888888Y' + 8888888888b. : : .d8888888888 + 88888Y' `Y8b. ` ' .d8Y' `Y88888 + j88888 {{ colors.white }}.db.{{ colors.blue }} Yb. ' ' .dY {{ colors.white }}.db.{{ colors.blue }} 88888k + `888 {{ colors.white }}Y88Y{{ colors.blue }} `b ( ) d' {{ colors.white }}Y88Y{{ colors.blue }} 888' + 888b {{ colors.white }}'"{{ colors.blue }} ,', {{ colors.white }}"'{{ colors.blue }} d888 + j888888bd8gf"' ':' `"?g8bd888888k + {{ colors.white }}'Y'{{ colors.blue }} .8' d' 'b '8. {{ colors.white }}'Y'{{ colors.reset }} + {{ colors.white }}!{{ colors.blue }} .8' {{ colors.white }}db{{ colors.blue }} d'; ;`b {{ colors.white }}db{{ colors.blue }} '8. {{ colors.white }}!{{ colors.blue }} + d88 {{ colors.white }}`'{{ colors.blue }} 8 ; ; 8 {{ colors.white }}`'{{ colors.blue }} 88b {{ colors.white }}butterfly {{ colors.yellow }}v {{ version }}{{ colors.blue }} + d888b .g8 ',' 8g. d888b + :888888888Y' 'Y888888888: {{ colors.light_white }}Connecting to:{{ colors.blue }} + '! 8888888' `8888888 !' {{ colors.red if opts.unsecure else colors.green }}{{ butterfly.socket.local_addr }}:{{ butterfly.socket.local_port }}{{ colors.blue }} + '8Y {{ colors.white }}`Y Y'{{ colors.blue }} Y8' +{{ colors.white }} Y Y {{ colors.light_white }}From:{{ colors.white }} + ! ! {{ colors.red if opts.unsecure else colors.green }}{{ butterfly.socket.remote_addr }}:{{ butterfly.socket.remote_port }}{{ colors.reset }} + +For more information type: $ butterfly_help + diff --git a/butterfly/utils.py b/butterfly/utils.py index 0734164..85714c5 100644 --- a/butterfly/utils.py +++ b/butterfly/utils.py @@ -290,6 +290,10 @@ def utmp_line(type, pid, fd, user, host, ts): def add_user_info(fd, pid, user, host): + # Freebsd format is not yet supported. + # Please submit PR + if sys.platform != 'linux': + return utmp = utmp_line(7, pid, fd, user, host, time.time()) for kind, file in { 'utmp': get_utmp_file(), @@ -310,10 +314,12 @@ def add_user_info(fd, pid, user, host): else: f.write(utmp_struct.pack(*utmp)) except Exception: - log.warning('Unable to write utmp info to ' + file, exc_info=True) + log.info('Unable to write utmp info to ' + file, exc_info=True) def rm_user_info(fd, pid, user): + if sys.platform != 'linux': + return utmp = utmp_line(8, pid, fd, user, '', time.time()) for kind, file in { 'utmp': get_utmp_file(), @@ -335,4 +341,32 @@ def rm_user_info(fd, pid, user): f.write(utmp_struct.pack(*utmp)) except Exception: - log.warning('Unable to update utmp info to ' + file, exc_info=True) + log.info('Unable to update utmp info to ' + file, exc_info=True) + + +class AnsiColors(object): + colors = { + 'black': 30, + 'red': 31, + 'green': 32, + 'yellow': 33, + 'blue': 34, + 'magenta': 35, + 'cyan': 36, + 'white': 37 + } + + def __getattr__(self, key): + bold = True + if key.startswith('light_'): + bold = False + key = key[len('light_'):] + if key in self.colors: + return '\x1b[%d%sm' % ( + self.colors[key], + ';1' if bold else '') + if key == 'reset': + return '\x1b[0m' + return '' + +ansi_colors = AnsiColors() diff --git a/setup.py b/setup.py index 089e9ab..da418c1 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,8 @@ options = dict( 'static/images/favicon.png', 'static/main.css', 'static/*.min.js', - 'templates/index.html' + 'templates/index.html', + 'templates/motd' ] }, classifiers=[