Add theme and session support bin.

This commit is contained in:
Florian Mounier
2015-10-05 17:03:26 +02:00
parent 6d346af6f4
commit 0e97cc8362
19 changed files with 293 additions and 100 deletions

13
butterfly/bin/bsession Executable file
View File

@@ -0,0 +1,13 @@
#!/usr/bin/env python
import os
import webbrowser
import argparse
parser = argparse.ArgumentParser(description='Butterfly session opener.')
parser.add_argument(
'session',
help='Open or rattach a butterfly session. '
'(Only in secure mode or in user unsecure mode (no su login))')
args = parser.parse_args()
webbrowser.open('%ssession/%s' % (os.getenv('LOCATION'), args.session))

87
butterfly/bin/btheme Executable file
View File

@@ -0,0 +1,87 @@
#!/usr/bin/env python
import argparse
import os
import sys
try:
from urllib.request import urlopen
except ImportError:
from urllib import urlopen
from io import BytesIO
from shutil import move
from zipfile import ZipFile
from tempfile import mkdtemp
from butterfly.escapes import sass
try:
import sass as _
_.CompileError
except Exception:
print('You must install libsass to use themes '
'(run: pip install libsass)')
sys.exit(1)
themes_url = ("https://github.com/paradoxxxzero/"
"butterfly-themes/archive/master.zip")
parser = argparse.ArgumentParser(description='Butterfly session opener.')
parser.add_argument(
'--install',
action="store_true",
help='Install preset themes from '
'https://github.com/paradoxxxzero/butterfly-themes')
parser.add_argument(
'--refresh',
action="store_true",
help='Refresh the current style')
parser.add_argument(
'-g',
action="store_true",
help='Install system wide for usage with butterfly as daemon')
parser.add_argument(
'theme',
nargs='?',
help='Theme to load. '
'Can be a sass file or a directory containing a sass file.')
args = parser.parse_args()
if args.g:
home_dir = os.path.join('/etc', 'butterfly')
else:
home_dir = os.path.expanduser(os.path.join('~', '.butterfly'))
if args.install:
print('Downloading %s...' % themes_url)
zip_ = ZipFile(BytesIO(urlopen(themes_url).read()))
print('Extracting in %s' % home_dir)
zip_.extractall(home_dir)
zip_dest = os.path.join(home_dir, 'butterfly-themes-master')
theme_dir = os.path.join(home_dir, 'themes')
if os.path.exists(theme_dir):
tmp_dir = mkdtemp()
print('%s already present. Putting it in %s.' % (
theme_dir, os.path.join(tmp_dir, 'themes')))
move(theme_dir, tmp_dir)
move(zip_dest, theme_dir)
print('%s extracted.' % theme_dir)
elif args.refresh:
with sass():
pass
else:
sss = os.path.abspath(args.theme or home_dir)
if not os.path.exists(sss):
sss = os.path.join(home_dir, 'themes', args.theme)
if not os.path.exists(sss):
print('%s not found' % sss)
sys.exit(1)
print('Loading %s' % sss)
with sass():
sys.stdout.write(sss)

View File

