Skip to content
Browse files

HUGE PERFORMANCE IMPROVEMENT IN THIS COMMIT! See the notes regarding …

…terminal.py...

terminal.py:  Implemented a two-pronged line cache (of sorts) that gets used by _spanify_screen() to save a significant amount of CPU whenever the function is called.  This involves two new variables:  Terminal.prev_dump and Terminal.html_cache which store copies of the raw screen and the HTML output, respectively, from the last time _spanify_screen() was called.  So if nothing changed on a particular line between the last call and the current one it will use the cached/prerendered HTML line instead of spending cycles converting the screen.
terminal.py:  Fixed an issue where if you had an image being displayed and you executed a program that uses the alternate screen buffer this could cause the terminal to wind up in a "sort of" hung state until you executed something like ctrl-l.
terminal.py:  Fixed an issue where renditions could be corrupted when returning from an alternate screen buffer (similar to above).
Updated the Licensing link in the README.rst.
gateone.py:  Added a new function to TerminalWebSocket:  get_bell().  It opens the bell sound, converts it into a data::URI and sends it to the client via the 'load_bell' action (see below how this action is handled).
gateone.py:  The logic that adds the bell sound to the index.html template has been removed from the MainHandler.
gateone.js:  Added an X close icon to the "Info and Tools" panel.
gateone.js:  Added an X close icon to the Preferences panel and moved the username/sign out bit below the header (to make room for the X).
gateone.js:  GateOne.prefs.bellSound has been renamed to GateOne.prefs.audibleBell to better reflect what the variable controls.
gateone.js:  Added back GateOne.prefs.bellSound as a place to cache the bell sound so we don't have to download it every time Gate One loads.
gateone.js:  Added a new function to GateOne.User:  loadBell().  It gets called when gateone.py sends the 'load_bell' action to the client (with the bell sound as a data::URI as the payload).  Essentially it just adds an <audio> element to the page containing the bell sound.  The idea is to make the bell work when embedding Gate One into another app without having to have said app include a separate <audio> tag (one less thing to worry about).
gateone.js:  Added some logic to GateOne.Net.onOpen() that checks to see if the bell sound is cached in GateOne.prefs.bellSound and sends a 'load_bell' action to the server if not.
gateone.js:  GateOne.Utils.savePrefs() now takes an argument:  skipNotification.  If true, displayMessage() won't be called to let the user know their prefs have been saved.  Added this since loadBell() calls savePrefs(true) after it sets GateOne.prefs.bellSound; there's really no reason to notify the user of this.
gateone.js:  Added a new function to GateOne.User:  uploadBellDialog().  It opens a dialog where users can customize the bell sound.  The bell will get saved locally when the form is submitted and never gets sent to the Gate One server (which is cool, client-side File API stuff!).  A button has been added to the Preferences panel to call this function.
gateone.js:  Fixed the bug where if you had more than ~40 terminals open they would start to get cut off at the top.  This would get worse the more terminals you had open (it would get real bad around terminal 150!).  Everything should line up perfectly now no matter how many terminals you have open.
index.html:  The <audio> element that contains the bell sound has been removed.
Themes:  Updated to place the username/sign out bits in the new location.
Bookmarks Plugin:  Modified bookmarks.css to make room for the X (close panel).  Essentially I just moved the search form 1.5em to the left of where it was.
  • Loading branch information...
