class Title(Widget): dispatcher = dependency(CommandDispatcher, 'commander') theme = dependency(Theme, 'theme') stretched = True def __zorro_di_done__(self): bar = self.theme.bar self.color = bar.text_color_pat self.font = bar.font self.padding = bar.text_padding self.dispatcher.events['window'].listen(self.window_changed) self.oldwin = None def window_changed(self): if self.oldwin is not None: self.oldwin.property_changed.unlisten(self.bar.redraw.emit) win = self.dispatcher.get('window', None) if win is not None: win.property_changed.listen(self.bar.redraw.emit) self.oldwin = win self.bar.redraw.emit() def draw(self, canvas, l, r): win = self.dispatcher.get('window', None) if not win: return r, r canvas.set_source(self.color) self.font.apply(canvas) canvas.move_to(l + self.padding.left, self.height - self.padding.bottom) canvas.show_text(get_title(win) or '') return r, r
class DashboardHTTP(HTTPService): jinja = dependency(Jinja, 'jinja') redis = dependency(Redis, 'redis') @public def default(self, uri): ident = uri[2:] leftcol = [] rightcol = [] data = self.redis.execute('GET', 'dashboard:{}:notepads'.format(ident)) if data: data = json.loads(data.decode('utf-8')) inject = di(self).inject for kind, uri in data: note = inject(Notepad.from_url(uri)) if kind == 'left': leftcol.append(note) elif kind == 'right': rightcol.append(note) else: raise NotImplementedError(kind) return (b'200 OK', b'Content-Type\0text/html; charset=utf-8\0', self.jinja.get_template('dashboard.html').render( title=uri_to_title(ident), left_column=leftcol, right_column=rightcol, ))
class State(object): commander = dependency(CommandDispatcher, 'commander') gman = dependency(GroupManager, 'group-manager') def __init__(self): self._state = None def dirty(self): return self._state != self._read() def update(self): nval = self._read() if nval != self._state: self._state = nval return True def _read(self): cur = self.commander.get('group') visgr = self.gman.current_groups.values() return tuple( GroupState(g.name, g.empty, g is cur, g in visgr, g.has_urgent_windows) for g in self.gman.groups) @property def groups(self): return self._state
class Icon(Widget): dispatcher = dependency(CommandDispatcher, 'commander') theme = dependency(Theme, 'theme') def __zorro_di_done__(self): self.padding = self.theme.bar.box_padding self.dispatcher.events['window'].listen(self.window_changed) self.oldwin = None def window_changed(self): if self.oldwin is not None: self.oldwin.property_changed.unlisten(self.bar.redraw.emit) win = self.dispatcher.get('window', None) if win is not None: win.property_changed.listen(self.bar.redraw.emit) self.oldwin = win self.bar.redraw.emit() def draw(self, canvas, l, r): win = self.dispatcher.get('window', None) if not win or not getattr(win, 'icons', None): return l, r h = self.height - self.padding.bottom - self.padding.top if self.right: x = r - self.padding.right - h else: x = l + self.padding.left win.draw_icon(canvas, x, self.padding.top, h) if self.right: return l, r - h - self.padding.left - self.padding.right else: return l + h + self.padding.left + self.padding.right, r
class EmulCommands(object): keyregistry = dependency(object, 'key-registry') # circ dependency commander = dependency(CommandDispatcher, 'commander') xcore = dependency(Core, 'xcore') def cmd_key(self, keystr): mod, sym = self.keyregistry.parse_key(keystr) code = self.xcore.keysym_to_keycode[sym][0] self.xcore.xtest.FakeInput( type=2, detail=code, time=0, root=self.xcore.root_window, rootX=0, rootY=0, deviceid=0, ) self.xcore.xtest.FakeInput( type=3, detail=code, time=100, root=self.xcore.root_window, rootX=0, rootY=0, deviceid=0, ) def cmd_button(self, num): num = int(num) self.xcore.xtest.FakeInput( type=4, detail=num, time=0, root=self.xcore.root_window, rootX=0, rootY=0, deviceid=0, ) self.xcore.xtest.FakeInput( type=5, detail=num, time=30, root=self.xcore.root_window, rootX=0, rootY=0, deviceid=0, )
class RenameWindow(Select): commander = dependency(CommandDispatcher, 'commander') def items(self): win = self._target_window titles = [ win.props.get("_NET_WM_VISIBLE_NAME"), win.props.get("_NET_WM_NAME"), win.props.get("WM_NAME"), win.props.get("WM_ICON_NAME"), win.props.get("WM_CLASS").replace('\0', ' '), win.props.get("WM_WINDOW_ROLE"), ] res = [] for t in titles: if not t: continue if res and res[-1][0] == t: continue res.append((t, t)) return res def submit(self, input, matched, value): self._target_window.set_property('_NET_WM_VISIBLE_NAME', input) def cmd_show(self): self._target_window = self.commander['window'] super().cmd_show() def _close(self): super()._close() if hasattr(self, '_target_window'): del self._target_window def cmd_clear_name(self): self.commander['window'].set_property('_NET_WM_VISIBLE_NAME', None)
class Notepad(object): redis = dependency(Redis, 'redis') def __init__(self, ident, title): self.ident = ident self.title = title self.records_key = ("notepad:{}:records".format(self.ident))\ .encode('ascii') self.topic = ("notepad:{}".format(self.ident)).encode('ascii') @classmethod def from_url(cls, url): return cls(url[1:], uri_to_title(url)) @classmethod def from_id(cls, ident): return Notepad(ident, None) @cached def records(self): # TODO(pc) cache recs = self.redis.execute("SORT", self.records_key, "BY", "nokey", "GET", "record:*") return [json.loads(rec.decode('utf-8')) for rec in recs] def new_record(self, title): title = title.strip() recid = self.redis.execute('INCR', 'record_counter') rec = { 'id': recid, 'title': title, } return rec
class ScreenManager(object): commander = dependency(CommandDispatcher, 'commander') def __init__(self, rectangles): self.screens = [] for i, rect in enumerate(rectangles): scr = Screen() scr.set_bounds(rect) self.screens.append(scr) def __zorro_di_done__(self): inj = di(self) for i, scr in enumerate(self.screens): inj.inject(scr) self.commander['screen.{}'.format(i)] = scr def update(self, screens): # TODO(tailhook) try to guess which one turned off while len(self.screens) > len(screens): scr = self.screens.pop() idx = len(self.screens) del self.commander['screen.{}'.format(idx)] for i, s in enumerate(self.screens): s.set_bounds(screens[i]) while len(self.screens) < len(screens): idx = len(self.screens) scr = Screen() scr.set_bounds(screens[idx]) self.screens.append(scr) self.commander['screen.{}'.format(idx)] = scr
class DefaultHandler(JSONWebsockInput): output = dependency(JSONWebsockOutput, 'websock_output') def handle_connect(self, cid): self.output.add_output(cid, '["notepad.', 'notepad') self.output.add_output(cid, '["dashboard.', 'dashboard')
class SelectLayout(Select): config = dependency(Config, 'config') def items(self): return sorted(self.config.all_layouts().items(), key=itemgetter(0)) def submit(self, input, matched, value): self.commander['group'].cmd_set_layout(matched)
class NotepadHTTP(HTTPService): jinja = dependency(Jinja, 'jinja') @public def default(self, uri): note = di(self).inject(Notepad.from_url(uri)) return (b'200 OK', b'Content-Type\0text/html; charset=utf-8\0', self.jinja.get_template('notepad.html').render(notepad=note))
class Tabs(GadgetBase): screens = dependency(ScreenManager, 'screen-manager') commander = dependency(CommandDispatcher, 'commander') def __init__(self, width=256, groups=()): self.bars = {} self.groups = set(groups) self.width = width self.states = {} def __zorro_di_done__(self): for s in self.screens.screens: bar = di(self).inject( LeftBar(s, self.width, self.groups, self.states)) self.bars[s] = bar if s.group.name in self.groups: s.slice_left(bar) def cmd_toggle(self): gr = self.commander['group'] if gr.name in self.groups: self.groups.remove(gr.name) else: self.groups.add(gr.name) self._update_bars() def cmd_show(self): gr = self.commander['group'] self.groups.add(gr.name) self._update_bars() def cmd_hide(self): if gr.name in self.groups: self.groups.remove(gr.name) self._update_bars() def _update_bars(self): for s, bar in self.bars.items(): if s.group.name in self.groups: bar.show() else: bar.hide()
class Ewmh(object): xcore = dependency(Core, 'xcore') dispatcher = dependency(object, 'event-dispatcher') def __zorro_di_done__(self): self.window = Window( self.xcore.create_toplevel(Rectangle(0, 0, 1, 1), klass=self.xcore.WindowClass.InputOnly, params={})) di(self).inject(self.window) self.xcore.raw.ChangeProperty( window=self.xcore.root_window, mode=self.xcore.PropMode.Replace, property=self.xcore.atom._NET_SUPPORTING_WM_CHECK, type=self.xcore.atom.WINDOW, format=32, data_len=1, data=struct.pack('<L', self.window)) self.window.set_property('_NET_SUPPORTING_WM_CHECK', self.window) self.window.set_property('_NET_WM_NAME', 'tilenol') def showing_window(self, win): self.xcore.raw.ChangeProperty(window=win, mode=self.xcore.PropMode.Replace, property=self.xcore.atom.WM_STATE, type=self.xcore.atom.CARD32, format=32, data_len=2, data=struct.pack('<LL', 1, 0)) def hiding_window(self, win): self.xcore.raw.ChangeProperty( window=win, mode=self.xcore.PropMode.Replace, property=self.xcore.atom.WM_STATE, type=self.xcore.atom.CARD32, format=32, data_len=2, data=struct.pack('<LL', 0, 0), _ignore_error=True) # window already destroyed
class FindWindow(Select): commander = dependency(CommandDispatcher, 'commander') def items(self): items = [] for g in self.commander['groups'].groups: for win in g.all_windows: t = (get_title(win) or win.props.get('WM_ICON_NAME') or win.props.get('WM_CLASS')) items.append((t, win)) return sorted(items, key=itemgetter(0)) def submit(self, input, matched, value): self.commander['groups'].cmd_switch(value.group.name)
class State(object): commander = dependency(CommandDispatcher, 'commander') def __init__(self, gr): self._state = None self._group = gr def dirty(self): return self._state != self._read() def update(self): nval = self._read() if nval != self._state: self._state = nval return True def _read(self): cur = self.commander.get('window') gr = self._group subs = list(gr.current_layout.sublayouts()) res = [] for sec in subs: wins = sec.windows if wins: title = getattr(sec, 'title', sec.__class__.__name__) sect = [title] for win in wins: sect.append(self._winstate(win, cur)) res.append(sect) if gr.floating_windows: sect = ['floating'] for win in gr.floating_windows: sect.append(self._winstate(win, cur)) return res def _winstate(self, win, cur): return WindowState( title=get_title(win) or win.props.get("WM_CLASS") or hex(win), icon=getattr(win, 'icons', None), active=win is cur, urgent=is_window_urgent(win), win=win, ) @property def sections(self): return self._state
class Clock(Widget): theme = dependency(Theme, 'theme') def __init__(self, *, format="%H:%M:%S %d.%m.%Y", right=False): super().__init__(right=right) self.format = format def __zorro_di_done__(self): bar = self.theme.bar self.font = bar.font self.color = bar.text_color_pat self.padding = bar.text_padding gethub().do_spawnhelper(self._update_time) def _update_time(self): while True: tts = 1.0 - (time.time() % 1.0) if tts < 0.001: tts = 1 sleep(tts) self.bar.redraw.emit() def _time(self): return datetime.datetime.now().strftime(self.format) def draw(self, canvas, l, r): self.font.apply(canvas) canvas.set_source(self.color) tm = self._time() _, _, w, h, _, _ = canvas.text_extents(tm) if self.right: x = r - self.padding.right - w r -= self.padding.left + self.padding.right + w else: x = l + self.padding.left l += self.padding.left + self.padding.right + w canvas.move_to(x, self.height - self.padding.bottom) canvas.show_text(tm) return l, r
class Split(Layout): """Split layout It's customized by subclassing, not by instantiating. Class definition should consist of at least one stack :var fixed: whether to skip empty stacks or reserve place for them """ fixed = False vertical = True commander = dependency(CommandDispatcher, 'commander') def __init__(self): super().__init__() self.auto_stacks = [] self.stack_list = [] self.stacks = {} for stack_class in self.get_defined_classes(BaseStack).values(): stack = stack_class(self) self.stacks[stack.__class__.__name__] = stack self.stack_list.append(stack) if stack.priority is not None: self.auto_stacks.append(stack) self.auto_stacks.sort(key=lambda s: s.priority) def set_bounds(self, bounds): self.bounds = bounds self.dirty() def _assign_boxes(self, box): if self.fixed: all_stacks = self.stack_list else: all_stacks = [s for s in self.stack_list if not s.empty] curw = 0 if self.vertical: rstart = start = box.x totalpx = box.width else: rstart = start = box.y totalpx = box.height totpx = sum((s.size or s.min_size) for s in all_stacks) totw = sum(s.weight for s in all_stacks if s.size is None) skip_pixels = totpx > totalpx or (not totw and totpx != totalpx) if skip_pixels: totw = sum(s.weight for s in all_stacks) else: totalpx -= sum(s.size for s in all_stacks if s.size is not None) pxoff = 0 for s in all_stacks: if s.size is not None and not skip_pixels: end = start + s.size pxoff += s.size else: curw += s.weight end = rstart + pxoff + int(floor(curw / totw * totalpx)) if self.vertical: s.box = Rectangle(start, box.y, end - start, box.height) else: s.box = Rectangle(box.x, start, box.width, end - start) start = end def add(self, win): # layout API if win.lprops.stack is not None: s = self.stacks.get(win.lprops.stack) if s is not None and not s.full: s.add(win) return True for s in self.auto_stacks: if not s.full: s.add(win) return True return False # no empty stacks, reject it, so it will be floating def remove(self, win): # layout API self.stacks[win.lprops.stack].remove(win) def sublayouts(self): # layout focus API return self.stacks.values() def layout(self): self._assign_boxes(self.bounds) for s in self.stack_list: s.layout() def swap_window(self, source, target, win): if target.full: other = target.windows[0] target.remove(other) source.remove(win) target.add(win) source.add(other) else: source.remove(win) target.add(win) @stackcommand def cmd_up(self, stack, win): if self.vertical: stack.shift_up() else: idx = self.stack_list.index(stack) if idx > 0: self.swap_window(stack, self.stack_list[idx - 1], win) @stackcommand def cmd_down(self, stack, win): if self.vertical: stack.shift_down() else: idx = self.stack_list.index(stack) if idx < len(self.stacks) - 1: self.swap_window(stack, self.stack_list[idx + 1], win) @stackcommand def cmd_left(self, stack, win): if not self.vertical: stack.shift_up() else: idx = self.stack_list.index(stack) if idx > 0: self.swap_window(stack, self.stack_list[idx - 1], win) @stackcommand def cmd_right(self, stack, win): if not self.vertical: stack.shift_down() else: idx = self.stack_list.index(stack) if idx < len(self.stacks) - 1: self.swap_window(stack, self.stack_list[idx + 1], win)
class Systray(Widget): xcore = dependency(Core, 'xcore') dispatcher = dependency(EventDispatcher, 'event-dispatcher') theme = dependency(Theme, 'theme') def __init__(self, *, right=False): super().__init__(right=right) self.icons = [] def __zorro_di_done__(self): self.padding = self.theme.bar.box_padding self.spacing = self.theme.bar.icon_spacing self.create_window() def create_window(self): self.window = di(self).inject( ClientMessageWindow( self.xcore.create_toplevel( Rectangle(0, 0, 1, 1), klass=self.xcore.WindowClass.InputOnly, params={}), self.systray_message)) self.window.show() self.dispatcher.register_window(self.window) self.xcore.raw.SetSelectionOwner( owner=self.window.wid, selection=self.xcore.atom._NET_SYSTEM_TRAY_S0, time=0, ) self.xcore.send_event('ClientMessage', self.xcore.EventMask.StructureNotify, self.xcore.root_window, window=self.xcore.root_window, type=self.xcore.atom.MANAGER, format=32, data=struct.pack( '<LLL', 0, self.xcore.atom._NET_SYSTEM_TRAY_S0, self.window.wid, )) def systray_message(self, msg): assert msg.type == self.xcore.atom._NET_SYSTEM_TRAY_OPCODE tm, op, wid, _, _ = struct.unpack('<LLLLL', msg.data) if op == 0: # REQUEST_DOCK win = self.dispatcher.windows[wid] win.__class__ = TrayIcon win.systray = self win.reparent_to(self.bar.window) self.xcore.raw.ChangeWindowAttributes( window=win, params={ self.xcore.CW.BackPixel: self.theme.bar.background, self.xcore.CW.EventMask: self.xcore.EventMask.ResizeRedirect, }) self.icons.append(win) win.show() self.bar.redraw.emit() elif op == 1: # BEGIN_MESSAGE pass elif op == 2: # CANCEL_MESSAGE pass def remove(self, icon): self.icons.remove(icon) self.bar.redraw.emit() def draw(self, canvas, l, r): l = int(l) r = int(r) t = self.padding.top h = self.bar.height - self.padding.bottom - t if self.right: r -= self.padding.right for i in reversed(self.icons): i.set_bounds(Rectangle(r - h, t, h, h)) r -= h + self.spacing r += self.spacing r -= self.padding.left else: l += self.padding.left for i in self.icons: i.set_bounds(Rectangle(l, t, h, h)) l += h + self.spacing l -= self.spacing l += self.padding.right return l, r
class Gesture(Widget): theme = dependency(Theme, 'theme') gestures = dependency(G.Gestures, 'gestures') def __init__(self, *, gestures=None, right=False): super().__init__(right=right) self.format = format self.gesture_names = gestures self.state = (None, None, None, None) def __zorro_di_done__(self): bar = self.theme.bar self.font = bar.font self.color = bar.text_color_pat self.background = bar.background_pat self.dig = bar.bright_color_pat self.inactive_color = bar.dim_color_pat self.padding = bar.text_padding self.gwidth = self.height - self.padding.top - self.padding.bottom if self.gesture_names is None: for name in self.gestures.active_gestures: self.gestures.add_callback(name, self._update_gesture) else: for name in self.gesture_names: self.gestures.add_callback(name, self._update_gesture) def _update_gesture(self, name, percent, state, cfg): px = min(int(percent * self.gwidth), self.gwidth) st = (name, px, state, cfg) if self.state != st: if state in {G.CANCEL, G.COMMIT}: self.state = (None, None, None, None) else: self.state = st self.bar.redraw.emit() def draw(self, canvas, l, r): name, offset, state, cfg = self.state if not name: return l, r fin, dir = name.split('-') nfin = int(fin[:-1]) char = cfg['char'] self.font.apply(canvas) if state == G.FULL: canvas.set_source(self.color) else: canvas.set_source(self.inactive_color) _, _, w, h, _, _ = canvas.text_extents(char) if self.right: x = r - self.padding.right - w r -= self.padding.left + self.padding.right + w else: x = l + self.padding.left l += self.padding.left + self.padding.right + w cx = x + w / 2 cy = self.padding.top + self.gwidth / 2 canvas.translate(cx + 1, cy) canvas.rotate(rotations[dir]) canvas.translate(-cx, -cy) canvas.translate(0, self.gwidth / 2 - offset) canvas.move_to(x, self.height - self.padding.bottom) canvas.show_text(char) return l, r
class EventDispatcher(object): keys = dependency(KeyRegistry, 'key-registry') mouse = dependency(MouseRegistry, 'mouse-registry') xcore = dependency(Core, 'xcore') groupman = dependency(GroupManager, 'group-manager') screenman = dependency(ScreenManager, 'screen-manager') classifier = dependency(Classifier, 'classifier') config = dependency(Config, 'config') def __init__(self): self.windows = {} self.frames = {} self.all_windows = {} self.active_field = None self.mapping_notify = Event('mapping_notify') self.mapping_notify.listen(self._mapping_notify_delayed) def dispatch(self, ev): meth = getattr(self, 'handle_'+ev.__class__.__name__, None) if meth: meth(ev) else: log.warning("Unknown event ``%r''", ev) def register_window(self, win): self.all_windows[win.wid] = win def handle_KeyPressEvent(self, ev): if not self.keys.dispatch_event(ev): if self.active_field: self.active_field.handle_keypress(ev) def handle_KeyReleaseEvent(self, ev): pass # nothing to do at the moment def handle_ButtonPressEvent(self, ev): self.mouse.dispatch_button_press(ev) def handle_ButtonReleaseEvent(self, ev): self.mouse.dispatch_button_release(ev) def handle_MotionNotifyEvent(self, ev): self.mouse.dispatch_motion(ev) def handle_MapRequestEvent(self, ev): try: win = self.windows[ev.window] except KeyError: log.warning("Configure request for non-existent window %r", ev.window) else: win.want.visible = True if win.frame is None: frm = win.create_frame() self.frames[frm.wid] = frm self.all_windows[frm.wid] = frm win.reparent_frame() if not hasattr(win, 'group'): self.classifier.apply(win) self.groupman.add_window(win) def handle_EnterNotifyEvent(self, ev): if self.mouse.drag: return try: win = self.frames[ev.event] except KeyError: log.warning("Enter notify for non-existent window %r", ev.event) else: if ev.mode != self.xcore.NotifyMode.Grab: if hasattr(win, 'pointer_enter'): win.pointer_enter() if self.active_field: return if(win.props.get("WM_HINTS") is None or win.props.get('WM_HINTS')[0] & 1): win.focus() def handle_LeaveNotifyEvent(self, ev): if self.mouse.drag: return try: win = self.frames[ev.event] except KeyError: log.warning("Leave notify for non-existent window %r", ev.event) else: if ev.mode != self.xcore.NotifyMode.Grab: if hasattr(win, 'pointer_leave'): win.pointer_leave() def handle_MapNotifyEvent(self, ev): try: win = self.all_windows[ev.window] except KeyError: log.warning("Map notify for non-existent window %r", ev.window) else: if hasattr(win, 'group') and win.group.visible: win.real.visible = True if win.frame: win.frame.show() def handle_UnmapNotifyEvent(self, ev): if ev.event not in self.frames: return # do not need to track unmapping of unmanaged windows try: win = self.windows[ev.window] except KeyError: log.warning("Unmap notify for non-existent window %r", ev.window) else: win.real.visible = False win.done.visible = False if win.frame: win.ewmh.hiding_window(win) win.frame.hide() # According to the docs here should be reparenting of windows # to the root window, but that doesn't work well if hasattr(win, 'group'): win.group.remove_window(win) def handle_FocusInEvent(self, ev): if(ev.event == self.xcore.root_window and ev.mode not in (self.xcore.NotifyMode.Grab, self.xcore.NotifyMode.Ungrab) and ev.detail == getattr(self.xcore.NotifyDetail, 'None')): self.xcore.raw.SetInputFocus( focus=self.xcore.root_window, revert_to=self.xcore.InputFocus.PointerRoot, time=self.xcore.last_time, ) return try: win = self.all_windows[ev.event] except KeyError: log.warning("Focus request for non-existent window %r", ev.event) else: if(ev.mode not in (self.xcore.NotifyMode.Grab, self.xcore.NotifyMode.Ungrab) and ev.detail != self.xcore.NotifyDetail.Pointer): win.focus_in() def handle_FocusOutEvent(self, ev): try: win = self.all_windows[ev.event] except KeyError: log.warning("Focus request for non-existent window %r", ev.event) else: if(ev.mode not in (self.xcore.NotifyMode.Grab, self.xcore.NotifyMode.Ungrab) and ev.detail != self.xcore.NotifyDetail.Pointer): win.focus_out() def handle_CreateNotifyEvent(self, ev): win = di(self).inject(Window.from_notify(ev)) if win.wid in self.windows: log.warning("Create notify for already existent window %r", win.wid) # TODO(tailhook) clean up old window if win.wid in self.all_windows: return win.done.size = win.want.size self.xcore.raw.ChangeWindowAttributes(window=win, params={ self.xcore.CW.EventMask: self.xcore.EventMask.PropertyChange }) self.windows[win.wid] = win self.all_windows[win.wid] = win try: for name in self.xcore.raw.ListProperties(window=win)['atoms']: win.update_property(name) except XError: log.warning("Window destroyed immediately %d", win.wid) def handle_ConfigureNotifyEvent(self, ev): pass def handle_ReparentNotifyEvent(self, ev): pass def handle_DestroyNotifyEvent(self, ev): try: win = self.all_windows.pop(ev.window) except KeyError: log.warning("Destroy notify for non-existent window %r", ev.window) else: self.windows.pop(win.wid, None) self.frames.pop(win.wid, None) if hasattr(win, 'group'): win.group.remove_window(win) win.destroyed() def handle_ConfigureRequestEvent(self, ev): try: win = self.windows[ev.window] except KeyError: log.warning("Configure request for non-existent window %r", ev.window) else: win.update_size_request(ev) def handle_PropertyNotifyEvent(self, ev): try: win = self.windows[ev.window] except KeyError: log.warning("Property notify event for non-existent window %r", ev.window) else: win.update_property(ev.atom) def handle_ExposeEvent(self, ev): try: win = self.all_windows[ev.window] except KeyError: log.warning("Expose event for non-existent window %r", ev.window) else: win.expose(Rectangle(ev.x, ev.y, ev.width, ev.height)) def handle_ClientMessageEvent(self, ev): type = self.xcore.atom[ev.type] # import struct # print("ClientMessage", ev, repr(type), struct.unpack('<5L', ev.data)) win = self.all_windows[ev.window] if hasattr(win, 'client_message'): win.client_message(ev) else: log.warning("Unhandled client message %r %r %r", ev, type, struct.unpack('<5L', ev.data)) def handle_ScreenChangeNotifyEvent(self, ev): # We only poll for events and use Xinerama for screen querying # because some drivers (nvidia) doesn't provide xrandr data # correctly if self.config['auto-screen-configuration']: if randr.check_screens(self.xcore): randr.configure_outputs(self.xcore, self.config['screen-dpi']/25.4) info = self.xcore.xinerama.QueryScreens()['screen_info'] self.screenman.update(list( Rectangle(scr['x_org'], scr['y_org'], scr['width'], scr['height']) for scr in info)) self.groupman.check_screens() def handle_NotifyEvent(self, ev): # Xrandr events are reported here log.warning("Notify event %r", ev) def handle_MappingNotifyEvent(self, ev): self.mapping_notify.emit() def _mapping_notify_delayed(self): self.keys.reconfigure_keys()
class DashboardWebsock(JSONWebsockInput): redis = dependency(Redis, 'redis') @public_with_connection_id def subscribe(self, cid, name): self.output.subscribe(cid, 'dashboard:' + name) return 'ok' @public_with_connection_id def insert_before(self, cid, dash, hint, uri): data = self.redis.execute('GET', 'dashboard:{}:notepads'.format(dash)) if data: data = json.loads(data.decode('utf-8')) else: data = [] for idx, (kind, nuri) in enumerate(data): if nuri[1:] == hint: data.insert(idx, (kind, uri)) break self.redis.execute('SET', 'dashboard:{}:notepads'.format(dash), json.dumps(data)) np = di(self).inject(Notepad.from_url(uri)) self.output.subscribe(cid, np.topic) self.output.publish('dashboard:' + dash, [ 'dashboard.insert_before', dash, hint, { 'ident': np.ident, 'title': uri_to_title(uri), 'records': np.records, } ]) return 'ok' @public_with_connection_id def insert_after(self, cid, dash, hint, uri): data = self.redis.execute('GET', 'dashboard:{}:notepads'.format(dash)) if data: data = json.loads(data.decode('utf-8')) else: data = [] for idx, (kind, nuri) in enumerate(data): if nuri[1:] == hint: data.insert(idx + 1, (kind, uri)) break self.redis.execute('SET', 'dashboard:{}:notepads'.format(dash), json.dumps(data)) np = di(self).inject(Notepad.from_url(uri)) self.output.subscribe(cid, np.topic) self.output.publish('dashboard:' + dash, [ 'dashboard.insert_after', dash, hint, { 'ident': np.ident, 'title': uri_to_title(uri), 'records': np.records, } ]) return 'ok' @public_with_connection_id def append_left(self, cid, dash, uri): data = self.redis.execute('GET', 'dashboard:{}:notepads'.format(dash)) if data: data = json.loads(data.decode('utf-8')) else: data = [] data.append(('left', uri)) self.redis.execute('SET', 'dashboard:{}:notepads'.format(dash), json.dumps(data)) np = di(self).inject(Notepad.from_url(uri)) self.output.subscribe(cid, np.topic) self.output.publish('dashboard:' + dash, [ 'dashboard.append_left', dash, { 'ident': np.ident, 'title': uri_to_title(uri), 'records': np.records, } ]) return 'ok' @public_with_connection_id def append_right(self, cid, dash, uri): data = self.redis.execute('GET', 'dashboard:{}:notepads'.format(dash)) if data: data = json.loads(data.decode('utf-8')) else: data = [] for idx, (kind, nuri) in data: if nuri == hint: data.insert(idx + 1, (kind, uri)) break self.redis.execute('SET', 'dashboard:{}:notepads'.format(dash), json.dumps(data)) data.append(('right', uri)) np = di(self).inject(Notepad.from_url(uri)) self.output.subscribe(cid, np.topic) self.output.publish('dashboard:' + dash, [ 'dashboard.append_left', dash, { 'ident': np.ident, 'title': uri_to_title(uri), 'records': np.records, } ]) return 'ok'
class Tilenol(object): xcore = dependency(Core, 'xcore') dispatcher = dependency(EventDispatcher, 'event-dispatcher') config = dependency(Config, 'config') commander = dependency(CommandDispatcher, 'commander') def __init__(self, options): pass # extract options needed def register_gadgets(self): inj = di(self) for name, inst in self.config.gadgets(): inj.inject(inst) self.commander[name] = inst def run(self): signal.signal(signal.SIGCHLD, child_handler) signal.signal(signal.SIGQUIT, quit_handler) proto = Proto() proto.load_xml('xproto') proto.load_xml('xtest') proto.load_xml('xinerama') proto.load_xml('shm') proto.load_xml('randr') self.conn = conn = Connection(proto) conn.connection() self.root_window = Root(conn.init_data['roots'][0]['root']) inj = DependencyInjector() inj['dns'] = dns.Resolver(dns.Config.system_config()) gethub().dns_resolver = inj['dns'] inj['xcore'] = xcore = Core(conn) inj['keysyms'] = keysyms = Keysyms() keysyms.load_default() cfg = inj['config'] = inj.inject(Config()) cfg.init_extensions() # Hack, but this only makes GetScreenInfo work xcore.randr._proto.requests['GetScreenInfo'].reply.items['rates'].code\ = compile('0', 'XPROTO', 'eval') if cfg['auto-screen-configuration']: if randr.check_screens(xcore): randr.configure_outputs(xcore, cfg['screen-dpi'] / 25.4) inj['theme'] = inj.inject(cfg.theme()) inj['commander'] = cmd = inj.inject(CommandDispatcher()) if hasattr(xcore, 'randr'): NM = xcore.randr.NotifyMask # We only poll for events and use Xinerama for screen querying # because some drivers (nvidia) doesn't provide xrandr data # correctly xcore.randr.SelectInput(window=xcore.root_window, enable=NM.ScreenChange | NM.CrtcChange | NM.OutputChange | NM.OutputProperty) if hasattr(xcore, 'xinerama'): info = xcore.xinerama.QueryScreens()['screen_info'] screenman = inj['screen-manager'] = ScreenManager([ Rectangle(scr['x_org'], scr['y_org'], scr['width'], scr['height']) for scr in info ]) else: screenman = inj['screen-manager'] = ScreenManager([ Rectangle(0, 0, xcore.root['width_in_pixels'], xcore.root['height_in_pixels']) ]) inj.inject(screenman) cmd['tilenol'] = self keys = KeyRegistry() inj['key-registry'] = inj.inject(keys) mouse = MouseRegistry() inj['mouse-registry'] = inj.inject(mouse) inj['gestures'] = inj.inject(Gestures()) gman = inj.inject(GroupManager(map(inj.inject, cfg.groups()))) cmd['groups'] = gman inj['group-manager'] = gman rules = inj['classifier'] = inj.inject(Classifier()) for cls, cond, act in cfg.rules(): rules.add_rule(cond, act, klass=cls) eman = inj.inject(EventDispatcher()) eman.all_windows[self.root_window.wid] = self.root_window inj['event-dispatcher'] = eman inj['ewmh'] = Ewmh() inj.inject(inj['ewmh']) inj.inject(self) cmd['env'] = EnvCommands() cmd['emul'] = inj.inject(EmulCommands()) # Register hotkeys as mapping notify can be skipped on inplace restart keys.configure_hotkeys() mouse.init_buttons() mouse.register_buttons(self.root_window) self.setup_events() for screen_no, bar in cfg.bars(): inj.inject(bar) if screen_no < len(screenman.screens): scr = screenman.screens[screen_no] if bar.position == 'bottom': scr.add_bottom_bar(bar) else: scr.add_top_bar(bar) bar.create_window() scr.updated.listen(bar.redraw.emit) self.register_gadgets() self.catch_windows() self.loop() def catch_windows(self): cnotify = self.xcore.proto.events['CreateNotify'].type mnotify = self.xcore.proto.events['MapRequest'].type for w in self.xcore.raw.QueryTree(window=self.root_window)['children']: if w == self.root_window or w in self.dispatcher.all_windows: continue try: attr = self.xcore.raw.GetWindowAttributes(window=w) except XError: # TODO(pc) check for error code continue if attr['class'] == self.xcore.WindowClass.InputOnly: continue geom = self.xcore.raw.GetGeometry(drawable=w) self.dispatcher.handle_CreateNotifyEvent( cnotify( 0, window=w, parent=self.root_window.wid, x=geom['x'], y=geom['y'], width=geom['width'], height=geom['height'], border_width=geom['border_width'], override_redirect=attr['override_redirect'], )) win = self.dispatcher.windows[w] if (attr['map_state'] != self.xcore.MapState.Unmapped and not attr['override_redirect']): self.dispatcher.handle_MapRequestEvent( mnotify( 0, parent=self.root_window.wid, window=w, )) def setup_events(self): EM = self.xcore.EventMask self.xcore.raw.ChangeWindowAttributes(window=self.root_window, params={ self.xcore.CW.EventMask: EM.StructureNotify | EM.SubstructureNotify | EM.SubstructureRedirect | EM.FocusChange }) attr = self.xcore.raw.GetWindowAttributes(window=self.root_window) if not (attr['your_event_mask'] & EM.SubstructureRedirect): print("Probably another window manager is running", file=sys.stderr) return def loop(self): for i in self.xcore.get_events(): try: self.dispatcher.dispatch(i) except Exception: log.exception("Error handling event %r", i) def cmd_restart(self): inplace_restart()
class Frame(Window): commander = dependency(CommandDispatcher, 'commander') theme = dependency(Theme, 'theme') def __init__(self, wid, content): super().__init__(wid) self.content = content def __zorro_di_done__(self): self.border_width = self.theme.window.border_width def focus(self): self.done.focus = True if self.content.lprops.floating: self.restack(self.xcore.StackMode.TopIf) self.content.focus() def focus_out(self): self.done.focus = False self.real.focus = False self.content.done.focus = False self.content.real.focus = False win = self.commander.pop('window', None) assert win in (self.content, None) self.xcore.raw.ChangeWindowAttributes( window=self, params={ self.xcore.CW.BorderPixel: self.theme.window.inactive_border, }, _ignore_error=True) def focus_in(self): self.real.focus = True self.content.real.focus = True assert self.commander.get('window') in (self.content, None) if not hasattr(self.content, 'group'): return self.commander['window'] = self.content self.commander['group'] = self.content.group self.commander['layout'] = self.content.group.current_layout self.commander['screen'] = self.content.group.screen self.xcore.raw.ChangeWindowAttributes( window=self, params={ self.xcore.CW.BorderPixel: self.theme.window.active_border, }, _ignore_error=True) def pointer_enter(self): self.commander['pointer_window'] = self.content def pointer_leave(self): if self.commander.get('pointer_window') == self.content: del self.commander['pointer_window'] def hide(self): if self.commander.get('window') == self.content: del self.commander['window'] super().hide() def destroyed(self): if self.commander.get('window') is self.content: del self.commander['window'] def configure_content(self, rect): hints = self.content.want.hints x = 0 y = 0 rw = rect.width - self.border_width * 2 rh = rect.height - self.border_width * 2 if hints and not self.content.ignore_hints: width, height = self._apply_hints(rw, rh, hints) if width < rw: x = rw // 2 - width // 2 if height < rh: y = rh // 2 - height // 2 # TODO(tailhook) obey gravity else: width = rw height = rh self.content.done.size = Rectangle(x, y, width, height) self.xcore.raw.ConfigureWindow(window=self.content, params={ self.xcore.ConfigWindow.X: x, self.xcore.ConfigWindow.Y: y, self.xcore.ConfigWindow.Width: width, self.xcore.ConfigWindow.Height: height, }) def _apply_hints(self, width, height, hints): if hasattr(hints, 'width_inc'): incr = hints.width_inc base = getattr(hints, 'base_width', getattr(hints, 'min_width', None)) n = (width - base) // incr width = base + n * incr if hasattr(hints, 'max_width') and width > hints.max_width: width = hints.max_width if hasattr(hints, 'height_inc'): incr = hints.height_inc base = getattr(hints, 'base_height', getattr(hints, 'min_height', None)) n = (height - base) // incr height = base + n * incr if hasattr(hints, 'max_height') and height > hints.max_height: height = hints.max_height # TODO(tailhook) obey aspect ratio return width, height def set_bounds(self, rect, force=False): if not super().set_bounds(rect, force=force): return False self.configure_content(rect) return True def show(self): super().show() if self.done.size: self.configure_content(self.done.size) def add_hint(self): res = di(self).inject( HintWindow( self.xcore.create_window( Rectangle(0, 0, 1, 1), klass=self.xcore.WindowClass.InputOutput, parent=self, params={ self.xcore.CW.BackPixel: self.theme.hint.background, self.xcore.CW.BorderPixel: self.theme.hint.border_color, self.xcore.CW.OverrideRedirect: True, self.xcore.CW.EventMask: self.xcore.EventMask.SubstructureRedirect | self.xcore.EventMask.SubstructureNotify | self.xcore.EventMask.EnterWindow | self.xcore.EventMask.LeaveWindow | self.xcore.EventMask.FocusChange }), self)) res.set_border(self.theme.hint.border_width) res.show() return res def toggle_border(self): if self.border_width == 0: self.set_border(self.theme.window.border_width) else: self.set_border(0)
class Groupbox(Widget): theme = dependency(Theme, 'theme') def __init__(self, *, filled=False, first_letter=False, right=False): super().__init__(right=right) self.filled = filled self.first_letter = first_letter def __zorro_di_done__(self): self.state = di(self).inject(State()) bar = self.theme.bar self.font = bar.font self.inactive_color = bar.dim_color_pat self.urgent_color = bar.bright_color_pat self.active_color = bar.text_color_pat self.selected_color = bar.active_border_pat self.subactive_color = bar.subactive_border_pat self.padding = bar.text_padding self.border_width = bar.border_width self.state.gman.group_changed.listen(self.bar.redraw.emit) Window.any_window_changed.listen(self.check_state) def check_state(self): if self.state.dirty: self.bar.redraw.emit() def draw(self, canvas, l, r): self.state.update() assert not self.right, "Sorry, right not implemented" self.font.apply(canvas) canvas.set_line_join(LINE_JOIN_ROUND) canvas.set_line_width(self.border_width) x = l between = self.padding.right + self.padding.left for gs in self.state.groups: gname = gs.name if self.first_letter: gname = gname[0] sx, sy, w, h, ax, ay = canvas.text_extents(gname) if gs.active: canvas.set_source(self.selected_color) if self.filled: canvas.rectangle(x, 0, ax + between, self.height) canvas.fill() else: canvas.rectangle(x + 2, 2, ax + between - 4, self.height - 4) canvas.stroke() elif gs.visible: canvas.set_source(self.subactive_color) if self.filled: canvas.rectangle(x, 0, ax + between, self.height) canvas.fill() else: canvas.rectangle(x + 2, 2, ax + between - 4, self.height - 4) canvas.stroke() if gs.urgent: canvas.set_source(self.urgent_color) elif gs.empty: canvas.set_source(self.inactive_color) else: canvas.set_source(self.active_color) canvas.move_to(x + self.padding.left, self.height - self.padding.bottom) canvas.show_text(gname) x += ax + between return x, r
class HintWindow(Window): cairo = None theme = dependency(Theme, 'theme') def __init__(self, wid, parent): super().__init__(wid) self.parent = parent self.redraw_ev = Event('hint.redraw') self.redraw_ev.listen(self.do_redraw) self.show_ev = Event('hint.show') self.show_ev.listen(self.do_show) def __zorro_di_done__(self): self.sizer = cairo.Context( cairo.ImageSurface(cairo.FORMAT_ARGB32, 1, 1)) sx, sy, w, h, ax, ay = self.sizer.text_extents('1') self.line_height = int(h - sy) self.font = self.theme.hint.font self.font.apply(self.sizer) self.padding = self.theme.hint.padding self.color = self.theme.hint.text_color_pat self.background = self.theme.hint.background_pat self._gc = self.xcore._conn.new_xid() # TODO(tialhook) private api? self.xcore.raw.CreateGC( cid=self._gc, drawable=self.xcore.root_window, params={}, ) def set_text(self, text): self.text = text lines = text.split('\n') w = 0 h = 0 for line in lines: sx, sy, tw, th, ax, ay = self.sizer.text_extents(line) w = max(w, tw) h += th w += self.padding.left + self.padding.right h += self.padding.top + self.padding.bottom w = int(w) h = int(h) need_resize = (self.cairo is None or w != self.cairo.get_target().get_width() and h != self.cairo.get_target().get_height) if need_resize: self.cairo = cairo.Context( cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)) self.font.apply(self.cairo) psz = self.parent.done.size self.set_bounds( Rectangle((psz.width - w) // 2 - self.border_width, (psz.height - h) // 2 - self.border_width, w, h)) self.redraw_ev.emit() def do_redraw(self): tgt = self.cairo.get_target() w = tgt.get_width() h = tgt.get_height() self.cairo.set_source(self.background) self.cairo.rectangle(0, 0, w, h) self.cairo.fill() self.cairo.set_source(self.color) y = self.padding.top + self.line_height for line in self.text.split('\n'): sx, sy, tw, th, ax, ay = self.sizer.text_extents(line) self.cairo.move_to((w - tw) // 2, y) self.cairo.show_text(line) y += th self.show_ev.emit() def expose(self, rect): self.show_ev.emit() def do_show(self): tgt = self.cairo.get_target() w = tgt.get_width() h = tgt.get_height() self.xcore.raw.PutImage( format=self.xcore.ImageFormat.ZPixmap, drawable=self, gc=self._gc, width=w, height=h, dst_x=0, dst_y=0, left_pad=0, depth=24, data=bytes(tgt), )
class Select(GadgetBase): commander = dependency(CommandDispatcher, 'commander') dispatcher = dependency(EventDispatcher, 'event-dispatcher') def __init__(self, max_lines=10): self.window = None self.max_lines = max_lines self.redraw = Event('menu.redraw') self.redraw.listen(self._redraw) self.submit_ev = Event('menu.submit') self.submit_ev.listen(self._submit) self.complete = Event('menu.complete') self.complete.listen(self._complete) self.close = Event('menu.close') self.close.listen(self._close) def __zorro_di_done__(self): self.line_height = self.theme.menu.line_height def cmd_show(self): if self.window: self.cmd_hide() self._current_items = self.items() show_lines = min(len(self._current_items) + 1, self.max_lines) h = self.theme.menu.line_height self.height = h * self.max_lines bounds = self.commander['screen'].bounds._replace(height=h) self._img = self.xcore.pixbuf(bounds.width, h) wid = self.xcore.create_toplevel( bounds, klass=self.xcore.WindowClass.InputOutput, params={ self.xcore.CW.BackPixel: self.theme.menu.background, self.xcore.CW.OverrideRedirect: True, self.xcore.CW.EventMask: self.xcore.EventMask.FocusChange | self.xcore.EventMask.EnterWindow | self.xcore.EventMask.LeaveWindow | self.xcore.EventMask.KeymapState | self.xcore.EventMask.KeyPress, }) self.window = di(self).inject( DisplayWindow(wid, self.draw, focus_out=self._close)) self.dispatcher.all_windows[wid] = self.window self.dispatcher.frames[wid] = self.window # dirty hack self.window.show() self.window.focus() self.text_field = di(self).inject( TextField(self.theme.menu, events={ 'draw': self.redraw, 'submit': self.submit_ev, 'complete': self.complete, 'close': self.close, })) self.dispatcher.active_field = self.text_field self._redraw() def cmd_hide(self): self._close() def draw(self, rect=None): self._img.draw(self.window) def match_lines(self, value): matched = set() for line, res in self._current_items: if line in matched: continue if line.startswith(value): matched.add(line) yield (line, [(1, line[:len(value)]), (0, line[len(value):])], res) ncval = value.lower() for line, res in self._current_items: if line in matched: continue if line.lower().startswith(value): matched.add(line) yield (line, [(1, line[:len(value)]), (0, line[len(value):])], res) for line, res in self._current_items: if line in matched: continue if ncval in line.lower(): matched.add(line) opcodes = [] for pt in re.compile('((?i)' + re.escape(value) + ')').split(line): opcodes.append((pt.lower() == ncval, pt)) yield (line, opcodes, res) def _redraw(self): if not self.window and not self.text_field: return lines = list( islice(self.match_lines(self.text_field.value), self.max_lines)) newh = (len(lines) + 1) * self.line_height if newh != self.height: # don't need to render, need resize self.height = newh bounds = self.commander['screen'].bounds._replace(height=newh) self._img = self.xcore.pixbuf(bounds.width, newh) self.window.set_bounds(bounds) ctx = self._img.context() ctx.set_source(self.theme.menu.background_pat) ctx.rectangle(0, 0, self._img.width, self._img.height) ctx.fill() sx, sy, _, _, ax, ay = ctx.text_extents(self.text_field.value) self.text_field.draw(ctx) th = self.theme.menu pad = th.padding y = self.line_height for text, opcodes, value in lines: ctx.move_to(pad.left, y + self.line_height - pad.bottom) for op, tx in opcodes: ctx.set_source(th.highlight_pat if op else th.text_pat) ctx.show_text(tx) y += self.line_height self.draw() def _submit(self): input = self.text_field.value matched = None value = None for matched, opcodes, value in self.match_lines(input): break self.submit(input, matched, value) self._close() def _close(self): if self.window: self.window.destroy() self.window = None if self.dispatcher.active_field == self.text_field: self.dispatcher.active_field = None self.text_field = None def _complete(self): text, _, val = next(iter(self.match_lines(self.text_field.value))) self.text_field.value = text self.text_field.sel_start = len(text) self.text_field.sel_width = 0 self.redraw.emit()
class KeyRegistry(object): keysyms = dependency(Keysyms, 'keysyms') xcore = dependency(Core, 'xcore') config = dependency(Config, 'config') commander = dependency(CommandDispatcher, 'commander') def __init__(self): self.keys = {} def configure_hotkeys(self): self.xcore.init_keymap() for key, cmd in self.config.keys(): self.add_key(key, self.commander.callback(*cmd)) self.register_keys(self.xcore.root_window) def reconfigure_keys(self): if self.keys: self.unregister_keys(self.xcore.root_window) self.keys = {} self.configure_hotkeys() def parse_key(self, keystr): mod = 0 if keystr[0] == '<': keystr = keystr[1:-1] if '-' in keystr: mstr, sym = keystr.split('-') if 'S' in mstr: mod |= self.xcore.ModMask.Shift if 'C' in mstr: mod |= self.xcore.ModMask.Control if 'W' in mstr: mod |= getattr(self.xcore.ModMask, '4') else: sym = keystr else: sym = keystr if sym.lower() != sym: mod = self.xcore.ModMask.Shift sym = sym.lower() code = self.keysyms.name_to_code[sym] return mod, code def add_key(self, keystr, handler): m = hotkey_re.match(keystr) if not m: raise ValueError(keystr) try: modmask, keysym = self.parse_key(m.group(0)) except (KeyError, ValueError): log.error("Can't parse key %r", m.group(0)) else: self.keys[modmask, keysym] = handler def init_modifiers(self): # TODO(tailhook) probably calculate them instead of hardcoding caps = self.xcore.ModMask.Lock # caps lock num = getattr(self.xcore.ModMask, '2') # mod2 is usually numlock mode = getattr(self.xcore.ModMask, '5') # mod5 is usually mode_switch self.extra_modifiers = [ 0, caps, num, mode, caps | num, num | mode, caps | num | mode, ] self.modifiers_mask = ~(caps | num | mode) def register_keys(self, win): self.init_modifiers() for mod, key in self.keys: kcodes = self.xcore.keysym_to_keycode[key] if not kcodes: log.error("No mapping for key %r", self.keysyms.code_to_name[key]) continue for kcode in kcodes: for extra in self.extra_modifiers: self.xcore.raw.GrabKey( owner_events=False, grab_window=win, modifiers=mod | extra, key=kcode, keyboard_mode=self.xcore.GrabMode.Async, pointer_mode=self.xcore.GrabMode.Async, ) def unregister_keys(self, win): self.xcore.raw.UngrabKey( grab_window=win, modifiers=self.xcore.ModMask.Any, key=self.xcore.Grab.Any, ) def dispatch_event(self, event): try: kcode = self.xcore.keycode_to_keysym[event.detail] handler = self.keys[event.state & self.modifiers_mask, kcode] except KeyError: return False else: try: handler() except Exception as e: log.exception("Error handling keypress %r", event, exc_info=(type(e), e, e.__traceback__))
class YahooWeather(Widget): tags_to_fetch = ( 'location', 'units', 'wind', 'atmosphere', 'astronomy', 'condition', ) theme = dependency(Theme, 'theme') def __init__(self, location, *, picture_url=DEFAULT_PIC, format='{condition_temp}°{units_temperature}', metric=True, right=False): """ Location should be either a woeid or a name string. Change metric to False for imperial units. Set `picture_url` to None to hide picture. Available format variables: astronomy_sunrise, astronomy_sunset atmosphere_humidity, atmosphere_visibility, atmosphere_pressure, atmosphere_rising condition_text, condition_code, condition_temp, condition_date location_city. location_region, location_country units_temperature, units_distance, units_pressure, units_speed wind_chill, wind_direction, wind_speed """ super().__init__(right=right) self.location = location self.format = format self.metric = metric self.picture_url = picture_url self.text = '--' self.image = None self.oldimg_url = None def __zorro_di_done__(self): bar = self.theme.bar self.font = bar.font self.color = bar.text_color_pat self.padding = bar.text_padding gethub().do_spawnhelper(self._update_handler) def _update_handler(self): try: woeid = int(self.location) except ValueError: woeid = self.fetch_woeid() self.uri = "{0}{1}".format( WEATHER_URL, urlencode({ 'w': woeid, 'u': self.metric and 'c' or 'f' })) while True: result = self.fetch() if result is not None: self.text = self.format.format_map(result) if self.picture_url is not None: self.fetch_image(result) sleep(600) def fetch_image(self, data): img_url = None try: img_url = self.picture_url.format_map(data) if img_url == self.oldimg_url and self.image: return data = fetchurl(img_url) self.image = cairo.ImageSurface.create_from_png(BytesIO(data)) self.oldimg_url = img_url except Exception as e: log.exception("Error fetching picture %r", img_url, exc_info=e) self.image = None def fetch_woeid(self): woeid = None while woeid is None: try: data = fetchurl(QUERY_URL, query={ 'q': "select woeid from geo.places " "where text='{0}'".format(self.location), 'format': 'json' }) data = json.loads(data.decode('ascii'))['query'] if data['count'] > 1: woeid = data['results']['place'][0]['woeid'] else: woeid = data['results']['place']['woeid'] except Exception as e: log.exception("Error fetching woeid", exc_info=e) sleep(60) else: if woeid is None: sleep(60) return woeid def fetch(self): try: data = fetchurl(self.uri) xml = ET.fromstring(data.decode('ascii')) except Exception as e: log.exception("Error fetching weather info", exc_info=e) return None data = dict() for tag in self.tags_to_fetch: elem = xml.find('.//{%s}%s' % (WEATHER_NS, tag)) for attr, val in elem.attrib.items(): data['{0}_{1}'.format(tag, attr)] = val return data def draw(self, canvas, l, r): self.font.apply(canvas) _, _, w, h, _, _ = canvas.text_extents(self.text) if self.image: iw = self.image.get_width() ih = self.image.get_height() imh = self.height imw = int(iw / ih * imh + 0.5) scale = ih / imh if self.right: x = r - w - self.padding.right - imw else: x = l y = 0 pat = cairo.SurfacePattern(self.image) pat.set_matrix( cairo.Matrix(xx=scale, yy=scale, x0=-x * scale, y0=-y * scale)) pat.set_filter(cairo.FILTER_BEST) canvas.set_source(pat) canvas.rectangle(x, 0, imw, imh) canvas.fill() else: imw = 0 canvas.set_source(self.color) if self.right: x = r - self.padding.right - w r -= self.padding.left + self.padding.right + w + imw else: x = l + self.padding.left l += self.padding.left + self.padding.right + w + imw canvas.move_to(x, self.height - self.padding.bottom) canvas.show_text(self.text) return l, r
class Bar(object): xcore = dependency(Core, 'xcore') dispatcher = dependency(EventDispatcher, 'event-dispatcher') theme = dependency(Theme, 'theme') def __init__(self, widgets, position='top'): self.widgets = widgets self.position = position self.bounds = None self.window = None self.redraw = Event('bar.redraw') self.redraw.listen(self.expose) def __zorro_di_done__(self): bar = self.theme.bar self.height = bar.height self.background = bar.background_pat inj = di(self).clone() inj['bar'] = self for w in self.widgets: w.height = self.height inj.inject(w) def set_bounds(self, rect): self.bounds = rect self.width = rect.width stride = self.xcore.bitmap_stride self.img = self.xcore.pixbuf(self.width, self.height) self.cairo = self.img.context() if self.window and not self.window.set_bounds(rect): self.redraw.emit() def create_window(self): EM = self.xcore.EventMask CW = self.xcore.CW self.window = DisplayWindow( self.xcore.create_toplevel( self.bounds, klass=self.xcore.WindowClass.InputOutput, params={ CW.EventMask: EM.Exposure | EM.SubstructureNotify, CW.OverrideRedirect: True, }), self.expose) self.window.want.size = self.bounds di(self).inject(self.window) self.dispatcher.register_window(self.window) self.window.show() def expose(self, rect=None): # TODO(tailhook) set clip region to specified rectangle self.cairo.set_source(self.background) self.cairo.rectangle(0, 0, self.width, self.height) self.cairo.fill() l = 0 r = self.width for i in self.widgets: self.cairo.save() self.cairo.rectangle(l, 0, r - l, self.height) self.cairo.clip() l, r = i.draw(self.cairo, l, r) self.cairo.restore() self.img.draw(self.window)
class Window(BaseWindow): xcore = dependency(Core, 'xcore') ewmh = dependency(Ewmh, 'ewmh') theme = dependency(Theme, 'theme') border_width = 0 ignore_hints = False any_window_changed = Event('Window.any_window_changed') def __init__(self, wid): super().__init__(wid) self.frame = None self.want = State() self.done = State() self.real = State() self.props = {} self.lprops = LayoutProperties(self) self.property_changed = Event('window.property_changed') self.protocols = set() self.ignore_protocols = set() def __repr__(self): return '<{} {}>'.format(self.__class__.__name__, self.wid) @classmethod def from_notify(cls, notify): win = cls(notify.window) win.parent = notify.parent win.override = notify.override_redirect win.want.size = SizeRequest.from_notify(notify) return win def update_size_request(self, req): msk = self.xcore.ConfigWindow sr = self.want.size if req.value_mask & msk.X: sr.x = req.x if req.value_mask & msk.Y: sr.y = req.y if req.value_mask & msk.Width: sr.width = req.width if req.value_mask & msk.Height: sr.height = req.height sz = self.done.size if sz is None: sz = sr self.send_event( 'ConfigureNotify', window=self, event=self, x=sz.x, y=sz.y, width=sz.width, height=sz.height, border_width=0, above_sibling=0, override_redirect=False, ) def send_event(self, event_type, event_mask=0, **kw): self.xcore.send_event(event_type, event_mask, self, **kw) def __index__(self): return self.wid def show(self): if self.done.visible: return False self.done.visible = True self.any_window_changed.emit() self.xcore.raw.MapWindow(window=self) if self.frame: self.ewmh.showing_window(self) self.frame.show() return True def hide(self): if not self.done.visible: return False self.done.visible = False self.any_window_changed.emit() if self.frame: self.ewmh.hiding_window(self) self.frame.hide() else: self.xcore.raw.UnmapWindow(window=self) return True def set_bounds(self, rect, force=False): if not force and self.done.size == rect: return False self.any_window_changed.emit() if self.frame: self.frame.set_bounds(rect) else: self.done.size = rect self.xcore.raw.ConfigureWindow( window=self, params={ self.xcore.ConfigWindow.X: rect.x & 0xFFFF, self.xcore.ConfigWindow.Y: rect.y & 0xFFFF, self.xcore.ConfigWindow.Width: rect.width - self.border_width * 2, self.xcore.ConfigWindow.Height: rect.height - self.border_width * 2, }) return True @property def toplevel(self): return self.parent == self.xcore.root_window def reparent_to(self, window): self.xcore.raw.ChangeSaveSet(window=self, mode=self.xcore.SetMode.Insert) self.xcore.raw.ReparentWindow(window=self, parent=window, x=0, y=0) def reparent_frame(self): self.reparent_to(self.frame) def reparent_root(self): self.xcore.raw.ReparentWindow(window=self, parent=self.xcore.root_window, x=0, y=0, _ignore_error=True) # already destroyed self.xcore.raw.ChangeSaveSet(window=self, mode=self.xcore.SetMode.Delete, _ignore_error=True) # already destroyed def create_frame(self): s = self.want.size if self.lprops.floating: border_width = self.theme.window.border_width s.x = s.x - border_width s.y = s.y - border_width self.frame = di(self).inject( Frame( self.xcore.create_toplevel( s, klass=self.xcore.WindowClass.InputOutput, params={ self.xcore.CW.BackPixel: self.theme.window.background, self.xcore.CW.BorderPixel: self.theme.window.inactive_border, self.xcore.CW.EventMask: self.xcore.EventMask.SubstructureRedirect | self.xcore.EventMask.SubstructureNotify | self.xcore.EventMask.EnterWindow | self.xcore.EventMask.LeaveWindow | self.xcore.EventMask.FocusChange }), self)) self.frame.want.size = s self.frame.done.size = s self.frame.set_border(self.frame.border_width) return self.frame def update_property(self, atom): try: self._set_property(self.xcore.atom[atom].name, *self.xcore.get_property(self, atom)) except XError: log.debug("Error getting property for window %r", self) def focus(self): self.done.focus = True want_input = True if "WM_TAKE_FOCUS" in self.protocols: self.send_event( 'ClientMessage', window=self, type=self.xcore.atom.WM_PROTOCOLS, format=32, data=struct.pack('<LL', self.xcore.atom.WM_TAKE_FOCUS, self.xcore.last_time), ) want_input = is_window_needs_input(self) if want_input: self.xcore.raw.SetInputFocus( focus=self, revert_to=self.xcore.InputFocus.PointerRoot, time=self.xcore.last_time, ) def _set_property(self, name, typ, value): if name == 'WM_NORMAL_HINTS': self.want.hints = SizeHints.from_property(typ, value) if name == 'WM_PROTOCOLS': self.protocols = set(self.xcore.atom[p].name for p in value if p not in self.ignore_protocols) if name in self.lprops.long_to_short: if isinstance(value, tuple) and len(value) == 1: value = value[0] super(LayoutProperties, self.lprops).__setattr__(self.lprops.long_to_short[name], value) elif name.startswith('_TN_LP_'): if isinstance(value, tuple) and len(value) == 1: value = value[0] super(LayoutProperties, self.lprops).__setattr__(name[len('_TN_LP_'):].lower(), value) elif name == '_NET_WM_ICON': icons = self.icons = [] lst = list(value) def cvt(px): a = px >> 24 k = a / 255.0 r = (px >> 16) & 0xff g = (px >> 8) & 0xff b = px & 0xff return (a << 24) | (int(r * k) << 16) | ( int(g * k) << 8) | int(b * k) while lst: w = lst.pop(0) h = lst.pop(0) idata = [cvt(px) for px in lst[:w * h]] del lst[:w * h] icons.append((w, h, idata)) icons.sort() self.props[name] = value self.property_changed.emit() self.any_window_changed.emit() def draw_icon(self, canvas, x, y, size): for iw, ih, data in self.icons: if iw >= size or ih >= size: break scale = min(iw / size, ih / size) data = array.array('I', data) assert data.itemsize == 4 surf = cairo.ImageSurface.create_for_data(memoryview(data), cairo.FORMAT_ARGB32, iw, ih, iw * 4) pat = cairo.SurfacePattern(surf) pat.set_matrix( cairo.Matrix(xx=scale, yy=scale, x0=-x * scale, y0=-y * scale)) pat.set_filter(cairo.FILTER_BEST) canvas.set_source(pat) canvas.rectangle(x, y, size, size) canvas.fill() def set_property(self, name, value): if isinstance(value, int): self.xcore.raw.ChangeProperty(window=self, mode=self.xcore.PropMode.Replace, property=getattr( self.xcore.atom, name), type=self.xcore.atom.CARDINAL, format=32, data_len=1, data=struct.pack('<L', value)) elif isinstance(value, Window): self.xcore.raw.ChangeProperty(window=self, mode=self.xcore.PropMode.Replace, property=getattr( self.xcore.atom, name), type=self.xcore.atom.WINDOW, format=32, data_len=1, data=struct.pack('<L', value)) elif isinstance(value, (str, bytes)): if isinstance(value, str): value = value.encode('utf-8') self.xcore.raw.ChangeProperty(window=self, mode=self.xcore.PropMode.Replace, property=getattr( self.xcore.atom, name), type=self.xcore.atom.UTF8_STRING, format=8, data=value) elif (isinstance(value, tuple) and all(isinstance(i, Atom) for i in value)): self.xcore.raw.ChangeProperty( window=self, mode=self.xcore.PropMode.Replace, property=getattr(self.xcore.atom, name), type=self.xcore.atom.ATOM, format=32, data_len=len(value), data=struct.pack('<{}L'.format(len(value)), *value)) elif value is None: self.xcore.raw.DeleteProperty(window=self, property=getattr( self.xcore.atom, name)) else: raise NotImplementedError(value) def destroyed(self): if self.frame: return self.frame.destroy() def destroy(self): self.xcore.raw.DestroyWindow(window=self) def cmd_close(self): delw = self.xcore.atom.WM_DELETE_WINDOW if delw in self.props.get('WM_PROTOCOLS', ()): self.send_event( 'ClientMessage', window=self, type=self.xcore.atom.WM_PROTOCOLS, format=32, data=struct.pack('<LL', delw, 0), ) else: log.warning("Can't close window gracefully, you can kill it") def cmd_kill(self): self.xcore.raw.KillClient(resource=self) def make_floating(self): if self.lprops.floating: return gr = self.group gr.remove_window(self) self.lprops.floating = True gr.add_window(self) cmd_make_floating = make_floating def make_tiled(self): if not self.lprops.floating: return gr = self.group gr.remove_window(self) self.lprops.floating = False gr.add_window(self) cmd_make_tiled = make_tiled def restack(self, stack_mode): self.stack_mode = stack_mode self.xcore.raw.ConfigureWindow(window=self, params={ self.xcore.ConfigWindow.StackMode: stack_mode, }) def set_border(self, width): self.border_width = width self.xcore.raw.ConfigureWindow(window=self, params={ self.xcore.ConfigWindow.BorderWidth: width, }) if self.done.size: self.set_bounds(self.done.size, force=True) def cmd_toggle_border(self): if self.frame: self.frame.toggle_border() def client_message(self, ev): if ev.type == self.xcore.atom._NET_WM_STATE: op, p1, p2, src, _ = struct.unpack('<5L', ev.data) if op is 1: # _NET_WM_STATE_ADD atom = self.xcore.atom._NET_WM_STATE_FULLSCREEN if atom == p1: self.set_property("_NET_WM_STATE", (atom, )) elif op is 0: # _NET_WM_STATE_REMOVE atom = self.xcore.atom._NET_WM_STATE_FULLSCREEN if atom == p1: self.set_property("_NET_WM_STATE", None) elif op is 2: # _NET_WM_STATE_TOGGLE pass # TODO(tailhook) implement me