@@ -23,24 +23,33 @@ Butterfly is a xterm compliant terminal built with python and javascript.
{title}Butterfly programs:{reset}
{strong}bcat : {reset}A wrapper around cat allowing to display images as <img> 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.
{strong}bcat : {reset}A wrapper around cat allowing to display images as <img> instead of binary.
{strong}bopen : {reset}Open a new terminal at specified location.
{strong}bopen : {reset}Open or rattach a butterfly session. Multiplexing is supported.
{strong}b16M : {reset}Test the 16M colors support in terminal.
{strong}bhr : {reset}Put a html hr. This is a test for html output.
{strong}bcal : {reset}Display current month using html. This is also a test for html output.
{strong}btheme : {reset}Set a theme as current theme. (Can also install preset themes)
{title}Styling butterfly:{reset}
To style butterfly in sass, you need to have the libsass python library installed.
You will have to:
$ cp {main} ~/.butterfly/style.sass
or for system wide:
# cp {main} /etc/butterfly/style.sass
First check out existing themes, run:
$ btheme --install
Then try:
$ btheme solarized-dark # or C64 or abstract-image
If you want to set a theme as default add a 'style=theme' line in your butterfly.conf
If you want to style it yourself do:
$ cp {main} ~/.butterfly/style.sass # or /etc/butterfly/style.sass for system wide
and then edit this file.
You can also copy the imported sass files in the same dir.
Sass files are compiled on the fly so just reload your tab to see the changes.
You can also copy the imported sass files in the same dir. Run:
$ btheme --refresh
To update the style in the current terminal.
Finally when your theme is done please submit it to https://github.com/paradoxxxzero/butterfly-themes
It is also possible to use a style.css file and re do all the styling in css exclusively.\
""".format(

View File

@@ -32,3 +32,11 @@ def text():
yield
sys.stdout.write('\x1bP')
sys.stdout.flush()
@contextmanager
def sass():
sys.stdout.write('\x1bP;SASS|')
yield
sys.stdout.write('\x1bP')
sys.stdout.flush()

View File

@@ -22,6 +22,7 @@ import tornado.options
import tornado.process
import tornado.web
import tornado.websocket
from mimetypes import guess_type
from collections import defaultdict
from butterfly import url, Route, utils, __version__
from butterfly.terminal import Terminal
@@ -68,28 +69,44 @@ class Style(Route):
@url(r'/theme/font/([^/]+)')
class Font(Route):
class ThemeFont(Route):
def get(self, name):
if not tornado.options.options.theme or not name:
raise tornado.web.HTTPError(404)
font = 'themes/%s/font/%s' % (
tornado.options.options.theme,
name)
for fn in [
'/etc/butterfly/%s' % font,
os.path.expanduser('~/.butterfly/%s' % font)]:
if os.path.exists(fn):
ext = fn.split('.')[-1]
self.set_header("Content-Type", "application/x-font-%s" % ext)
with open(fn, 'rb') as s:
while True:
data = s.read(16384)
if data:
self.write(data)
else:
break
self.finish()
fn = os.path.join(
os.path.dirname(utils.get_style_path()), 'font', name)
if os.path.exists(fn):
self.set_header("Content-Type", guess_type(fn)[0])
with open(fn, 'rb') as s:
while True:
data = s.read(16384)
if data:
self.write(data)
else:
break
self.finish()
raise tornado.web.HTTPError(404)
@url(r'/theme/image/([^/]+)')
class ThemeImage(Route):
def get(self, name):
if not tornado.options.options.theme or not name:
raise tornado.web.HTTPError(404)
fn = os.path.join(
os.path.dirname(utils.get_style_path()), 'image', name)
if os.path.exists(fn):
self.set_header("Content-Type", guess_type(fn)[0])
with open(fn, 'rb') as s:
while True:
data = s.read(16384)
if data:
self.write(data)
else:
break
self.finish()
raise tornado.web.HTTPError(404)

View File

@@ -18,22 +18,35 @@
/* Here are the 16 "normal" colors for theming */
+termcolor(0, #2e3436) /* Black */
+termcolor(1, #cc0000) /* Red */
+termcolor(2, #4e9a06) /* Green */
+termcolor(3, #c4a000) /* Yellow */
+termcolor(4, #3465a4) /* Blue */
+termcolor(5, #75507b) /* Magenta */
+termcolor(6, #06989a) /* Cyan */
+termcolor(7, #d3d7cf) /* White */
+termcolor(8, #555753) /* Bright Black */
+termcolor(9, #ef2929) /* Bright Red */
+termcolor(10, #8ae234) /* Bright Green */
+termcolor(11, #fce94f) /* Bright Yellow */
+termcolor(12, #729fcf) /* Bright Blue */
+termcolor(13, #ad7fa8) /* Bright Magenta */
+termcolor(14, #34e2e2) /* Bright Cyan */
+termcolor(15, #eeeeec) /* Bright White */
$bg: #110f13
$fg: #f4ead5
/* Black */
+termcolor(0, #2e3436)
/* Red */
+termcolor(1, #cc0000)
/* Green */
+termcolor(2, #4e9a06)
/* Yellow */
+termcolor(3, #c4a000)
/* Blue */
+termcolor(4, #3465a4)
/* Magenta */
+termcolor(5, #75507b)
/* Cyan */
+termcolor(6, #06989a)
/* White */
+termcolor(7, #d3d7cf)
/* Bright Black */
+termcolor(8, #555753)
/* Bright Red */
+termcolor(9, #ef2929)
/* Bright Green */
+termcolor(10, #8ae234)
/* Bright Yellow */
+termcolor(11, #fce94f)
/* Bright Blue */
+termcolor(12, #729fcf)
/* Bright Magenta */
+termcolor(13, #ad7fa8)
/* Bright Cyan */
+termcolor(14, #34e2e2)
/* Bright White */
+termcolor(15, #eeeeec)

View File

@@ -15,8 +15,8 @@
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
$fg: #fff !default
$bg: #000 !default
$fg: #f4ead5 !default
$bg: #110f13 !default
/* Here are the 240 xterm colors */
/* See http://upload.wikimedia.org/wikipedia/en/1/15/Xterm_256color_chart.svg */

View File

@@ -15,10 +15,6 @@
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
$fg: #fff !default
$bg: #000 !default
.bold
font-weight: bold

View File

@@ -263,7 +263,7 @@
} else {
node = needle.node;
}
text = node.textContent;
text = node != null ? node.textContent : void 0;
i = needle.offset;
if (backward) {
while (node) {
@@ -276,7 +276,7 @@
}
}
node = previousLeaf(node);
text = node.textContent;
text = node != null ? node.textContent : void 0;
i = text.length;
}
} else {
@@ -290,7 +290,7 @@
}
}
node = nextLeaf(node);
text = node.textContent;
text = node != null ? node.textContent : void 0;
i = 0;
}
}

File diff suppressed because one or more lines are too long

View File

@@ -156,6 +156,7 @@ body {
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* Here are the 16 "normal" colors for theming */
/* Black */
.bg-color-0 {
background-color: #2e3436; }
.bg-color-0.reverse-video {
@@ -167,7 +168,7 @@ body {
.fg-color-0.reverse-video {
background-color: #2e3436 !important; }
/* Black */
/* Red */
.bg-color-1 {
background-color: #cc0000; }
.bg-color-1.reverse-video {
@@ -179,7 +180,7 @@ body {
.fg-color-1.reverse-video, .reverse-video.nbsp {
background-color: #cc0000 !important; }
/* Red */
/* Green */
.bg-color-2 {
background-color: #4e9a06; }
.bg-color-2.reverse-video {
@@ -191,7 +192,7 @@ body {
.fg-color-2.reverse-video {
background-color: #4e9a06 !important; }
/* Green */
/* Yellow */
.bg-color-3 {
background-color: #c4a000; }
.bg-color-3.reverse-video {
@@ -203,7 +204,7 @@ body {
.fg-color-3.reverse-video {
background-color: #c4a000 !important; }
/* Yellow */
/* Blue */
.bg-color-4 {
background-color: #3465a4; }
.bg-color-4.reverse-video {
@@ -215,7 +216,7 @@ body {
.fg-color-4.reverse-video {
background-color: #3465a4 !important; }
/* Blue */
/* Magenta */
.bg-color-5 {
background-color: #75507b; }
.bg-color-5.reverse-video {
@@ -227,7 +228,7 @@ body {
.fg-color-5.reverse-video {
background-color: #75507b !important; }
/* Magenta */
/* Cyan */
.bg-color-6 {
background-color: #06989a; }
.bg-color-6.reverse-video {
@@ -239,7 +240,7 @@ body {
.fg-color-6.reverse-video {
background-color: #06989a !important; }
/* Cyan */
/* White */
.bg-color-7 {
background-color: #d3d7cf; }
.bg-color-7.reverse-video {
@@ -251,7 +252,7 @@ body {
.fg-color-7.reverse-video {
background-color: #d3d7cf !important; }
/* White */
/* Bright Black */
.bg-color-8 {
background-color: #555753; }
.bg-color-8.reverse-video {
@@ -263,7 +264,7 @@ body {
.fg-color-8.reverse-video {
background-color: #555753 !important; }
/* Bright Black */
/* Bright Red */
.bg-color-9 {
background-color: #ef2929; }
.bg-color-9.reverse-video {
@@ -275,7 +276,7 @@ body {
.fg-color-9.reverse-video {
background-color: #ef2929 !important; }
/* Bright Red */
/* Bright Green */
.bg-color-10 {
background-color: #8ae234; }
.bg-color-10.reverse-video {
@@ -287,7 +288,7 @@ body {
.fg-color-10.reverse-video {
background-color: #8ae234 !important; }
/* Bright Green */
/* Bright Yellow */
.bg-color-11 {
background-color: #fce94f; }
.bg-color-11.reverse-video {
@@ -299,7 +300,7 @@ body {
.fg-color-11.reverse-video {
background-color: #fce94f !important; }
/* Bright Yellow */
/* Bright Blue */
.bg-color-12 {
background-color: #729fcf; }
.bg-color-12.reverse-video {
@@ -311,7 +312,7 @@ body {
.fg-color-12.reverse-video {
background-color: #729fcf !important; }
/* Bright Blue */
/* Bright Magenta */
.bg-color-13 {
background-color: #ad7fa8; }
.bg-color-13.reverse-video {
@@ -323,7 +324,7 @@ body {
.fg-color-13.reverse-video {
background-color: #ad7fa8 !important; }
/* Bright Magenta */
/* Bright Cyan */
.bg-color-14 {
background-color: #34e2e2; }
.bg-color-14.reverse-video {
@@ -335,7 +336,7 @@ body {
.fg-color-14.reverse-video {
background-color: #34e2e2 !important; }
/* Bright Cyan */
/* Bright White */
.bg-color-15 {
background-color: #eeeeec; }
.bg-color-15.reverse-video {
@@ -347,7 +348,6 @@ body {
.fg-color-15.reverse-video {
background-color: #eeeeec !important; }
/* Bright White */
/* *-* coding: utf-8 *-* */
/* This file is part of butterfly */
/* butterfly Copyright (C) 2014 Florian Mounier */
@@ -3015,15 +3015,15 @@ body {
background-color: #110f13 !important; }
.bg-color-257 {
background-color: #f4ead5; }
background-color: #fff; }
.bg-color-257.reverse-video {
color: #f4ead5 !important; }
color: #fff !important; }
.fg-color-257 {
color: #f4ead5;
text-shadow: 0 0 6px rgba(244, 234, 213, 0.5); }
color: #fff;
text-shadow: 0 0 6px rgba(255, 255, 255, 0.5); }
.fg-color-257.reverse-video {
background-color: #f4ead5 !important; }
background-color: #fff !important; }
/* *-* coding: utf-8 *-* */
/* This file is part of butterfly */
@@ -3043,7 +3043,7 @@ html, body {
padding: 0;
line-height: 1.2;
background-color: #110f13;
color: #f4ead5; }
color: #fff; }
body {
white-space: nowrap;
@@ -3053,9 +3053,9 @@ body {
background: #110f13;
width: .75em; }
body::-webkit-scrollbar-thumb {
background: rgba(244, 234, 213, 0.1); }
background: rgba(255, 255, 255, 0.1); }
body::-webkit-scrollbar-thumb:hover {
background: rgba(244, 234, 213, 0.15); }
background: rgba(255, 255, 255, 0.15); }
.terminal {
outline: none; }
@@ -3077,7 +3077,7 @@ body {
transition: 300ms; }
.cursor.reverse-video {
box-shadow: 0 0 0.5 #f4ead5; }
box-shadow: 0 0 0.5 #fff; }
/* *-* coding: utf-8 *-* */
/* This file is part of butterfly */
@@ -3117,7 +3117,7 @@ body {
.reverse-video {
color: #110f13;
background-color: #f4ead5; }
background-color: #fff; }
.blur .cursor.reverse-video {
background: none; }

View File

@@ -22,6 +22,8 @@
params = args.join(',');
if (type === 'Resize') {
return ws.send('R' + params);
} else if (type === 'Theme') {
return ws.send('T' + params);
}
};
if (location.protocol === 'https:') {
@@ -1224,7 +1226,7 @@
}
pt = pt.slice(1);
ref3 = pt.split('|', 2), type = ref3[0], content = ref3[1];
if (!content) {
if (!content && type !== 'SASS') {
console.error("No type for inline DECUDK: " + pt);
break;
}
@@ -1262,6 +1264,12 @@
l += content.length;
data = data.slice(0, i + 1) + content + data.slice(i + 1);
break;
case "SASS":
if (content.length) {
this.ctl('Theme', content);
}
setTimeout(this.refreshStyle.bind(this), 50);
break;
default:
console.error("Unknown type " + type + " for DECUDK");
}
@@ -1432,6 +1440,9 @@
break;
case 33:
if (ev.shiftKey) {
if (ev.ctrlKey) {
break;
}
this.scrollDisplay(-(this.rows - 1));
return cancel(ev);
} else {
@@ -1440,6 +1451,9 @@
break;
case 34:
if (ev.shiftKey) {
if (ev.ctrlKey) {
break;
}
this.scrollDisplay(this.rows - 1);
return cancel(ev);
} else {
@@ -2687,6 +2701,11 @@
return results;
};
Terminal.prototype.refreshStyle = function() {
document.getElementById('style').setAttribute('href', '/style.css?' + new Date().getTime());
return setTimeout(this.resize.bind(this), 300);
};
Terminal.prototype.charsets = {
SCLD: {
"`": "◆",

File diff suppressed because one or more lines are too long

View File

@@ -10,7 +10,7 @@
<link rel="shortcut icon" href="{{ static_url('images/favicon.png') }}">
<title>Butterfly</title>
<link href="/style.css" rel="stylesheet">
<link href="/style.css" rel="stylesheet" id="style">
</head>
<body spellcheck="false"

View File

@@ -278,6 +278,8 @@ class Terminal(object):
log.debug('WRIT<%r' % message)
self.writer.write(message[1:])
self.writer.flush()
elif message[0] == 'T':
tornado.options.options.theme = message[1:]
def shell_handler(self, fd, events):
if events & ioloop.READ:

View File

@@ -30,11 +30,19 @@ import re
log = getLogger('butterfly')
def get_style():
style = None
def get_style_path():
opts = tornado.options.options
if tornado.options.options.theme:
theme = 'themes/%s/' % tornado.options.options.theme
if opts.theme and os.path.exists(opts.theme):
if os.path.isdir(opts.theme):
theme = os.path.join(opts.theme, 'style.sass')
if os.path.exists(theme):
return theme
else:
return opts.theme
if opts.theme:
theme = 'themes/%s/' % opts.theme
else:
theme = '/'
@@ -43,8 +51,11 @@ def get_style():
'/etc/butterfly/%sstyle' % theme,
os.path.expanduser('~/.butterfly/%sstyle' % theme)]:
if os.path.exists('%s.%s' % (fn, ext)):
style = '%s.%s' % (fn, ext)
return '%s.%s' % (fn, ext)
def get_style():
style = get_style_path()
if style is None:
return
@@ -53,18 +64,19 @@ def get_style():
os.path.dirname(__file__), 'sass')
try:
import sass
sass.CompileError
except Exception:
log.error('You must install libsass to use sass '
'(pip install libsass)')
return
base = os.path.dirname(style)
try:
return sass.compile(filename=style, include_paths=[
theme, sass_path])
base, sass_path])
except sass.CompileError:
log.error(
'Unable to compile style.scss (filename: %s, paths: %r) ' % (
style, [theme, sass_path]), exc_info=True)
style, [base, sass_path]), exc_info=True)
return
with open(style) as s:

View File

@@ -148,7 +148,7 @@ class Selection
else
node = needle.node
text = node.textContent
text = node?.textContent
i = needle.offset
if backward
while node
@@ -156,7 +156,7 @@ class Selection
if text[--i].match til
return node: node, offset: i + 1
node = previousLeaf node
text = node.textContent
text = node?.textContent
i = text.length
else
while node
@@ -164,7 +164,7 @@ class Selection
if text[i++].match til
return node: node, offset: i - 1
node = nextLeaf node
text = node.textContent
text = node?.textContent
i = 0
return needle

View File

@@ -30,6 +30,8 @@ document.addEventListener 'DOMContentLoaded', ->
params = args.join(',')
if type == 'Resize'
ws.send 'R' + params
else if type == 'Theme'
ws.send 'T' + params
if location.protocol == 'https:'
wsUrl = 'wss://'

View File

@@ -1115,7 +1115,7 @@ class Terminal
[type, content] = pt.split('|', 2)
unless content
if not content and type isnt 'SASS'
console.error "No type for inline DECUDK: #{pt}"
break
@@ -1154,6 +1154,13 @@ class Terminal
when "TEXT"
l += content.length
data = data.slice(0, i + 1) + content + data.slice(i + 1)
when "SASS"
# sass file
if content.length
@ctl 'Theme', content
setTimeout @refreshStyle.bind(@), 50
else
console.error "Unknown type #{type} for DECUDK"
@@ -1333,6 +1340,7 @@ class Terminal
# page up
when 33
if ev.shiftKey
break if ev.ctrlKey
@scrollDisplay -(@rows - 1)
return cancel(ev)
else
@@ -1341,6 +1349,7 @@ class Terminal
# page down
when 34
if ev.shiftKey
break if ev.ctrlKey
@scrollDisplay @rows - 1
return cancel(ev)
else
@@ -3045,6 +3054,12 @@ class Terminal
@screen[i].wrap = false
i++
refreshStyle: ->
document.getElementById('style').setAttribute(
'href', '/style.css?' + new Date().getTime())
setTimeout @resize.bind(@), 300
# DEC Special Character and Line Drawing Set.
# http://vt100.net/docs/vt102-ug/table5-13.html
# A lot of curses apps use this if they see TERM=xterm.