#!/usr/bin/env python # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2016, Kovid Goyal import math import os import re import shlex import signal import string import subprocess from collections import namedtuple from contextlib import contextmanager from functools import lru_cache from time import monotonic from .constants import isosx from .fast_data_types import glfw_get_physical_dpi, wcwidth as wcwidth_impl def safe_print(*a, **k): try: print(*a, **k) except Exception: pass def ceil_int(x): return int(math.ceil(x)) @lru_cache(maxsize=2**13) def wcwidth(c: str) -> int: try: return wcwidth_impl(ord(c)) except TypeError: return wcwidth_impl(ord(c[0])) @contextmanager def timeit(name, do_timing=False): if do_timing: st = monotonic() yield if do_timing: safe_print('Time for {}: {}'.format(name, monotonic() - st)) def sanitize_title(x): return re.sub(r'\s+', ' ', re.sub(r'[\0-\x19]', '', x)) def get_logical_dpi(): if not hasattr(get_logical_dpi, 'ans'): if isosx: # TODO: Investigate if this needs a different implementation on OS X get_logical_dpi.ans = glfw_get_physical_dpi() else: raw = subprocess.check_output(['xdpyinfo']).decode('utf-8') m = re.search( r'^\s*resolution:\s*(\d+)+x(\d+)', raw, flags=re.MULTILINE ) get_logical_dpi.ans = int(m.group(1)), int(m.group(2)) return get_logical_dpi.ans def get_dpi(): if not hasattr(get_dpi, 'ans'): pdpi = glfw_get_physical_dpi() get_dpi.ans = {'physical': pdpi, 'logical': get_logical_dpi()} return get_dpi.ans # Color names {{{ color_pat = re.compile(r'^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6})$') color_pat2 = re.compile( r'rgb:([a-f0-9]{2})/([a-f0-9]{2})/([a-f0-9]{2})$', re.IGNORECASE ) color_names = { 'aliceblue': 'f0f8ff', 'antiquewhite': 'faebd7', 'aqua': '00ffff', 'aquamarine': '7fffd4', 'azure': 'f0ffff', 'beige': 'f5f5dc', 'bisque': 'ffe4c4', 'black': '000000', 'blanchedalmond': 'ffebcd', 'blue': '0000ff', 'blueviolet': '8a2be2', 'brown': 'a52a2a', 'burlywood': 'deb887', 'cadetblue': '5f9ea0', 'chartreuse': '7fff00', 'chocolate': 'd2691e', 'coral': 'ff7f50', 'cornflowerblue': '6495ed', 'cornsilk': 'fff8dc', 'crimson': 'dc143c', 'cyan': '00ffff', 'darkblue': '00008b', 'darkcyan': '008b8b', 'darkgoldenrod': 'b8860b', 'darkgray': 'a9a9a9', 'darkgrey': 'a9a9a9', 'darkgreen': '006400', 'darkkhaki': 'bdb76b', 'darkmagenta': '8b008b', 'darkolivegreen': '556b2f', 'darkorange': 'ff8c00', 'darkorchid': '9932cc', 'darkred': '8b0000', 'darksalmon': 'e9967a', 'darkseagreen': '8fbc8f', 'darkslateblue': '483d8b', 'darkslategray': '2f4f4f', 'darkslategrey': '2f4f4f', 'darkturquoise': '00ced1', 'darkviolet': '9400d3', 'deeppink': 'ff1493', 'deepskyblue': '00bfff', 'dimgray': '696969', 'dimgrey': '696969', 'dodgerblue': '1e90ff', 'firebrick': 'b22222', 'floralwhite': 'fffaf0', 'forestgreen': '228b22', 'fuchsia': 'ff00ff', 'gainsboro': 'dcdcdc', 'ghostwhite': 'f8f8ff', 'gold': 'ffd700', 'goldenrod': 'daa520', 'gray': '808080', 'grey': '808080', 'green': '008000', 'greenyellow': 'adff2f', 'honeydew': 'f0fff0', 'hotpink': 'ff69b4', 'indianred': 'cd5c5c', 'indigo': '4b0082', 'ivory': 'fffff0', 'khaki': 'f0e68c', 'lavender': 'e6e6fa', 'lavenderblush': 'fff0f5', 'lawngreen': '7cfc00', 'lemonchiffon': 'fffacd', 'lightblue': 'add8e6', 'lightcoral': 'f08080', 'lightcyan': 'e0ffff', 'lightgoldenrodyellow': 'fafad2', 'lightgray': 'd3d3d3', 'lightgrey': 'd3d3d3', 'lightgreen': '90ee90', 'lightpink': 'ffb6c1', 'lightsalmon': 'ffa07a', 'lightseagreen': '20b2aa', 'lightskyblue': '87cefa', 'lightslategray': '778899', 'lightslategrey': '778899', 'lightsteelblue': 'b0c4de', 'lightyellow': 'ffffe0', 'lime': '00ff00', 'limegreen': '32cd32', 'linen': 'faf0e6', 'magenta': 'ff00ff', 'maroon': '800000', 'mediumaquamarine': '66cdaa', 'mediumblue': '0000cd', 'mediumorchid': 'ba55d3', 'mediumpurple': '9370db', 'mediumseagreen': '3cb371', 'mediumslateblue': '7b68ee', 'mediumspringgreen': '00fa9a', 'mediumturquoise': '48d1cc', 'mediumvioletred': 'c71585', 'midnightblue': '191970', 'mintcream': 'f5fffa', 'mistyrose': 'ffe4e1', 'moccasin': 'ffe4b5', 'navajowhite': 'ffdead', 'navy': '000080', 'oldlace': 'fdf5e6', 'olive': '808000', 'olivedrab': '6b8e23', 'orange': 'ffa500', 'orangered': 'ff4500', 'orchid': 'da70d6', 'palegoldenrod': 'eee8aa', 'palegreen': '98fb98', 'paleturquoise': 'afeeee', 'palevioletred': 'db7093', 'papayawhip': 'ffefd5', 'peachpuff': 'ffdab9', 'per': 'cd853f', 'pink': 'ffc0cb', 'plum': 'dda0dd', 'powderblue': 'b0e0e6', 'purple': '800080', 'red': 'ff0000', 'rosybrown': 'bc8f8f', 'royalblue': '4169e1', 'saddlebrown': '8b4513', 'salmon': 'fa8072', 'sandybrown': 'f4a460', 'seagreen': '2e8b57', 'seashell': 'fff5ee', 'sienna': 'a0522d', 'silver': 'c0c0c0', 'skyblue': '87ceeb', 'slateblue': '6a5acd', 'slategray': '708090', 'slategrey': '708090', 'snow': 'fffafa', 'springgreen': '00ff7f', 'steelblue': '4682b4', 'tan': 'd2b48c', 'teal': '008080', 'thistle': 'd8bfd8', 'tomato': 'ff6347', 'turquoise': '40e0d0', 'violet': 'ee82ee', 'wheat': 'f5deb3', 'white': 'ffffff', 'whitesmoke': 'f5f5f5', 'yellow': 'ffff00', 'yellowgreen': '9acd32', } Color = namedtuple('Color', 'red green blue') # }}} def to_color(raw, validate=False): x = raw.strip().lower() m = color_pat.match(x) val = None if m is not None: val = m.group(1) if len(val) == 3: val = ''.join(2 * s for s in val) else: m = color_pat2.match(x) if m is not None: val = m.group(1) + m.group(2) + m.group(3) else: val = color_names.get(x) if val is None: if validate: raise ValueError('Invalid color name: {}'.format(raw)) return return Color(int(val[:2], 16), int(val[2:4], 16), int(val[4:], 16)) def color_as_int(val): return val[0] << 16 | val[1] << 8 | val[2] def parse_color_set(raw): parts = raw.split(';') for c, spec in [parts[i:i + 2] for i in range(0, len(parts), 2)]: try: c = int(c) if c < 0 or c > 255: raise IndexError('Out of bounds') r, g, b = to_color(spec) yield c, r << 16 | g << 8 | b except Exception: continue def pipe2(): try: read_fd, write_fd = os.pipe2(os.O_NONBLOCK | os.O_CLOEXEC) except AttributeError: import fcntl read_fd, write_fd = os.pipe() for fd in (read_fd, write_fd): flag = fcntl.fcntl(fd, fcntl.F_GETFD) fcntl.fcntl(fd, fcntl.F_SETFD, flag | fcntl.FD_CLOEXEC) flag = fcntl.fcntl(fd, fcntl.F_GETFL) fcntl.fcntl(fd, fcntl.F_SETFL, flag | os.O_NONBLOCK) return read_fd, write_fd def handle_unix_signals(): read_fd, write_fd = pipe2() for sig in (signal.SIGINT, signal.SIGTERM): signal.signal(sig, lambda x, y: None) signal.siginterrupt(sig, False) signal.set_wakeup_fd(write_fd) return read_fd def get_primary_selection(): if isosx: return '' # There is no primary selection on OS X # glfw has no way to get the primary selection # https://github.com/glfw/glfw/issues/894 return subprocess.check_output(['xsel', '-p']).decode('utf-8') def base64_encode( integer, chars=string.ascii_uppercase + string.ascii_lowercase + string.digits + '+/' ): ans = '' while True: integer, remainder = divmod(integer, 64) ans = chars[remainder] + ans if integer == 0: break return ans def set_primary_selection(text): if isosx: return # There is no primary selection on OS X if isinstance(text, str): text = text.encode('utf-8') p = subprocess.Popen(['xsel', '-i', '-p'], stdin=subprocess.PIPE) p.stdin.write(text), p.stdin.close() p.wait() def open_url(url, program='default'): if program == 'default': cmd = ['open'] if isosx else ['xdg-open'] else: cmd = shlex.split(program) cmd.append(url) subprocess.Popen(cmd).wait()