1 parent c0cfc3a commit 67c05c6e332ce606ebd2c1df99b7ca877f842248 @liftoff committed Apr 18, 2012
View
2 README.rst
@@ -21,7 +21,7 @@ About Gate One
License
-------
-Gate One is dual licensed: `AGPLv3 <http://www.gnu.org/licenses/agpl.html>`_ or `Commercial Licensing <http://liftoffsoftware.com/Licensing>`_. More information can be found at http://liftoffsoftware.com/
+Gate One is dual licensed: `AGPLv3 <http://www.gnu.org/licenses/agpl.html>`_ or `Commercial Licensing <http://liftoffsoftware.com/Pricing>`_. More information can be found at http://liftoffsoftware.com/
Screenshots
-----------
View
BIN gateone/docs/source/.index.rst.kate-swp
Binary file not shown.
View
66 gateone/gateone.py
@@ -658,15 +658,6 @@ def get(self):
minified_js_abspath = os.path.join(GATEONE_DIR, 'static')
minified_js_abspath = os.path.join(
minified_js_abspath, 'gateone.min.js')
- bell_path = os.path.join(GATEONE_DIR, 'static')
- bell_path = os.path.join(bell_path, 'bell.ogg')
- if os.path.exists(bell_path):
- try:
- bell_data_uri = create_data_uri(bell_path)
- except MimeTypeFail:
- bell_data_uri = fallback_bell
- else: # There's always the fallback
- bell_data_uri = fallback_bell
js_init = self.settings['js_init']
# Use the minified version if it exists
if os.path.exists(minified_js_abspath):
@@ -692,7 +683,6 @@ def get(self):
jsplugins=PLUGINS['js'],
cssplugins=PLUGINS['css'],
js_init=js_init,
- bell_data_uri=bell_data_uri,
url_prefix=self.settings['url_prefix'],
head=head_html,
body=body_html
@@ -788,6 +778,7 @@ def __init__(self, application, request):
'refresh': self.refresh_screen,
'full_refresh': self.full_refresh,
'resize': self.resize,
+ 'get_bell': self.get_bell,
'get_webworker': self.get_webworker,
'get_style': self.get_style,
#'get_js': self.get_js,
@@ -1456,15 +1447,16 @@ def dsr(self, term, response):
def _send_refresh(self, term, full=False):
"""Sends a screen update to the client."""
- try:
- now = datetime.now()
- tidy_thread = SESSIONS[self.session]['tidy_thread']
- tidy_thread.keepalive(now)
- multiplex = SESSIONS[self.session][term]['multiplex']
- scrollback, screen = multiplex.dump_html(
- full=full, client_id=self.client_id)
- except KeyError as e: # Session died (i.e. command ended).
- scrollback, screen = [], []
+ #try:
+ now = datetime.now()
+ tidy_thread = SESSIONS[self.session]['tidy_thread']
+ tidy_thread.keepalive(now)
+ multiplex = SESSIONS[self.session][term]['multiplex']
+ scrollback, screen = multiplex.dump_html(
+ full=full, client_id=self.client_id)
+ #except KeyError as e: # Session died (i.e. command ended).
+ #print("KeyError in _send_refresh()?")
+ #scrollback, screen = [], []
if [a for a in screen if a]:
output_dict = {
'termupdate': {
@@ -1620,10 +1612,32 @@ def esc_opt_handler(self, chars):
"sequence handler..."))
logging.error(str(e))
+ def get_bell(self):
+ """
+ Sends the bell sound data to the client in in the form of a data::URI.
+ """
+ bell_path = os.path.join(GATEONE_DIR, 'static')
+ bell_path = os.path.join(bell_path, 'bell.ogg')
+ if os.path.exists(bell_path):
+ try:
+ bell_data_uri = create_data_uri(bell_path)
+ except MimeTypeFail:
+ bell_data_uri = fallback_bell
+ else: # There's always the fallback
+ bell_data_uri = fallback_bell
+ mimetype = bell_data_uri.split(';')[0].split(':')[1]
+ message = {
+ 'load_bell': {
+ 'data_uri': bell_data_uri, 'mimetype': mimetype
+ }
+ }
+ self.write_message(json_encode(message))
+
def get_webworker(self):
"""
- Returns the text of our go_process.js to get around the limitations of
- loading remote Web Worker URLs (for embedding Gate One into other apps).
+ Sends the text of our go_process.js to the client in order to get around
+ the limitations of loading remote Web Worker URLs (for embedding Gate
+ One into other apps).
"""
static_path = os.path.join(GATEONE_DIR, "static")
webworker_path = os.path.join(static_path, 'go_process.js')
@@ -1777,8 +1791,9 @@ def debug_terminal(self, term):
GateOne.ws.send(JSON.stringify({'debug_terminal': *term*}));
"""
- screen = SESSIONS[self.session][term]['multiplex'].term.screen
- renditions = SESSIONS[self.session][term]['multiplex'].term.renditions
+ termObj = SESSIONS[self.session][term]['multiplex'].term
+ screen = termObj.screen
+ renditions = termObj.renditions
for i, line in enumerate(screen):
# This gets rid of images:
line = [a for a in line if len(a) == 1]
@@ -1796,6 +1811,7 @@ def debug_terminal(self, term):
from pympler import asizeof
print("screen size: %s" % asizeof.asizeof(screen))
print("renditions size: %s" % asizeof.asizeof(renditions))
+ print("Total term object size: %s" % asizeof.asizeof(termObj))
# Thread classes
class TidyThread(threading.Thread):
@@ -2291,14 +2307,14 @@ def main():
)
define(
"uid",
- default=os.getuid(), # root
+ default=os.getuid(),
help=_(
"Drop privileges and run Gate One as this user/uid."),
type=str
)
define(
"gid",
- default=os.getgid(), # root
+ default=os.getgid(),
help=_(
"Drop privileges and run Gate One as this group/gid."),
type=str
View
13 gateone/plugins/bookmarks/static/bookmarks.js
@@ -1010,6 +1010,7 @@ GateOne.Base.update(GateOne.Bookmarks, {
delay = 1000, // Pretty much everything has the 'sectrans' class for 1-second transition effects
existingPanel = u.getNode('#'+prefix+'panel_bookmarks'),
bmPanel = u.createElement('div', {'id': 'panel_bookmarks', 'class': 'panel sectrans'}),
+ panelClose = u.createElement('div', {'id': 'icon_closepanel', 'class': 'panel_close_icon', 'title': "Close This Panel"}),
bmHeader = u.createElement('div', {'id': 'bm_header', 'class': 'sectrans'}),
bmContainer = u.createElement('div', {'id': 'bm_container', 'class': 'sectrans'}),
bmPagination = u.createElement('div', {'id': 'bm_pagination', 'class': 'sectrans'}),
@@ -1025,16 +1026,13 @@ GateOne.Base.update(GateOne.Bookmarks, {
bmSync = u.createElement('a', {'id': 'bm_sync', 'title': 'Synchronize your bookmarks with the server.'}),
bmH2 = u.createElement('h2'),
bmHeaderImage = u.createElement('span', {'id': 'bm_header_star'}),
-// bmTagCloudUL = u.createElement('ul', {'id': 'bm_tagcloud_ul'}),
-// bmTagCloudTip = u.createElement('span', {'id': 'bm_tagcloud_tip', 'class': 'sectrans'}),
-// bmTagsHeader = u.createElement('h3', {'class': 'sectrans'}),
-// pipeSeparator = u.createElement('span'),
-// bmTagsHeaderTagsLink = u.createElement('a'),
-// bmTagsHeaderAutotagsLink = u.createElement('a', {'class': 'inactive'}),
bmSearch = u.createElement('input', {'id': 'bm_search', 'name': prefix+'search', 'type': 'search', 'tabindex': 1, 'placeholder': 'Search Bookmarks'}),
-// allTags = b.getTags(b.bookmarks),
toggleSort = u.partial(b.toggleSortOrder, b.bookmarks);
bmH2.innerHTML = 'Bookmarks';
+ panelClose.innerHTML = go.Icons['panelclose'];
+ panelClose.onclick = function(e) {
+ go.Visual.togglePanel('#'+prefix+'panel_bookmarks'); // Scale away, scale away, scale away.
+ }
if (!embedded) {
bmH2.appendChild(bmSearch);
bmSearch.onchange = function(e) {
@@ -1049,6 +1047,7 @@ GateOne.Base.update(GateOne.Bookmarks, {
}
}
bmHeader.appendChild(bmH2);
+ bmHeader.appendChild(panelClose);
bmTags.innerHTML = '<span id="'+prefix+'bm_taglist_label">Tag Filter:</span> <ul id="'+prefix+'bm_taglist"></ul> ';
bmSync.innerHTML = 'Sync Bookmarks | ';
bmImport.innerHTML = 'Import | ';
View
4 gateone/plugins/bookmarks/templates/bookmarks.css
@@ -499,8 +499,8 @@
}
#{{prefix}}bm_search {
position: absolute;
- top: 0.1em;
- right: 0;
+ top: 0;
+ right: 1.5em;
font-size: 0.7em;
margin-right: 0;
}
View
4 gateone/plugins/mobile/static/mobile.js
@@ -126,8 +126,8 @@ GateOne.Base.update(GateOne.Mobile, {
dimensions = u.getRowsAndColumns(go.prefs.goDiv),
prefs = {
'term': term,
- 'rows': Math.ceil(dimensions.rows - 2),
- 'cols': Math.ceil(dimensions.cols - 7), // -6 for the sidebar + scrollbar and -1 because we're using Math.ceil
+ 'rows': Math.ceil(dimensions.rows - 1),
+ 'cols': Math.ceil(dimensions.cols - 6), // -5 for the sidebar + scrollbar and -1 because we're using Math.ceil
'em_dimensions': emDimensions
}
if (!go.prefs.showToolbar && !go.prefs.showTitle) {
View
1 gateone/plugins/ssh/static/ssh.js
@@ -802,7 +802,6 @@ GateOne.Base.update(GateOne.SSH, {
// *identity* should be the name of the identity associated with this certificate
var go = GateOne,
u = go.Utils,
- ssh = go.SSH,
prefix = go.prefs.prefix,
goDiv = u.getNode(go.prefs.goDiv),
sshIDPanel = u.getNode('#'+prefix+'panel_ssh_ids'),
View
151 gateone/static/gateone.js
@@ -142,9 +142,11 @@ GateOne.prefs = { // Tunable prefs (things users can change)
embedded: false, // Equivalent to {showTitle: false, showToolbar: false} and certain keyboard shortcuts won't be registered
disableTermTransitions: false, // Disabled the sliding animation on terminals to make switching faster
auth: null, // If using API authentication, this value will hold the user's auth object (see docs for the format).
- showTitle: true, // If false, the terminal title will not be shown in the sidebar
- showToolbar: true, // If false, the toolbar will now be shown in the sidebar
- bellSound: true // If false, the bell sound will not be played (visual notification will still occur)
+ showTitle: true, // If false, the terminal title will not be shown in the sidebar.
+ showToolbar: true, // If false, the toolbar will now be shown in the sidebar.
+ audibleBell: true, // If false, the bell sound will not be played (visual notification will still occur),
+ bellSound: '', // Stores the bell sound data::URI (cached).
+ bellSoundType: '' // Stores the mimetype of the bell sound.
};
// Properties in this object will get ignored when GateOne.prefs is saved to localStorage
GateOne.noSavePrefs = {
@@ -244,6 +246,7 @@ GateOne.Base.update(GateOne, {
u = go.Utils,
prefix = go.prefs.prefix,
goDiv = u.getNode(go.prefs.goDiv),
+ panelClose = u.createElement('div', {'id': 'icon_closepanel', 'class': 'panel_close_icon', 'title': "Close This Panel"}),
prefsPanel = u.createElement('div', {'id': 'panel_prefs', 'class':'panel'}),
prefsPanelH2 = u.createElement('h2'),
prefsPanelForm = u.createElement('form', {'id': 'prefs_form', 'name': prefix+'prefs_form'}),
@@ -252,6 +255,7 @@ GateOne.Base.update(GateOne, {
prefsPanelStyleRow3 = u.createElement('div', {'class':'paneltablerow'}),
prefsPanelStyleRow4 = u.createElement('div', {'class':'paneltablerow'}),
prefsPanelStyleRow5 = u.createElement('div', {'class':'paneltablerow'}),
+ prefsPanelStyleRow6 = u.createElement('div', {'class':'paneltablerow'}),
prefsPanelRow1 = u.createElement('div', {'class':'paneltablerow'}),
prefsPanelRow2 = u.createElement('div', {'class':'paneltablerow'}),
prefsPanelRow4 = u.createElement('div', {'class':'paneltablerow'}),
@@ -268,6 +272,8 @@ GateOne.Base.update(GateOne, {
prefsPanelDisableTermTransitions = u.createElement('input', {'id': 'prefs_disabletermtrans', 'name': prefix+'prefs_disabletermtrans', 'value': 'disabletermtrans', 'type': 'checkbox', 'style': {'display': 'table-cell', 'text-align': 'right', 'float': 'right'}}),
prefsPanelDisableAudibleBellLabel = u.createElement('span', {'id': 'prefs_disableaudiblebell_label', 'class':'paneltablelabel'}),
prefsPanelDisableAudibleBell = u.createElement('input', {'id': 'prefs_disableaudiblebell', 'name': prefix+'prefs_disableaudiblebell', 'value': 'disableaudiblebell', 'type': 'checkbox', 'style': {'display': 'table-cell', 'text-align': 'right', 'float': 'right'}}),
+ prefsPanelBellLabel = u.createElement('span', {'id': 'prefs_bell_label', 'class':'paneltablelabel'}),
+ prefsPanelBell = u.createElement('button', {'id': 'prefs_bell', 'value': 'bell', 'class': 'button black', 'style': {'display': 'table-cell', 'float': 'right'}}),
prefsPanelScrollbackLabel = u.createElement('span', {'id': 'prefs_scrollback_label', 'class':'paneltablelabel'}),
prefsPanelScrollback = u.createElement('input', {'id': 'prefs_scrollback', 'name': prefix+'prefs_scrollback', 'size': 5, 'style': {'display': 'table-cell', 'text-align': 'right', 'float': 'right'}}),
prefsPanelRowsLabel = u.createElement('span', {'id': 'prefs_rows_label', 'class':'paneltablelabel'}),
@@ -289,12 +295,23 @@ GateOne.Base.update(GateOne, {
go.Visual.applyTransform(prefsPanel, 'scale(0)');
toolbarIconPrefs.innerHTML = go.Icons.prefs;
prefsPanelH2.innerHTML = "Preferences";
+ panelClose.innerHTML = go.Icons['panelclose'];
+ panelClose.onclick = function(e) {
+ go.Visual.togglePanel('#'+prefix+'panel_prefs'); // Scale away, scale away, scale away.
+ }
+ prefsPanelBell.onclick = function(e) {
+ e.preventDefault(); // Just in case
+ go.User.uploadBellDialog();
+ }
prefsPanel.appendChild(prefsPanelH2);
+ prefsPanel.appendChild(panelClose);
prefsPanelThemeLabel.innerHTML = "<b>Theme:</b> ";
prefsPanelColorsLabel.innerHTML = "<b>Color Scheme:</b> ";
prefsPanelFontSizeLabel.innerHTML = "<b>Font Size:</b> ";
prefsPanelDisableTermTransitionsLabel.innerHTML = "<b>Disable Terminal Slide Effect:</b> ";
prefsPanelDisableAudibleBellLabel.innerHTML = "<b>Disable Bell Sound:</b> ";
+ prefsPanelBell.innerHTML = "Configure";
+ prefsPanelBellLabel.innerHTML = "<b>Bell Sound:</b> ";
prefsPanelFontSize.value = go.prefs.fontSize;
prefsPanelStyleRow1.appendChild(prefsPanelThemeLabel);
prefsPanelStyleRow1.appendChild(prefsPanelTheme);
@@ -306,11 +323,14 @@ GateOne.Base.update(GateOne, {
prefsPanelStyleRow4.appendChild(prefsPanelDisableTermTransitions);
prefsPanelStyleRow5.appendChild(prefsPanelDisableAudibleBellLabel);
prefsPanelStyleRow5.appendChild(prefsPanelDisableAudibleBell);
+ prefsPanelStyleRow6.appendChild(prefsPanelBellLabel);
+ prefsPanelStyleRow6.appendChild(prefsPanelBell);
tableDiv.appendChild(prefsPanelStyleRow1);
tableDiv.appendChild(prefsPanelStyleRow2);
tableDiv.appendChild(prefsPanelStyleRow3);
tableDiv.appendChild(prefsPanelStyleRow4);
tableDiv.appendChild(prefsPanelStyleRow5);
+ tableDiv.appendChild(prefsPanelStyleRow6);
prefsPanelScrollbackLabel.innerHTML = "<b>Scrollback Buffer Lines:</b> ";
prefsPanelScrollback.value = go.prefs.scrollback;
prefsPanelRowsLabel.innerHTML = "<b>Terminal Rows:</b> ";
@@ -381,9 +401,9 @@ GateOne.Base.update(GateOne, {
}
}
if (disableAudibleBell) {
- go.prefs.bellSound = false;
+ go.prefs.audibleBell = false;
} else {
- go.prefs.bellSound = true;
+ go.prefs.audibleBell = true;
}
if (go.savePrefsCallbacks.length) {
// Call any registered prefs callbacks
@@ -778,10 +798,10 @@ GateOne.Base.update(GateOne.Utils, {
// sizingDiv.innerHTML = "\u2588"; // Fill it with a single character (this is a unicode "full block": █). Using the \u syntax because minifiers don't seem to like unicode characters to be in the source as-is.
// We need two lines so we can factor in the line height and character spacing (if it has been messed with).
sizingDiv.className = "terminal";
- for (var i=0; i <= 15; i++) {
+ for (var i=0; i <= 63; i++) {
fillerX += "\u2588";
}
- for (var i=0; i <= 15; i++) {
+ for (var i=0; i <= 63; i++) {
fillerY.push(fillerX);
}
sizingPre.innerHTML = fillerY.join('\n');
@@ -793,8 +813,8 @@ GateOne.Base.update(GateOne.Utils, {
node.appendChild(sizingDiv);
var nodeHeight = sizingPre.getClientRects()[0].height,
nodeWidth = sizingPre.getClientRects()[0].width;
- nodeHeight = parseInt(nodeHeight)/16;
- nodeWidth = parseInt(nodeWidth)/16;
+ nodeHeight = parseInt(nodeHeight)/64;
+ nodeWidth = parseInt(nodeWidth)/64;
node.removeChild(sizingDiv);
return {'w': nodeWidth, 'h': nodeHeight};
},
@@ -1019,8 +1039,9 @@ GateOne.Base.update(GateOne.Utils, {
}
}
},
- savePrefs: function() {
+ savePrefs: function(skipNotification) {
// Saves all user-specific settings in GateOne.prefs.* to localStorage[prefix+'prefs']
+ // if *skipNotification* is True, no message will be displayed to the user.
var prefs = GateOne.prefs,
userPrefs = {};
for (var pref in prefs) {
@@ -1031,7 +1052,9 @@ GateOne.Base.update(GateOne.Utils, {
}
}
localStorage[prefs.prefix+'prefs'] = JSON.stringify(userPrefs);
- GateOne.Visual.displayMessage("Preferences have been saved.");
+ if (!skipNotification) {
+ GateOne.Visual.displayMessage("Preferences have been saved.");
+ }
},
loadPrefs: function() {
// Populates GateOne.prefs.* with values from localStorage['prefs']
@@ -1294,6 +1317,13 @@ GateOne.Base.update(GateOne.Net, {
// Load the Web Worker
logDebug("Attempting to download our WebWorker...");
go.ws.send(JSON.stringify({'get_webworker': null}));
+ // Load the bell sound
+ if (go.prefs.bellSound.length) {
+ go.User.loadBell({'mimetype': go.prefs.bellSoundType, 'data_uri': go.prefs.bellSound});
+ } else {
+ logDebug("Attempting to download our bell sound...");
+ go.ws.send(JSON.stringify({'get_bell': null}));
+ }
// Check if there are any existing terminals for the current session ID
go.ws.send(JSON.stringify({'authenticate': settings}));
// Autoconnect if autoConnectURL is specified
@@ -2187,8 +2217,8 @@ GateOne.Base.update(GateOne.Visual, {
terms.forEach(function(termObj) {
termObj.style.height = go.Visual.goDimensions.h + 'px';
termObj.style.width = go.Visual.goDimensions.w + 'px';
- termObj.style['margin-right'] = style['padding-right'];
- termObj.style['margin-bottom'] = style['padding-bottom'];
+// termObj.style['margin-right'] = style['padding-right'];
+// termObj.style['margin-bottom'] = style['padding-bottom'];
});
}
},
@@ -2411,9 +2441,9 @@ GateOne.Base.update(GateOne.Visual, {
},
playBell: function() {
// Plays the bell sound without any visual notification.
- var snd = GateOne.Utils.getNode('#bell');
+ var snd = GateOne.Utils.getNode('#'+GateOne.prefs.prefix+'bell');
if (snd) {
- if (GateOne.prefs.bellSound) {
+ if (GateOne.prefs.audibleBell) {
snd.play();
}
}
@@ -2541,10 +2571,10 @@ GateOne.Base.update(GateOne.Visual, {
} else {
return; // This can happen if the terminal closed before a timeout completed. Not a big deal, ignore
}
- if (style['padding-right']) {
+ if (style['padding-right'] != "0px") {
rightAdjust = parseInt(style['padding-right'].split('px')[0]);
}
- if (style['padding-bottom']) {
+ if (style['padding-bottom'] != "0px") {
bottomAdjust = parseInt(style['padding-bottom'].split('px')[0]);
}
if (changeSelected) {
@@ -2559,10 +2589,10 @@ GateOne.Base.update(GateOne.Visual, {
if (termObj.id == go.prefs.prefix+'term' + term) {
if (u.isEven(count)) {
wPX = ((v.goDimensions.w+rightAdjust) * 2) - (v.goDimensions.w+rightAdjust);
- hPX = (((v.goDimensions.h+bottomAdjust) * count)/2) - (v.goDimensions.h+bottomAdjust);
+ hPX = (((v.goDimensions.h+bottomAdjust) * count)/2) - (v.goDimensions.h+(bottomAdjust*Math.floor(count/2)));
} else {
wPX = 0;
- hPX = (((v.goDimensions.h+bottomAdjust) * (count+1))/2) - (v.goDimensions.h+bottomAdjust);
+ hPX = (((v.goDimensions.h+bottomAdjust) * (count+1))/2) - (v.goDimensions.h+(bottomAdjust*Math.floor(count/2)));
}
}
});
@@ -3210,6 +3240,7 @@ GateOne.Base.update(GateOne.Terminal, {
toolbarNewTerm = u.createElement('div', {'id': 'icon_newterm', 'class': 'toolbar', 'title': "New Terminal"}),
toolbarInfo = u.createElement('div', {'id': 'icon_info', 'class': 'toolbar', 'title': "Info and Tools"}),
infoPanel = u.createElement('div', {'id': 'panel_info', 'class': 'panel'}),
+ panelClose = u.createElement('div', {'id': 'icon_closepanel', 'class': 'panel_close_icon', 'title': "Close This Panel"}),
infoPanelRow1 = u.createElement('div', {'class': 'paneltablerow', 'id': 'panel_inforow1'}),
infoPanelRow2 = u.createElement('div', {'class': 'paneltablerow', 'id': 'panel_inforow2'}),
infoPanelRow3 = u.createElement('div', {'class': 'paneltablerow', 'id': 'panel_inforow3'}),
@@ -3248,12 +3279,17 @@ GateOne.Base.update(GateOne.Terminal, {
go.Icons['newTerm'] = '<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="18" width="18" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/"><defs><linearGradient id="linearGradient12259" y2="234.18" gradientUnits="userSpaceOnUse" x2="561.42" y1="252.18" x1="561.42"><stop class="stop1" offset="0"/><stop class="stop2" offset="0.4944"/><stop class="stop3" offset="0.5"/><stop class="stop4" offset="1"/></linearGradient></defs><metadata><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/><dc:title/></cc:Work></rdf:RDF></metadata><g transform="translate(-261.95455,-486.69334)"><g transform="matrix(0.94996733,0,0,0.94996733,-256.96226,264.67838)"><rect height="3.867" width="7.54" y="241.25" x="557.66" fill="url(#linearGradient12259)"/><rect height="3.866" width="7.541" y="241.25" x="546.25" fill="url(#linearGradient12259)"/><rect height="7.541" width="3.867" y="245.12" x="553.79" fill="url(#linearGradient12259)"/><rect height="7.541" width="3.867" y="233.71" x="553.79" fill="url(#linearGradient12259)"/><rect height="3.867" width="3.867" y="241.25" x="553.79" fill="url(#linearGradient12259)"/><rect height="3.867" width="3.867" y="241.25" x="553.79" fill="url(#linearGradient12259)"/></g></g></svg>';
toolbarNewTerm.innerHTML = go.Icons['newTerm'];
infoPanelH2.innerHTML = "Gate One";
+ panelClose.innerHTML = go.Icons['panelclose'];
+ panelClose.onclick = function(e) {
+ go.Visual.togglePanel('#'+prefix+'panel_info'); // Scale away, scale away, scale away.
+ }
infoPanelTimeLabel.innerHTML = "<b>Connected Since:</b> ";
infoPanelRowsLabel.innerHTML = "<b>Rows:</b> ";
infoPanelRows.innerHTML = go.prefs.rows; // Will be replaced
infoPanelColsLabel.innerHTML = "<b>Columns:</b> ";
infoPanelCols.innerHTML = go.prefs.cols; // Will be replaced
infoPanel.appendChild(infoPanelH2);
+ infoPanel.appendChild(panelClose);
infoPanel.appendChild(p);
infoPanel.appendChild(tableDiv);
infoPanel.appendChild(tableDiv2);
@@ -3997,6 +4033,7 @@ GateOne.Base.update(GateOne.User, {
}
// Register our actions
go.Net.addAction('set_username', go.User.setUsername);
+ go.Net.addAction('load_bell', go.User.loadBell);
},
setUsername: function(username) {
// Sets GateOne.User.username using *username*. Also provides hooks that plugins can have called after a user has logged in successfully.
@@ -4038,7 +4075,81 @@ GateOne.Base.update(GateOne.User, {
window.location.href = url;
}, 2000);
});
- }
+ },
+ loadBell: function(message) {
+ // Loads the bell sound into the page as an <audio> element using the given *audioDataURI*.
+ var go = GateOne,
+ u = go.Utils,
+ goDiv = u.getNode(go.prefs.goDiv),
+ audioDataURI = message['data_uri'],
+ mimetype = message['mimetype'],
+ existing = u.getNode('#'+go.prefs.prefix+'bell'),
+ audioElem = u.createElement('audio', {'id': 'bell', 'preload': 'auto'}),
+ sourceElem = u.createElement('source', {'id': 'bell_source', 'type': mimetype});
+ if (existing) {
+ u.removeElement(existing);
+ }
+ sourceElem.src = audioDataURI;
+ audioElem.appendChild(sourceElem);
+ goDiv.appendChild(audioElem);
+ // Cache it so we don't have to re-download it every time.
+ go.prefs.bellSound = audioDataURI;
+ go.prefs.bellSoundType = mimetype;
+ u.savePrefs(true);
+ },
+ uploadBellDialog: function() {
+ // Displays a dialog/form where the user can upload a replacement bell sound or use the default
+ var go = GateOne,
+ u = go.Utils,
+ prefix = go.prefs.prefix,
+ goDiv = u.getNode(go.prefs.goDiv),
+ playBell = u.createElement('button', {'id': 'play_bell', 'value': 'play_bell', 'class': 'button black'}),
+ defaultBell = u.createElement('button', {'id': 'default_bell', 'value': 'default_bell', 'class': 'button black', 'style': {'float': 'right', 'margin-right': '1.5em'}}),
+ uploadBellForm = u.createElement('form', {'name': prefix+'upload_bell_form', 'style': {'width': '25em'}}),
+ bellFile = u.createElement('input', {'type': 'file', 'id': 'upload_bell', 'name': prefix+'upload_bell'}),
+ bellFileLabel = u.createElement('label'),
+ submit = u.createElement('button', {'id': 'submit', 'type': 'submit', 'value': 'Submit', 'class': 'button black', 'style': {'float': 'right', 'margin-right': '1.5em'}}),
+ cancel = u.createElement('button', {'id': 'cancel', 'type': 'reset', 'value': 'Cancel', 'class': 'button black', 'style': {'float': 'right'}});
+ submit.innerHTML = "Submit";
+ cancel.innerHTML = "Cancel";
+ defaultBell.innerHTML = "Reset Bell to Default";
+ playBell.innerHTML = "Play Current Bell";
+ playBell.onclick = function(e) {
+ e.preventDefault();
+ go.Visual.playBell();
+ }
+ bellFileLabel.innerHTML = "Select a Sound File";
+ bellFileLabel.htmlFor = prefix+'upload_bell';
+ uploadBellForm.appendChild(playBell);
+ uploadBellForm.appendChild(defaultBell);
+ uploadBellForm.appendChild(bellFileLabel);
+ uploadBellForm.appendChild(bellFile);
+ uploadBellForm.appendChild(submit);
+ uploadBellForm.appendChild(cancel);
+ var closeDialog = go.Visual.dialog('Upload Bell Sound', uploadBellForm);
+ cancel.onclick = closeDialog;
+ defaultBell.onclick = function(e) {
+ e.preventDefault();
+ go.ws.send(JSON.stringify({'get_bell': null}));
+ closeDialog();
+ }
+ uploadBellForm.onsubmit = function(e) {
+ // Don't actually submit it
+ e.preventDefault();
+ // Grab the form values
+ var bellFile = u.getNode('#'+prefix+'upload_bell').files[0],
+ bellReader = new FileReader(),
+ saveBell = function(evt) {
+ var dataURI = evt.target.result,
+ mimetype = bellFile.type;
+ go.User.loadBell({'mimetype': mimetype, 'data_uri': dataURI});
+ };
+ // Get the data out of the files
+ bellReader.onload = saveBell;
+ bellReader.readAsDataURL(bellFile);
+ closeDialog();
+ }
+ },
});
// Protocol actions
View
3 gateone/templates/index.html
@@ -29,9 +29,6 @@
// NOTE: Fonts *and* icons will be scaled according to the fontSize property.
}
</script>
-<audio id="bell" preload="auto"> <!-- This is used for the system bell -->
- <source src="{{bell_data_uri}}" type="audio/ogg" />
-</audio>
</body>
</html>
View
10 gateone/templates/themes/black.css
@@ -194,7 +194,6 @@ hr {
color: #eee;
position: static;
float: left;
- top: 0;
-webkit-transition: -webkit-transform 1s ease-in-out;
-moz-transition: -moz-transform 1s ease-in-out;
-ms-transition: -ms-transform 1s ease-in-out;
@@ -429,6 +428,8 @@ hr {
#{{container}} .paneltablelabel {
display: table-cell;
width: 75%;
+ vertical-align: middle;
+ padding-top: 0.2em;
}
#{{container}} .paneltablecell {
display: table-cell;
@@ -585,11 +586,8 @@ hr {
}
#{{prefix}}user_info {
font-size: 0.8em;
- position: absolute;
- top: 0;
- right: 0;
- padding-top: 0.5em;
- padding-right: 0.25em;
+ padding-bottom: 0.25em;
+ text-align: right;
}
#{{prefix}}user_info a:hover {
cursor: pointer;
View
10 gateone/templates/themes/dark-black.css
@@ -195,7 +195,6 @@ hr {
color: #fff;
position: static;
float: left;
- top: 0;
-webkit-transition: -webkit-transform 1s ease-in-out;
-moz-transition: -moz-transform 1s ease-in-out;
-ms-transition: -ms-transform 1s ease-in-out;
@@ -417,6 +416,8 @@ hr {
#{{container}} .paneltablelabel {
display: table-cell;
width: 75%;
+ vertical-align: middle;
+ padding-top: 0.2em;
}
#{{container}} .paneltablecell {
display: table-cell;
@@ -617,11 +618,8 @@ hr {
}
#{{prefix}}user_info {
font-size: 0.8em;
- position: absolute;
- top: 0;
- right: 0;
- padding-top: 0.5em;
- padding-right: 0.25em;
+ padding-bottom: 0.25em;
+ text-align: right;
}
#{{prefix}}user_info a:hover {
cursor: pointer;
View
10 gateone/templates/themes/white.css
@@ -194,7 +194,6 @@ hr {
color: #000;
position:static;
float: left;
- top: 0;
-webkit-transition: -webkit-transform 1s ease-in-out;
-moz-transition: -moz-transform 1s ease-in-out;
-ms-transition: -ms-transform 1s ease-in-out;
@@ -430,6 +429,8 @@ hr {
#{{container}} .paneltablelabel {
display: table-cell;
width: 75%;
+ vertical-align: middle;
+ padding-top: 0.2em;
}
#{{container}} .paneltablecell {
display: table-cell;
@@ -586,11 +587,8 @@ hr {
}
#{{prefix}}user_info {
font-size: 0.8em;
- position: absolute;
- top: 0;
- right: 0;
- padding-top: 0.5em;
- padding-right: 0.25em;
+ padding-bottom: 0.25em;
+ text-align: right;
}
#{{prefix}}user_info a:hover {
cursor: pointer;
View
195 gateone/terminal.py
@@ -145,7 +145,7 @@
"""
# Import stdlib stuff
-import os, re, logging, base64, copy, StringIO, codecs, unicodedata, tempfile
+import os, re, logging, base64, StringIO, codecs, unicodedata, tempfile
from array import array
from datetime import datetime, timedelta
from collections import defaultdict
@@ -808,6 +808,17 @@ def initialize(self, rows=24, cols=80, em_dimensions=None):
self.image = ""
self.images = {}
self.image_counter = pua_counter()
+ # This is for creating a new point of reference every time there's a new
+ # unique rendition at a given coordinate
+ self.rend_counter = pua_counter()
+ # Used for mapping unicode chars to acutal renditions (to save memory):
+ self.renditions_store = {
+ u' ': [0], # Nada, nothing, no rendition. Not the same as below
+ self.rend_counter.next(): [0] # Default is actually reset
+ }
+ self.prev_dump = [] # A cache to speed things up
+ self.prev_dump_rend = [] # Ditto
+ self.html_cache = [] # Ditto
def init_screen(self):
"""
@@ -817,9 +828,6 @@ def init_screen(self):
.. note:: Just because each line starts out with a uniform length does not mean it will stay that way. Processing of escape sequences is handled when an output function is called.
"""
logging.debug('init_screen()')
- #self.screen = [
- #[u' ' for a in xrange(self.cols)] for b in xrange(self.rows)
- #]
self.screen = [array('u', u' ' * self.cols) for a in xrange(self.rows)]
# Tabstops
tabs, remainder = divmod(self.cols, 8) # Default is every 8 chars
@@ -829,26 +837,18 @@ def init_screen(self):
self.cursorX = 0
self.cursorY = 0
self.rendition_set = False
+ self.prev_dump = [] # Force a full dump with an init
+ self.prev_dump_rend = []
+ self.html_cache = [] # Force this to be reset as well
- def init_renditions(self):
+ def init_renditions(self, rendition=u'\U00100000'):
"""
- Fills :attr:`self.renditions` with lists of [0] using :attr:`self.cols`
- and :attr:`self.rows` for the dimenions.
+ Replaces :attr:`self.renditions` with arrays of *rendition* (characters)
+ using :attr:`self.cols` and :attr:`self.rows` for the dimenions.
"""
- # This is for creating a new point of reference every time there's a new
- # unique rendition at a given coordinate
- self.rend_counter = pua_counter()
- # Used for mapping unicode chars to acutal renditions (to save memory):
- self.renditions_store = {
- u' ': [], # Nada, nothing, no rendition. Not the same as below
- self.rend_counter.next(): [0] # Default is actually reset
- }
# The actual renditions at various coordinates:
self.renditions = [
- array('u', u' ' * self.cols) for a in xrange(self.rows)]
- #self.renditions = [
- #[[0] for a in xrange(self.cols)] for b in xrange(self.rows)
- #]
+ array('u', rendition * self.cols) for a in xrange(self.rows)]
def init_scrollback(self):
"""
@@ -938,6 +938,9 @@ def reset(self, *args, **kwargs):
self.init_screen()
self.init_renditions()
self.init_scrollback()
+ self.prev_dump = []
+ self.prev_dump_rend = []
+ self.html_cache = []
try:
self.callbacks[CALLBACK_RESET]()
except TypeError:
@@ -967,10 +970,8 @@ def resize(self, rows, cols, em_dimensions=None):
self.renditions.pop(0)
elif rows > self.rows: # Add rows at the bottom
for i in xrange(rows - self.rows):
- #line = [u' ' for a in xrange(cols)]
line = array('u', u' ' * self.cols)
- renditions = array('u', u' ' * self.cols)
- #renditions = [[0] for a in xrange(self.cols)]
+ renditions = array('u', u'\U00100000' * self.cols)
self.screen.append(line)
self.renditions.append(renditions)
self.rows = rows
@@ -989,8 +990,7 @@ def resize(self, rows, cols, em_dimensions=None):
for i in xrange(self.rows):
for j in xrange(cols - self.cols):
self.screen[i].append(u' ')
- self.renditions[i].append(u' ')
- #self.renditions[i].append([0])
+ self.renditions[i].append(u'\U00100000')
self.cols = cols
# Fix the cursor location:
@@ -1012,7 +1012,7 @@ def _set_top_bottom(self, settings):
# This is a set/restore DEC PMV sequence
return # Ignore (until I figure out what this should do)
top, bottom = settings.split(';')
- self.top_margin = max(0, int(top) - 1) # These are 0-based like self.cursor[XY]
+ self.top_margin = max(0, int(top) - 1) # These are 0-based
if bottom:
self.bottom_margin = min(self.rows - 1, int(bottom) - 1)
else:
@@ -1119,9 +1119,6 @@ def _set_line_params(self, param):
if param == 8:
# Screen alignment test
self.init_renditions()
- #self.screen = [
- #[u'E' for a in xrange(self.cols)] for b in xrange(self.rows)
- #]
self.screen = [
array('u', u'E' * self.cols) for a in xrange(self.rows)]
# TODO: Get this handling double line height stuff... For kicks
@@ -1246,7 +1243,6 @@ def write(self, chars, special_checks=True):
self.matched_header].split(self.image)
# Eliminate anything before the match
self.image = match.group()
- #open('/tmp/good_image.png', 'w').write(self.image)
self._capture_image()
self.image = "" # Empty it now that is is captured
self.matched_header = None # Ditto
@@ -1258,7 +1254,6 @@ def write(self, chars, special_checks=True):
# went wrong. Discard what we've got and restart.
one_second = timedelta(seconds=1)
if datetime.now() - self.timeout_image > one_second:
- #open('/tmp/bad_image.png', 'w').write(self.image)
self.image = "" # Empty it
self.matched_header = None
chars = _("Failed to decode image. Buffer discarded.")
@@ -1307,7 +1302,7 @@ def write(self, chars, special_checks=True):
try:
csi_handlers[csi_type](csi_values)
except ValueError:
- # Commented this out because it can be super noisy
+ # Commented this out because it can be super noisy
#logging.error(_(
#"CSI Handler Error: Type: %s, Values: %s" %
#(csi_type, csi_values)
@@ -1405,18 +1400,15 @@ def scroll_up(self, n=1):
self.init_scrollback()
# NOTE: This would only be if 1000 lines piled up before the
# next dump_html() or dump().
- #empty_line = [u' ' for a in xrange(self.cols)] # Line full of spaces
empty_line = array('u', u' ' * self.cols) # Line full of spaces
# Add it to the bottom of the window:
self.screen.insert(self.bottom_margin, empty_line)
# Remove top line's style information
style = self.renditions.pop(self.top_margin)
self.scrollback_renditions.append(style)
# Insert a new empty rendition as well:
- empty_line = array('u', u' ' * self.cols) # Line full of spaces
+ empty_line = array('u', u'\U00100000' * self.cols)
self.renditions.insert(self.bottom_margin, empty_line)
- #self.renditions.insert(
- #self.bottom_margin, [[0] for a in xrange(self.cols)])
# Execute our callback indicating lines have been updated
try:
for callback in self.callbacks[CALLBACK_CHANGED].values():
@@ -1439,16 +1431,13 @@ def scroll_down(self, n=1):
#logging.debug("scroll_down(%s)" % n)
for x in xrange(int(n)):
self.screen.pop(self.bottom_margin) # Remove the bottom line
- #empty_line = [u' ' for a in xrange(self.cols)] # Line full of spaces
empty_line = array('u', u' ' * self.cols) # Line full of spaces
self.screen.insert(self.top_margin, empty_line) # Add it to the top
# Remove bottom line's style information:
self.renditions.pop(self.bottom_margin)
# Insert a new empty one:
- empty_line = array('u', u' ' * self.cols) # Line full of spaces
- self.renditions.insert(self.top_margin, empty_line) # Add it to the top
- #self.renditions.insert(
- #self.top_margin, [[0] for a in xrange(self.cols)])
+ empty_line = array('u', u'\U00100000' * self.cols)
+ self.renditions.insert(self.top_margin, empty_line)
# Execute our callback indicating lines have been updated
try:
for callback in self.callbacks[CALLBACK_CHANGED].values():
@@ -1475,13 +1464,11 @@ def insert_line(self, n=1):
self.screen.pop(self.bottom_margin) # Remove the bottom line
# Remove bottom line's style information as well:
self.renditions.pop(self.bottom_margin)
- #empty_line = [u' ' for a in xrange(self.cols)] # Line full of spaces
empty_line = array('u', u' ' * self.cols) # Line full of spaces
self.screen.insert(self.cursorY, empty_line) # Insert at cursor
# Insert a new empty rendition as well:
- empty_line = array('u', u' ' * self.cols) # Line full of spaces
+ empty_line = array('u', u'\U00100000' * self.cols)
self.renditions.insert(self.cursorY, empty_line) # Insert at cursor
- #self.renditions.insert(self.cursorY, [[0] for a in xrange(self.cols)])
def delete_line(self, n=1):
"""
@@ -1495,23 +1482,19 @@ def delete_line(self, n=1):
self.screen.pop(self.cursorY) # Remove the line at the cursor
# Remove the line's style information as well:
self.renditions.pop(self.cursorY)
- # Now add an empty line and empty set of renditions to the bottom of the
- # view
- #empty_line = [u' ' for a in xrange(self.cols)] # Line full of spaces
+ # Now add an empty line and empty set of renditions to the bottom of
+ # the view
empty_line = array('u', u' ' * self.cols) # Line full of spaces
# Add it to the bottom of the view:
self.screen.insert(self.bottom_margin, empty_line) # Insert at bottom
# Insert a new empty rendition as well:
- empty_line = array('u', u' ' * self.cols) # Line full of spaces
- self.renditions.insert(self.bottom_margin, empty_line) # Insert at bottom
- #self.renditions.insert(
- #self.bottom_margin, [[0] for a in xrange(self.cols)])
+ empty_line = array('u', u'\U00100000' * self.cols)
+ self.renditions.insert(self.bottom_margin, empty_line)
def backspace(self):
"""Execute a backspace (\\x08)"""
try:
self.renditions[self.cursorY][self.cursorX] = u' '
- #self.renditions[self.cursorY][self.cursorX] = []
except IndexError:
pass # At the edge, no biggie
self.cursor_left(1)
@@ -1734,18 +1717,22 @@ def close_image_fds(self):
Closes the file descriptors of any images that are no longer on the
screen.
"""
- logging.debug('close_image_fds()')
+ #logging.debug('close_image_fds()') # Commented because it's kinda noisy
if self.images:
for ref in self.images.keys():
found = False
for line in self.screen:
if ref in line:
found = True
break
+ if self.alt_screen:
+ for line in self.alt_screen:
+ if ref in line:
+ found = True
+ break
if not found:
self.images[ref].close()
del self.images[ref]
- #print('It took %0.2fms to close image FDs' % (elapsed*1000.0))
def _string_terminator(self):
"""
@@ -1916,20 +1903,23 @@ def toggle_alternate_screen_buffer(self, alt):
#logging.debug('toggle_alternate_screen_buffer(%s)' % alt)
if alt:
# Save the existing screen and renditions
- self.alt_screen = copy.copy(self.screen)
- self.alt_renditions = copy.copy(self.renditions)
+ self.alt_screen = self.screen[:]
+ self.alt_renditions = self.renditions[:]
# Make a fresh one
self.clear_screen()
else:
# Restore the screen
if self.alt_screen and self.alt_renditions:
- self.screen = self.alt_screen
- self.renditions = self.alt_renditions
+ self.screen = self.alt_screen[:]
+ self.renditions = self.alt_renditions[:]
# Empty out the alternate buffer (to save memory)
self.alt_screen = None
self.alt_renditions = None
- self.cur_rendition = u' '
- #self.cur_rendition = []
+ # These all need to be reset no matter what
+ self.cur_rendition = u'\U00100000'
+ self.prev_dump = []
+ self.prev_dump_rend = []
+ self.html_cache = []
def toggle_alternate_screen_buffer_cursor(self, alt):
"""
@@ -1977,7 +1967,7 @@ def insert_characters(self, n=1):
n = int(n)
for i in xrange(n):
self.screen[self.cursorY].pop() # Take one down, pass it around
- self.screen[self.cursorY].insert(self.cursorX, u' ')
+ self.screen[self.cursorY].insert(self.cursorX, u'\U00100000')
def delete_characters(self, n=1):
"""
@@ -2000,8 +1990,7 @@ def delete_characters(self, n=1):
self.screen[self.cursorY].pop(self.cursorX)
self.screen[self.cursorY].append(u' ')
self.renditions[self.cursorY].pop(self.cursorX)
- self.renditions[self.cursorY].append(u' ')
- #self.renditions[self.cursorY].append([0])
+ self.renditions[self.cursorY].append(u'\U00100000')
except IndexError:
# At edge of screen, ignore
#print('IndexError in delete_characters(): %s' % e)
@@ -2023,8 +2012,7 @@ def _erase_characters(self, n=1):
n = min(n, distance)
for i in xrange(n):
self.screen[self.cursorY][self.cursorX+i] = u' '
- self.renditions[self.cursorY][self.cursorX+i] = u' '
- #self.renditions[self.cursorY][self.cursorX+i] = [0]
+ self.renditions[self.cursorY][self.cursorX+i] = u'\U00100000'
def cursor_left(self, n=1):
"""ESCnD CUB (Cursor Back)"""
@@ -2166,11 +2154,7 @@ def clear_screen(self):
"""
#logging.debug('clear_screen()')
self.init_screen()
- self.init_renditions()
- #self.renditions = [
- #[self.cur_rendition for a in xrange(self.cols)
- #] for b in xrange(self.rows)
- #]
+ self.init_renditions(self.cur_rendition)
self.cursorX = 0
self.cursorY = 0
@@ -2179,19 +2163,13 @@ def clear_screen_from_cursor_down(self):
Clears the screen from the cursor down (ESC[J or ESC[0J).
"""
#logging.debug('clear_screen_from_cursor_down()')
- #self.screen[self.cursorY:] = [
- #[u' ' for a in xrange(self.cols)] for a in self.screen[self.cursorY:]
- #]
self.screen[self.cursorY:] = [
array('u', u' ' * self.cols) for a in self.screen[self.cursorY:]
]
+ c = self.cur_rendition # Just to save space below
self.renditions[self.cursorY:] = [
- array('u', u' ' * self.cols) for a in self.renditions[self.cursorY:]
+ array('u', c * self.cols) for a in self.renditions[self.cursorY:]
]
- #self.renditions[self.cursorY:] = [
- #[self.cur_rendition for a in xrange(self.cols)] for a in self.screen[
- #self.cursorY:]
- #]
self.cursorX = 0
def clear_screen_from_cursor_up(self):
@@ -2202,12 +2180,10 @@ def clear_screen_from_cursor_up(self):
self.screen[:self.cursorY+1] = [
array('u', u' ' * self.cols) for a in self.screen[:self.cursorY]
]
+ c = self.cur_rendition
self.renditions[:self.cursorY+1] = [
- array('u', u' ' * self.cols) for a in self.renditions[:self.cursorY]
+ array('u', c * self.cols) for a in self.renditions[:self.cursorY]
]
- #self.renditions[:self.cursorY+1] = [
- #[[0] for a in xrange(self.cols)] for a in self.screen[:self.cursorY]
- #]
self.cursorX = 0
self.cursorY = 0
@@ -2256,13 +2232,11 @@ def clear_line_from_cursor_right(self):
saved = self.screen[self.cursorY][:self.cursorX]
saved_renditions = self.renditions[self.cursorY][:self.cursorX]
spaces = array('u', u' '*len(self.screen[self.cursorY][self.cursorX:]))
+ renditions = array('u',
+ self.cur_rendition * len(self.screen[self.cursorY][self.cursorX:]))
self.screen[self.cursorY] = saved + spaces
- #self.screen[self.cursorY][self.cursorX:] = [
- #u' ' for a in self.screen[self.cursorY][self.cursorX:]]
# Reset the cursor position's rendition to the end of the line
- self.renditions[self.cursorY] = saved_renditions + spaces
- #self.renditions[self.cursorY][self.cursorX:] = [
- #self.cur_rendition for a in self.screen[self.cursorY][self.cursorX:]]
+ self.renditions[self.cursorY] = saved_renditions + renditions
def clear_line_from_cursor_left(self):
"""
@@ -2272,20 +2246,19 @@ def clear_line_from_cursor_left(self):
saved = self.screen[self.cursorY][self.cursorX:]
saved_renditions = self.renditions[self.cursorY][self.cursorX:]
spaces = array('u', u' '*len(self.screen[self.cursorY][:self.cursorX]))
+ renditions = array('u',
+ self.cur_rendition * len(self.screen[self.cursorY][self.cursorX:]))
self.screen[self.cursorY] = spaces + saved
- self.renditions[self.cursorY] = spaces + saved_renditions
- #self.renditions[self.cursorY] = [
- #[] for a in self.screen[self.cursorY][:self.cursorX]
- #] + saved_renditions
+ self.renditions[self.cursorY] = renditions + saved_renditions
def clear_line(self):
"""
Clears the entire line (ESC[2K).
"""
#logging.debug("clear_line()")
self.screen[self.cursorY] = array('u', u' ' * self.cols)
- self.renditions[self.cursorY] = array('u', u' ' * self.cols)
- #self.renditions[self.cursorY] = [[0] for a in xrange(self.cols)]
+ c = self.cur_rendition
+ self.renditions[self.cursorY] = array('u', c * self.cols)
self.cursorX = 0
def clear_line_from_cursor(self, n):
@@ -2376,17 +2349,11 @@ def _set_rendition(self, n):
preserved.
"""
#logging.debug("_set_rendition(%s)" % n)
- # TODO: Make this whole thing faster (or prove it isn't possible).
- # TODO: If we can't make it faster then we should at least see if we can
- # simplify it.
cursorY = self.cursorY
cursorX = self.cursorX
- #logging.debug("Setting rendition: %s at %s, %s" % (repr(n), cursorY, cursorX))
if cursorX >= self.cols: # We're at the end of the row
if len(self.renditions[cursorY]) <= cursorX:
# Make it all longer
- #logging.debug("Making line %s longer" % self.cursorY)
- #self.renditions[cursorY].append([]) # Make it longer
self.renditions[cursorY].append(u' ') # Make it longer
self.screen[cursorY].append(u'\x00') # This needs to match
if cursorY >= self.rows:
@@ -2395,15 +2362,14 @@ def _set_rendition(self, n):
"cursorY >= self.rows! This should not happen! Bug!"))
return # Don't bother setting renditions past the bottom
if not n: # or \x1b[m (reset)
- #self.cur_rendition = [0]
# First char in PUA Plane 16 is always the default:
self.cur_rendition = u'\U00100000' # Should be reset (e.g. [0])
return # No need for further processing; save some CPU
# Convert the string (e.g. '0;1;32') to a list (e.g. [0,1,32]
new_renditions = [int(a) for a in n.split(';') if a != '']
# Handle 256-color renditions by getting rid of the (38|48);5 part and
# incrementing foregrounds by 1000 and backgrounds by 10000 so we can
- # tell them apart in __spanify_screen().
+ # tell them apart in _spanify_screen().
if 38 in new_renditions:
foreground_index = new_renditions.index(38)
if len(new_renditions[foreground_index:]) >= 2:
@@ -2438,7 +2404,6 @@ def _set_rendition(self, n):
for k, v in self.renditions_store.items():
if reduced == v:
self.cur_rendition = k
- #self.cur_rendition = _reduce_renditions(out_renditions)
return
new_renditions = out_renditions
cur_rendition_list = self.renditions_store[self.cur_rendition]
@@ -2451,7 +2416,6 @@ def _set_rendition(self, n):
for k, v in self.renditions_store.items():
if reduced == v:
self.cur_rendition = k
- #self.cur_rendition = reduced
def _opt_handler(self, chars):
"""
@@ -2483,13 +2447,24 @@ def _spanify_screen(self):
lines. It also marks the cursor position via a <span> tag at the
appropriate location.
"""
+ #logging.debug("_spanify_screen()")
results = []
+ # NOTE: Why these duplicates of self.* and globals? Local variable
+ # lookups are faster--especially in loops.
rendition_classes = RENDITION_CLASSES
screen = self.screen
renditions = self.renditions
renditions_store = self.renditions_store
cursorX = self.cursorX
cursorY = self.cursorY
+ if len(self.prev_dump) != len(screen):
+ # Fix it to be equal--assume first time/screen reset/resize/etc
+ # Just fill it with empty strings (only the length matters here)
+ self.prev_dump = [[] for a in screen]
+ self.prev_dump_rend = [[] for a in screen]
+ # The html_cache may need to be fixed as well
+ if len(self.html_cache) != len(screen):
+ self.html_cache = [u'' for a in screen] # Essentially a reset
spancount = 0
current_classes = []
prev_rendition = None
@@ -2498,10 +2473,15 @@ def _spanify_screen(self):
for linecount, line_rendition in enumerate(izip(screen, renditions)):
line = line_rendition[0]
rendition = line_rendition[1]
+ if linecount != cursorY and self.prev_dump[linecount] == line:
+ if '<span class="cursor">' not in self.html_cache[linecount]:
+ if self.prev_dump_rend[linecount] == rendition:
+ # No change since the last dump. Use the cache...
+ results.append(self.html_cache[linecount])
+ continue # Nothing changed so move on to the next line
outline = ""
charcount = 0
for char, rend in izip(line, rendition):
- #print("char: %s, rend: %s" % (`char`, `rend`))
rend = renditions_store[rend] # Get actual rendition
if ord(char) > 1048575: # Special stuff =)
# Obviously, not really a single character
@@ -2614,10 +2594,14 @@ def _spanify_screen(self):
else:
outline += char
charcount += 1
+ self.prev_dump[linecount] = line[:]
+ self.prev_dump_rend[linecount] = rendition[:]
if outline:
results.append(outline)
+ self.html_cache[linecount] = outline
else:
results.append(None) # 'null' is shorter than 4 spaces
+ self.html_cache[linecount] = None
# NOTE: The client has been programmed to treat None (aka null in
# JavaScript) as blank lines.
for whatever in xrange(spancount): # Bit of cleanup to be safe
@@ -2757,10 +2741,7 @@ def dump_html(self, renditions=True):
.. note:: This places <span class="cursor">(current character)</span> around the cursor location.
"""
- # NOTE: On my laptop this function will take about 30ms to complete
- # a full-screen 'top' refresh on a 57x209 screen.
- # In other words, it is pretty fast... Not much optimization necessary
- if renditions:
+ if renditions: # i.e. Use stylized text
screen = self._spanify_screen()
scrollback = []
if self.scrollback_buf:

0 comments on commit 67c05c6

Please sign in to comment.
Something went wrong with that request. Please try again.