def calc_coords(text, layout, pos, clamp=1): """ Calculate the coordinates closest to position pos in text with layout. text -- raw string or unicode string layout -- layout structure applied to text pos -- integer position into text clamp -- ignored right now """ closest = None y = 0 for line_layout in layout: x = 0 for seg in line_layout: s = LayoutSegment(seg) if s.offs is None: x += s.sc continue if s.offs == pos: return x, y if s.end is not None and s.offs <= pos and s.end > pos: x += calc_width(text, s.offs, pos) return x, y distance = abs(s.offs - pos) if s.end is not None and s.end < pos: distance = pos - (s.end - 1) if closest is None or distance < closest[0]: closest = distance, (x, y) x += s.sc y += 1 if closest: return closest[1] return 0, 0
def __init__(self, text=None, attr=None, cs=None, cursor=None, maxcol=None, check_width=True): """ text -- list of strings, one for each line attr -- list of run length encoded attributes for text cs -- list of run length encoded character set for text cursor -- (x,y) of cursor or None maxcol -- screen columns taken by this canvas check_width -- check and fix width of all lines in text """ Canvas.__init__(self) if text == None: text = [] if check_width: widths = [] for t in text: if type(t) != bytes: raise CanvasError("Canvas text must be plain strings encoded in the screen's encoding", repr(text)) widths.append( calc_width( t, 0, len(t)) ) else: assert type(maxcol) == int widths = [maxcol] * len(text) if maxcol is None: if widths: # find maxcol ourselves maxcol = max(widths) else: maxcol = 0 if attr == None: attr = [[] for x in range(len(text))] if cs == None: cs = [[] for x in range(len(text))] # pad text and attr to maxcol for i in range(len(text)): w = widths[i] if w > maxcol: raise CanvasError("Canvas text is wider than the maxcol specified \n%r\n%r\n%r"%(maxcol,widths,text)) if w < maxcol: text[i] = text[i] + bytes().rjust(maxcol-w) a_gap = len(text[i]) - rle_len( attr[i] ) if a_gap < 0: raise CanvasError("Attribute extends beyond text \n%r\n%r" % (text[i],attr[i]) ) if a_gap: rle_append_modify( attr[i], (None, a_gap)) cs_gap = len(text[i]) - rle_len( cs[i] ) if cs_gap < 0: raise CanvasError("Character Set extends beyond text \n%r\n%r" % (text[i],cs[i]) ) if cs_gap: rle_append_modify( cs[i], (None, cs_gap)) self._attr = attr self._cs = cs self.cursor = cursor self._text = text self._maxcol = maxcol
def calc_coords( text, layout, pos, clamp=1 ): """ Calculate the coordinates closest to position pos in text with layout. text -- raw string or unicode string layout -- layout structure applied to text pos -- integer position into text clamp -- ignored right now """ closest = None y = 0 for line_layout in layout: x = 0 for seg in line_layout: s = LayoutSegment(seg) if s.offs is None: x += s.sc continue if s.offs == pos: return x,y if s.end is not None and s.offs<=pos and s.end>pos: x += calc_width( text, s.offs, pos ) return x,y distance = abs(s.offs - pos) if s.end is not None and s.end<pos: distance = pos - (s.end-1) if closest is None or distance < closest[0]: closest = distance, (x,y) x += s.sc y += 1 if closest: return closest[1] return 0,0
def text_width(txt): """ Return the width of the text in the terminal Use this instead of len() whenever txt could contain double- or zero-width Unicode characters. """ return calc_width(txt, 0, len(txt))
def _last_row(self, row): """On the last row we need to slide the bottom right character into place. Calculate the new line, attr and an insert sequence to do that. eg. last row: XXXXXXXXXXXXXXXXXXXXYZ Y will be drawn after Z, shifting Z into position. """ new_row = row[:-1] z_attr, z_cs, last_text = row[-1] last_cols = util.calc_width(last_text, 0, len(last_text)) last_offs, z_col = util.calc_text_pos(last_text, 0, len(last_text), last_cols-1) if last_offs == 0: z_text = last_text del new_row[-1] # we need another segment y_attr, y_cs, nlast_text = row[-2] nlast_cols = util.calc_width(nlast_text, 0, len(nlast_text)) z_col += nlast_cols nlast_offs, y_col = util.calc_text_pos(nlast_text, 0, len(nlast_text), nlast_cols-1) y_text = nlast_text[nlast_offs:] if nlast_offs: new_row.append((y_attr, y_cs, nlast_text[:nlast_offs])) else: z_text = last_text[last_offs:] y_attr, y_cs = z_attr, z_cs nlast_cols = util.calc_width(last_text, 0, last_offs) nlast_offs, y_col = util.calc_text_pos(last_text, 0, last_offs, nlast_cols-1) y_text = last_text[nlast_offs:last_offs] if nlast_offs: new_row.append((y_attr, y_cs, last_text[:nlast_offs])) new_row.append((z_attr, z_cs, z_text)) return new_row, z_col-y_col, (y_attr, y_cs, y_text)
def draw_screen(self, size, r): """Create an html fragment from the render object. Append it to HtmlGenerator.fragments list. """ # collect output in l l = [] cols, rows = size assert r.rows() == rows if r.cursor is not None: cx, cy = r.cursor else: cx = cy = None y = -1 for row in r.content(): y += 1 col = 0 for a, cs, run in row: if not str is bytes: run = run.decode() run = run.translate(_trans_table) if isinstance(a, AttrSpec): aspec = a else: aspec = self._palette[a][{ 1: 1, 16: 0, 88: 2, 256: 3 }[self.colors]] if y == cy and col <= cx: run_width = util.calc_width(run, 0, len(run)) if col + run_width > cx: l.append(html_span(run, aspec, cx - col)) else: l.append(html_span(run, aspec)) col += run_width else: l.append(html_span(run, aspec)) l.append("\n") # add the fragment to the list self.fragments.append("<pre>%s</pre>" % "".join(l))
def draw_screen(self, xxx_todo_changeme, r ): """Create an html fragment from the render object. Append it to HtmlGenerator.fragments list. """ (cols, rows) = xxx_todo_changeme l = [] assert r.rows() == rows if r.cursor is not None: cx, cy = r.cursor else: cx = cy = None y = -1 for row in r.content(): y += 1 col = 0 for a, cs, run in row: run = run.translate(_trans_table) if isinstance(a, AttrSpec): aspec = a else: aspec = self._palette[a][ {1: 1, 16: 0, 88:2, 256:3}[self.colors]] if y == cy and col <= cx: run_width = util.calc_width(run, 0, len(run)) if col+run_width > cx: l.append(html_span(run, aspec, cx-col)) else: l.append(html_span(run, aspec)) col += run_width else: l.append(html_span(run, aspec)) l.append("\n") # add the fragment to the list self.fragments.append( "<pre>%s</pre>" % "".join(l) )
def make_canvas(txt, attr, maxcol, fill_attr=None): processed_txt = [] processed_attr = [] processed_cs = [] for line, line_attr in zip(txt, attr): # filter out zero-length attrs line_attr = [(aname, l) for aname, l in line_attr if l > 0] diff = maxcol - calc_width(line, 0, len(line)) if diff > 0: line += " " * diff line_attr.append((fill_attr, diff)) else: from urwid.util import rle_subseg line = line[:maxcol] line_attr = rle_subseg(line_attr, 0, maxcol) from urwid.util import apply_target_encoding encoded_line, line_cs = apply_target_encoding(line) # line_cs contains byte counts as requested by TextCanvas, but # line_attr still contains column counts at this point: let's fix this. def get_byte_line_attr(line, line_attr): i = 0 for label, column_count in line_attr: byte_count = len(line[i:i + column_count].encode(_target_encoding)) i += column_count yield label, byte_count line_attr = list(get_byte_line_attr(line, line_attr)) processed_txt.append(encoded_line) processed_attr.append(line_attr) processed_cs.append(line_cs) return urwid.TextCanvas(processed_txt, processed_attr, processed_cs, maxcol=maxcol)
def pack(self, size=None, focus=False): """ Return the number of screen columns and rows required for this Text widget to be displayed without wrapping or clipping, as a single element tuple. size -- None for unlimited screen columns or (maxcol,) to specify a maximum column size >>> Text(u"important things").pack() (16, 1) >>> Text(u"important things").pack((15,)) (9, 2) >>> Text(u"important things").pack((8,)) (8, 2) """ text, attr = self.get_text() if size is not None: (maxcol, ) = size if not hasattr(self.layout, "pack"): return size trans = self.get_line_translation(maxcol, (text, attr)) cols = self.layout.pack(maxcol, trans) return (cols, len(trans)) i = 0 cols = 0 while i < len(text): j = text.find('\n', i) if j == -1: j = len(text) c = calc_width(text, i, j) if c > cols: cols = c i = j + 1 return (cols, text.count('\n') + 1)
def pack(self, size=None, focus=False): """ Return the number of screen columns and rows required for this Text widget to be displayed without wrapping or clipping, as a single element tuple. size -- None for unlimited screen columns or (maxcol,) to specify a maximum column size >>> Text(u"important things").pack() (16, 1) >>> Text(u"important things").pack((15,)) (9, 2) >>> Text(u"important things").pack((8,)) (8, 2) """ text, attr = self.get_text() if size is not None: (maxcol,) = size if not hasattr(self.layout, "pack"): return size trans = self.get_line_translation( maxcol, (text,attr)) cols = self.layout.pack( maxcol, trans ) return (cols, len(trans)) i = 0 cols = 0 while i < len(text): j = text.find('\n', i) if j == -1: j = len(text) c = calc_width(text, i, j) if c>cols: cols = c i = j+1 return (cols, text.count('\n') + 1)
def calculate_text_segments(self, text, width, wrap): """ Calculate the segments of text to display given width screen columns to display them. text - unicode text or byte string to display width - number of available screen columns wrap - wrapping mode used Returns a layout structure without alignment applied. """ nl, nl_o, sp_o = "\n", "\n", " " if PYTHON3 and isinstance(text, bytes): nl = B(nl) # can only find bytes in python3 bytestrings nl_o = ord(nl_o) # + an item of a bytestring is the ordinal value sp_o = ord(sp_o) b = [] p = 0 if wrap == 'clip': # no wrapping to calculate, so it's easy. while p<=len(text): n_cr = text.find(nl, p) if n_cr == -1: n_cr = len(text) sc = calc_width(text, p, n_cr) l = [(0,n_cr)] if p!=n_cr: l = [(sc, p, n_cr)] + l b.append(l) p = n_cr+1 return b while p<=len(text): # look for next eligible line break n_cr = text.find(nl, p) if n_cr == -1: n_cr = len(text) sc = calc_width(text, p, n_cr) if sc == 0: # removed character hint b.append([(0,n_cr)]) p = n_cr+1 continue if sc <= width: # this segment fits b.append([(sc,p,n_cr), # removed character hint (0,n_cr)]) p = n_cr+1 continue pos, sc = calc_text_pos( text, p, n_cr, width ) if pos == p: # pathological width=1 double-byte case raise CanNotDisplayText( "Wide character will not fit in 1-column width") if wrap == 'any': b.append([(sc,p,pos)]) p = pos continue assert wrap == 'space' if text[pos] == sp_o: # perfect space wrap b.append([(sc,p,pos), # removed character hint (0,pos)]) p = pos+1 continue if is_wide_char(text, pos): # perfect next wide b.append([(sc,p,pos)]) p = pos continue prev = pos while prev > p: prev = move_prev_char(text, p, prev) if text[prev] == sp_o: sc = calc_width(text,p,prev) l = [(0,prev)] if p!=prev: l = [(sc,p,prev)] + l b.append(l) p = prev+1 break if is_wide_char(text,prev): # wrap after wide char next = move_next_char(text, prev, pos) sc = calc_width(text,p,next) b.append([(sc,p,next)]) p = next break else: # unwrap previous line space if possible to # fit more text (we're breaking a word anyway) if b and (len(b[-1]) == 2 or ( len(b[-1])==1 and len(b[-1][0])==2 )): # look for removed space above if len(b[-1]) == 1: [(h_sc, h_off)] = b[-1] p_sc = 0 p_off = p_end = h_off else: [(p_sc, p_off, p_end), (h_sc, h_off)] = b[-1] if (p_sc < width and h_sc==0 and text[h_off] == sp_o): # combine with previous line del b[-1] p = p_off pos, sc = calc_text_pos( text, p, n_cr, width ) b.append([(sc,p,pos)]) # check for trailing " " or "\n" p = pos if p < len(text) and ( text[p] in (sp_o, nl_o)): # removed character hint b[-1].append((0,p)) p += 1 continue # force any char wrap b.append([(sc,p,pos)]) p = pos return b
def calculate_text_segments( self, text, width, wrap ): """ Calculate the segments of text to display given width screen columns to display them. text - text to display width - number of available screen columns wrap - wrapping mode used Returns a layout structure without aligmnent applied. """ b = [] p = 0 if wrap == 'clip': # no wrapping to calculate, so it's easy. while p<=len(text): n_cr = text.find("\n", p) if n_cr == -1: n_cr = len(text) sc = calc_width(text, p, n_cr) l = [(0,n_cr)] if p!=n_cr: l = [(sc, p, n_cr)] + l b.append(l) p = n_cr+1 return b while p<=len(text): # look for next eligible line break n_cr = text.find("\n", p) if n_cr == -1: n_cr = len(text) sc = calc_width(text, p, n_cr) if sc == 0: # removed character hint b.append([(0,n_cr)]) p = n_cr+1 continue if sc <= width: # this segment fits b.append([(sc,p,n_cr), # removed character hint (0,n_cr)]) p = n_cr+1 continue pos, sc = calc_text_pos( text, p, n_cr, width ) # FIXME: handle pathological width=1 double-byte case if wrap == 'any': b.append([(sc,p,pos)]) p = pos continue assert wrap == 'space' if text[pos] == " ": # perfect space wrap b.append([(sc,p,pos), # removed character hint (0,pos)]) p = pos+1 continue if is_wide_char(text, pos): # perfect next wide b.append([(sc,p,pos)]) p = pos continue prev = pos while prev > p: prev = move_prev_char(text, p, prev) if text[prev] == " ": sc = calc_width(text,p,prev) l = [(0,prev)] if p!=prev: l = [(sc,p,prev)] + l b.append(l) p = prev+1 break if is_wide_char(text,prev): # wrap after wide char next = move_next_char(text, prev, pos) sc = calc_width(text,p,next) b.append([(sc,p,next)]) p = next break else: # unwrap previous line space if possible to # fit more text (we're breaking a word anyway) if b and (len(b[-1]) == 2 or ( len(b[-1])==1 and len(b[-1][0])==2 )): # look for removed space above if len(b[-1]) == 1: [(h_sc, h_off)] = b[-1] p_sc = 0 p_off = p_end = h_off else: [(p_sc, p_off, p_end), (h_sc, h_off)] = b[-1] if (p_sc < width and h_sc==0 and text[h_off] == " "): # combine with previous line del b[-1] p = p_off pos, sc = calc_text_pos( text, p, n_cr, width ) b.append([(sc,p,pos)]) # check for trailing " " or "\n" p = pos if p < len(text) and ( text[p] in (" ","\n")): # removed character hint b[-1].append((0,p)) p += 1 continue # force any char wrap b.append([(sc,p,pos)]) p = pos return b
def calculate_text_segments(self, text, width, wrap): """ Calculate the segments of text to display given width screen columns to display them. text - unicode text or byte string to display width - number of available screen columns wrap - wrapping mode used Returns a layout structure without alignment applied. """ # TODO: This function is a horror and a mess, and really hard to # understand. It's based on urwids StandardLayout, which by itself # is overly complex, and I added tab handling, which made it worse. # It's a prime candidate for refacturing, making easier to understand # and as it is heavily used, profiling would be nice too. nl, nl_o, sp_o, tab_o = "\n", "\n", " ", "\t" if PYTHON3 and isinstance(text, bytes): nl = B(nl) # can only find bytes in python3 bytestrings nl_o = ord(nl_o) # + an item of a bytestring is the ordinal value sp_o = ord(sp_o) tab_o = ord(tab_o) b = [] p = 0 if wrap == 'clip': # no wrapping to calculate, so it's easy. l = [] while p <= len(text): n_cr = find_newline(text, p) if p != n_cr: line = text[p:n_cr] pt = 0 while pt < len(line): n_tab = line.find(tab_o, pt) if n_tab == -1: end = len(line) else: end = n_tab sc = calc_width(line, pt, end) if sc != 0: l.append((sc, p + pt, p + end)) if end == n_tab: # A tab was found extra_space = (self.tab_width - ( sc % self.tab_width)) l.append((extra_space, p + n_tab)) pt = end + 1 l.append((0, n_cr)) b.append(l) l = [] if text[n_cr:n_cr+2] in TWOCHAR_NEWLINES: # Two char newline: p = n_cr + 2 else: p = n_cr + 1 return b while p <= len(text): # look for next eligible line break n_cr = find_newline(text, p) line = text[p:n_cr] l = [] pt = 0 lc = 0 while pt < len(line): n_tab = line.find(tab_o, pt) if n_tab == -1: end = len(line) else: end = n_tab sc = calc_width(line, pt, end) if lc + sc <= width: # this segment fits if sc: l.append((sc, p + pt, p + end)) if end == n_tab: # A tab was found extra_space = self.tab_width - (sc % self.tab_width) l.append((extra_space, p + n_tab)) lc += extra_space else: # removed character hint l.append((0, p + end)) pt = end + 1 lc += sc if lc >= width: # The tab can sometimes push line length to width, and # then we adjust the line length and make a new line. overshoot = lc - width spaces, pos = l[-1] l[-1] = (spaces - overshoot, pos) b.append(l) l = [] lc = 0 continue # This segment does not fit. Let's fit it. pos, sc = calc_text_pos(line, pt, end, width - lc) if pos == pt: # pathological width=1 double-byte case raise CanNotDisplayText( "Wide character will not fit in 1-column width") if wrap == 'any': l.append((sc, p + pt, p + pos)) l.append((0, p + pos)) b.append(l) l = [] lc = 0 pt = pos continue assert wrap == 'space' if line[pos] == sp_o: # perfect space wrap l.append((sc, p + pt, p + pos)) # removed character hint l.append((0, p + pos)) b.append(l) l = [] lc = 0 pt = pos + 1 continue if is_wide_char(line, pos): # perfect next wide l.append((sc, p + pt, p + pos)) b.append(l) l = [] lc = 0 pt = pos continue prev = pos while prev > pt: prev = move_prev_char(line, pt, prev) if line[prev] == sp_o: sc = calc_width(line, pt, prev) if prev != pt: l.append((sc, p + pt, p + prev)) l.append((0, p + prev)) b.append(l) l = [] lc = 0 pt = prev + 1 break if is_wide_char(line, prev): # wrap after wide char nextc = move_next_char(line, prev, pos) sc = calc_width(line, pt, nextc) l.append((sc, p + pt, p + nextc)) b.append(l) l = [] lc = 0 pt = nextc break else: if lc == 0: # unwrap previous line space if possible to # fit more text (we're breaking a word anyway) if b and (len(b[-1]) == 2 or (len(b[-1]) == 1 and len(b[-1][0]) == 2)): # look for removed space above if len(b[-1]) == 1: [(h_sc, h_off)] = b[-1] p_sc = 0 p_off = p_end = h_off else: [(p_sc, p_off, p_end), (h_sc, h_off)] = b[-1][-2:] if (p_sc < width and h_sc == 0 and text[h_off] == sp_o): # combine with previous line old_line = b[-1][:-2] del b[-1] pt = p_off - p pos, sc = calc_text_pos( line, pt, end, width) old_line.append((sc, p + pt, p + pos)) b.append(old_line) # check for trailing " " or "\n" pt = pos if pt < len(text) and ( text[pt] in (sp_o, nl_o)): # removed character hint b[-1].append((0, p + pt)) pt += 1 continue # Break on previous tab, and try again. if l: b.append(l) l = [] lc = 0 continue # There is no space to break the line on, unwrapping the # previous line doesn't help, I guess we just break on a # character. b.append([(sc, p + pt, p + pos)]) l = [] lc = 0 pt = pos # force any char wrap if l: b.append(l) elif not line: # An empty line. b.append([(0, n_cr)]) pt = 1 if text[pt-1:pt+1] in TWOCHAR_NEWLINES: # Two char newline: pt += 1 p += pt return b
def calculate_text_segments(self, text, width, wrap): """ Calculate the segments of text to display given width screen columns to display them. text - unicode text or byte string to display width - number of available screen columns wrap - wrapping mode used Returns a layout structure without aligmnent applied. """ nl, nl_o, sp_o = "\n", "\n", " " if PYTHON3 and isinstance(text, bytes): nl = B(nl) # can only find bytes in python3 bytestrings nl_o = ord(nl_o) # + an item of a bytestring is the ordinal value sp_o = ord(sp_o) b = [] p = 0 if wrap == 'clip': # no wrapping to calculate, so it's easy. while p <= len(text): n_cr = text.find(nl, p) if n_cr == -1: n_cr = len(text) sc = calc_width(text, p, n_cr) l = [(0, n_cr)] if p != n_cr: l = [(sc, p, n_cr)] + l b.append(l) p = n_cr + 1 return b while p <= len(text): # look for next eligible line break n_cr = text.find(nl, p) if n_cr == -1: n_cr = len(text) sc = calc_width(text, p, n_cr) if sc == 0: # removed character hint b.append([(0, n_cr)]) p = n_cr + 1 continue if sc <= width: # this segment fits b.append([ (sc, p, n_cr), # removed character hint (0, n_cr) ]) p = n_cr + 1 continue pos, sc = calc_text_pos(text, p, n_cr, width) if pos == p: # pathological width=1 double-byte case raise CanNotDisplayText( "Wide character will not fit in 1-column width") if wrap == 'any': b.append([(sc, p, pos)]) p = pos continue assert wrap == 'space' if text[pos] == sp_o: # perfect space wrap b.append([ (sc, p, pos), # removed character hint (0, pos) ]) p = pos + 1 continue if is_wide_char(text, pos): # perfect next wide b.append([(sc, p, pos)]) p = pos continue prev = pos while prev > p: prev = move_prev_char(text, p, prev) if text[prev] == sp_o: sc = calc_width(text, p, prev) l = [(0, prev)] if p != prev: l = [(sc, p, prev)] + l b.append(l) p = prev + 1 break if is_wide_char(text, prev): # wrap after wide char next = move_next_char(text, prev, pos) sc = calc_width(text, p, next) b.append([(sc, p, next)]) p = next break else: # unwrap previous line space if possible to # fit more text (we're breaking a word anyway) if b and (len(b[-1]) == 2 or (len(b[-1]) == 1 and len(b[-1][0]) == 2)): # look for removed space above if len(b[-1]) == 1: [(h_sc, h_off)] = b[-1] p_sc = 0 p_off = p_end = h_off else: [(p_sc, p_off, p_end), (h_sc, h_off)] = b[-1] if (p_sc < width and h_sc == 0 and text[h_off] == sp_o): # combine with previous line del b[-1] p = p_off pos, sc = calc_text_pos(text, p, n_cr, width) b.append([(sc, p, pos)]) # check for trailing " " or "\n" p = pos if p < len(text) and (text[p] in (sp_o, nl_o)): # removed character hint b[-1].append((0, p)) p += 1 continue # force any char wrap b.append([(sc, p, pos)]) p = pos return b
class Screen: def __init__(self): self.palette = {} self.has_color = True self._started = False started = property(lambda self: self._started) def register_palette(self, l): """Register a list of palette entries. l -- list of (name, foreground, background) or (name, same_as_other_name) palette entries. calls self.register_palette_entry for each item in l """ for item in l: if len(item) in (3, 4): self.register_palette_entry(*item) continue assert len(item) == 2, "Invalid register_palette usage" name, like_name = item if not self.palette.has_key(like_name): raise Exception("palette entry '%s' doesn't exist" % like_name) self.palette[name] = self.palette[like_name] def register_palette_entry(self, name, foreground, background, mono=None): """Register a single palette entry. name -- new entry/attribute name foreground -- foreground colour background -- background colour mono -- monochrome terminal attribute See curses_display.register_palette_entry for more info. """ if foreground == "default": foreground = "black" if background == "default": background = "light gray" self.palette[name] = (foreground, background, mono) def set_mouse_tracking(self, enable=True): """Not yet implemented""" pass def tty_signal_keys(self, *args, **vargs): """Do nothing.""" pass def start(self): """ This function reads the initial screen size, generates a unique id and handles cleanup when fn exits. web_display.set_preferences(..) must be called before calling this function for the preferences to take effect """ global _prefs assert not self._started client_init = sys.stdin.read(50) assert client_init.startswith("window resize "), client_init ignore1, ignore2, x, y = client_init.split(" ", 3) x = int(x) y = int(y) self._set_screen_size(x, y) self.last_screen = {} self.last_screen_width = 0 self.update_method = os.environ["HTTP_X_URWID_METHOD"] assert self.update_method in ("multipart", "polling") if self.update_method == "polling" and not _prefs.allow_polling: sys.stdout.write("Status: 403 Forbidden\r\n\r\n") sys.exit(0) clients = glob.glob(os.path.join(_prefs.pipe_dir, "urwid*.in")) if len(clients) >= _prefs.max_clients: sys.stdout.write("Status: 503 Sever Busy\r\n\r\n") sys.exit(0) urwid_id = "%09d%09d" % (random.randrange(10** 9), random.randrange(10**9)) self.pipe_name = os.path.join(_prefs.pipe_dir, "urwid" + urwid_id) os.mkfifo(self.pipe_name + ".in", 0600) signal.signal(signal.SIGTERM, self._cleanup_pipe) self.input_fd = os.open(self.pipe_name + ".in", os.O_NONBLOCK | os.O_RDONLY) self.input_tail = "" self.content_head = ("Content-type: " "multipart/x-mixed-replace;boundary=ZZ\r\n" "X-Urwid-ID: " + urwid_id + "\r\n" "\r\n\r\n" "--ZZ\r\n") if self.update_method == "polling": self.content_head = ("Content-type: text/plain\r\n" "X-Urwid-ID: " + urwid_id + "\r\n" "\r\n\r\n") signal.signal(signal.SIGALRM, self._handle_alarm) signal.alarm(ALARM_DELAY) self._started = True def stop(self): """ Restore settings and clean up. """ assert self._started # XXX which exceptions does this actually raise? EnvironmentError? try: self._close_connection() except Exception: pass signal.signal(signal.SIGTERM, signal.SIG_DFL) self._cleanup_pipe() self._started = False def set_input_timeouts(self, *args): pass def run_wrapper(self, fn): """ Run the application main loop, calling start() first and stop() on exit. """ try: self.start() return fn() finally: self.stop() def _close_connection(self): if self.update_method == "polling child": self.server_socket.settimeout(0) sock, addr = self.server_socket.accept() sock.sendall("Z") sock.close() if self.update_method == "multipart": sys.stdout.write("\r\nZ" "\r\n--ZZ--\r\n") sys.stdout.flush() def _cleanup_pipe(self, *args): if not self.pipe_name: return # XXX which exceptions does this actually raise? EnvironmentError? try: os.remove(self.pipe_name + ".in") os.remove(self.pipe_name + ".update") except Exception: pass def _set_screen_size(self, cols, rows): """Set the screen size (within max size).""" if cols > MAX_COLS: cols = MAX_COLS if rows > MAX_ROWS: rows = MAX_ROWS self.screen_size = cols, rows def draw_screen(self, (cols, rows), r): """Send a screen update to the client.""" if cols != self.last_screen_width: self.last_screen = {} sendq = [self.content_head] if self.update_method == "polling": send = sendq.append elif self.update_method == "polling child": signal.alarm(0) try: s, addr = self.server_socket.accept() except socket.timeout: sys.exit(0) send = s.sendall else: signal.alarm(0) send = sendq.append send("\r\n") self.content_head = "" assert r.rows() == rows if r.cursor is not None: cx, cy = r.cursor else: cx = cy = None new_screen = {} y = -1 for row in r.content(): y += 1 row = list(row) l = [] sig = tuple(row) if y == cy: sig = sig + (cx, ) new_screen[sig] = new_screen.get(sig, []) + [y] old_line_numbers = self.last_screen.get(sig, None) if old_line_numbers is not None: if y in old_line_numbers: old_line = y else: old_line = old_line_numbers[0] send("<%d\n" % old_line) continue col = 0 for (a, cs, run) in row: run = run.translate(_trans_table) if a is None: fg, bg, mono = "black", "light gray", None else: fg, bg, mono = self.palette[a] if y == cy and col <= cx: run_width = util.calc_width(run, 0, len(run)) if col + run_width > cx: l.append(code_span(run, fg, bg, cx - col)) else: l.append(code_span(run, fg, bg)) col += run_width else: l.append(code_span(run, fg, bg)) send("".join(l) + "\n") self.last_screen = new_screen self.last_screen_width = cols if self.update_method == "polling": sys.stdout.write("".join(sendq)) sys.stdout.flush() sys.stdout.close() self._fork_child() elif self.update_method == "polling child": s.close() else: # update_method == "multipart" send("\r\n--ZZ\r\n") sys.stdout.write("".join(sendq)) sys.stdout.flush() signal.alarm(ALARM_DELAY)
def wtest(self, desc, s, exp): s = B(s) result = util.calc_width(s, 0, len(s)) assert result == exp, "%s got:%r expected:%r" % (desc, result, exp)
class HtmlGenerator(BaseScreen): # class variables fragments = [] sizes = [] keys = [] started = True def __init__(self): super(HtmlGenerator, self).__init__() self.colors = 16 self.bright_is_bold = False # ignored self.has_underline = True # ignored self.register_palette_entry(None, _default_foreground, _default_background) def set_terminal_properties(self, colors=None, bright_is_bold=None, has_underline=None): if colors is None: colors = self.colors if bright_is_bold is None: bright_is_bold = self.bright_is_bold if has_underline is None: has_underline = self.has_underline self.colors = colors self.bright_is_bold = bright_is_bold self.has_underline = has_underline def set_mouse_tracking(self): """Not yet implemented""" pass def start(self): pass def stop(self): pass def set_input_timeouts(self, *args): pass def reset_default_terminal_palette(self, *args): pass def run_wrapper(self, fn): """Call fn.""" return fn() def draw_screen(self, (cols, rows), r): """Create an html fragment from the render object. Append it to HtmlGenerator.fragments list. """ # collect output in l l = [] assert r.rows() == rows if r.cursor is not None: cx, cy = r.cursor else: cx = cy = None y = -1 for row in r.content(): y += 1 col = 0 for a, cs, run in row: run = run.translate(_trans_table) if isinstance(a, AttrSpec): aspec = a else: aspec = self._palette[a][{ 1: 1, 16: 0, 88: 2, 256: 3 }[self.colors]] if y == cy and col <= cx: run_width = util.calc_width(run, 0, len(run)) if col + run_width > cx: l.append(html_span(run, aspec, cx - col)) else: l.append(html_span(run, aspec)) col += run_width else: l.append(html_span(run, aspec)) l.append("\n") # add the fragment to the list self.fragments.append("<pre>%s</pre>" % "".join(l))
def wtest(self, desc, s, exp): s = B(s) result = util.calc_width( s, 0, len(s)) assert result==exp, "%s got:%r expected:%r" % (desc, result, exp)
def draw_screen(self, size, r): """Send a screen update to the client.""" (cols, rows) = size if cols != self.last_screen_width: self.last_screen = {} sendq = [self.content_head] if self.update_method == "polling": send = sendq.append elif self.update_method == "polling child": signal.alarm(0) try: s, addr = self.server_socket.accept() except socket.timeout: sys.exit(0) send = s.sendall else: signal.alarm(0) send = sendq.append send("\r\n") self.content_head = "" assert r.rows() == rows if r.cursor is not None: cx, cy = r.cursor else: cx = cy = None new_screen = {} y = -1 for row in r.content(): y += 1 row = list(row) l = [] sig = tuple(row) if y == cy: sig = sig + (cx, ) new_screen[sig] = new_screen.get(sig, []) + [y] old_line_numbers = self.last_screen.get(sig, None) if old_line_numbers is not None: if y in old_line_numbers: old_line = y else: old_line = old_line_numbers[0] send("<%d\n" % old_line) continue col = 0 for (a, cs, run) in row: run = run.translate(_trans_table) if a is None: fg, bg, mono = "black", "light gray", None else: fg, bg, mono = self.palette[a] if y == cy and col <= cx: run_width = util.calc_width(run, 0, len(run)) if col + run_width > cx: l.append(code_span(run, fg, bg, cx - col)) else: l.append(code_span(run, fg, bg)) col += run_width else: l.append(code_span(run, fg, bg)) send("".join(l) + "\n") self.last_screen = new_screen self.last_screen_width = cols if self.update_method == "polling": sys.stdout.write("".join(sendq)) sys.stdout.flush() sys.stdout.close() self._fork_child() elif self.update_method == "polling child": s.close() else: # update_method == "multipart" send("\r\n--ZZ\r\n") sys.stdout.write("".join(sendq)) sys.stdout.flush() signal.alarm(ALARM_DELAY)
def draw_screen(self, xxx_todo_changeme, r ): """Send a screen update to the client.""" (cols, rows) = xxx_todo_changeme if cols != self.last_screen_width: self.last_screen = {} sendq = [self.content_head] if self.update_method == "polling": send = sendq.append elif self.update_method == "polling child": signal.alarm( 0 ) try: s, addr = self.server_socket.accept() except socket.timeout: sys.exit(0) send = s.sendall else: signal.alarm( 0 ) send = sendq.append send("\r\n") self.content_head = "" assert r.rows() == rows if r.cursor is not None: cx, cy = r.cursor else: cx = cy = None new_screen = {} y = -1 for row in r.content(): y += 1 row = list(row) l = [] sig = tuple(row) if y == cy: sig = sig + (cx,) new_screen[sig] = new_screen.get(sig,[]) + [y] old_line_numbers = self.last_screen.get(sig, None) if old_line_numbers is not None: if y in old_line_numbers: old_line = y else: old_line = old_line_numbers[0] send( "<%d\n"%old_line ) continue col = 0 for (a, cs, run) in row: run = run.translate(_trans_table) if a is None: fg,bg,mono = "black", "light gray", None else: fg,bg,mono = self.palette[a] if y == cy and col <= cx: run_width = util.calc_width(run, 0, len(run)) if col+run_width > cx: l.append(code_span(run, fg, bg, cx-col)) else: l.append(code_span(run, fg, bg)) col += run_width else: l.append(code_span(run, fg, bg)) send("".join(l)+"\n") self.last_screen = new_screen self.last_screen_width = cols if self.update_method == "polling": sys.stdout.write("".join(sendq)) sys.stdout.flush() sys.stdout.close() self._fork_child() elif self.update_method == "polling child": s.close() else: # update_method == "multipart" send("\r\n--ZZ\r\n") sys.stdout.write("".join(sendq)) sys.stdout.flush() signal.alarm( ALARM_DELAY )