class Mouse: def __init__(self, conn, root): self.conn = conn self.root = root self.log = Log("mouse") def move(self, x=None, y=None, dx=0, dy=0, window=None): if window is None: window = self.root xcb_reply = self.conn.core.QueryPointer(window.wid).reply() new_x = xcb_reply.win_x new_y = xcb_reply.win_y if x: new_x = x if y: new_y = y if dx: new_x += dx if dy: new_y += dy self.log.debug("relocating to ({}, {})".format(new_x, new_y)) self.conn.core.WarpPointerChecked( 0, window.wid, # src_window, dst_window 0, 0, # src_x, src_y 0, 0, # src_width, src_height new_x, new_y # dest_x, dest_y )
def __init__(self, id, windows=None, name=None): self.id = id if not name: name = "(desktop %s)" % id(self) self.log = Log("desktop %s" % name) if not windows: windows = [] self.windows = windows self.name = name self.cur_focus = None self.prev_focus = None self.were_mapped = [] self.hidden = True # TODO: rename to active
class Worker(Thread): def __init__(self, timeline): super().__init__(daemon=True) self.timeline = timeline self.log = Log("worker %s" % self.name) def run(self): queue = self.timeline.outq schedule = self.timeline.schedule while True: c = queue.get() self.log.debug("running %s" % c) r, out = c._check() schedule(c)
def __init__(self, path, secret): assert secret, "password cannot be empty" self.log = Log("passwords") self.set_secret(secret) self.passwords = OrderedDict() self.path = os.path.expanduser(path) if not os.path.exists(self.path): return print("No passwords stored yet.\n" "%s will be created on first save" % self.path) with MyPipe() as (r,w): os.write(w, self._secret) try: cmd = DECRYPT.format(fd=r, path=self.path) with MyPopen(cmd, stdout=PIPE, close_fds=False) as p: try: self.passwords = pickle.load(p.stdout) except Exception as err: self.log.critical("cannot load db: it is truncated, \n" "corrupted or wrong password provided: %s" % err) raise except FileNotFoundError as err: self.log.critical(err) sys.exit("Please install openssl or check it's in your $PATH.") except CalledProcessError as err: self.log.critical("Can't load DB. Wrong password or no openssl? %s" % err) raise
def __init__(self, interval=60, name="<no name>", descr=None): self.interval = interval self.name = name self.descr = descr self.statuses = deque(maxlen=6) self.log = Log("checker %s" % self.__class__.__name__) global checks; checks += [self]
def __init__(self, interval=60, name="<no name>", descr=None, histlen=10): global all_checks all_checks.append(self) self.interval = interval self.name = name self.descr = descr self.history = deque(maxlen=histlen) self.log = Log("checker %s" % self.__class__.__name__)
def __init__(self, s, dst, buf, timeout=1): global cnt cnt += 1 super().__init__() self.s1 = s self.dst = dst self.buf = buf self.timeout = timeout self.log = Log("{} {}".format(dst, cnt))
class Worker(Thread): """ Get tasks that are ready to run and execute them. """ def __init__(self, scheduler): super().__init__(daemon=True) self.scheduler = scheduler self.log = Log("worker %s" % self.name) def run(self): queue = self.scheduler.ready schedule = self.scheduler.schedule history = self.scheduler.history while True: checker = queue.get() self.log.debug("running %s" % checker) r, out = checker._check() self.log.debug("result: %s %s" %(r, out)) schedule(checker) history.appendleft((time.time(), r, out))
def __init__(self, src=None, dst=None, buf=128*Kb, backlog=128): assert src and dst, \ "please specify src and dst addresses" self.src = src self.dst = dst self.buf = buf self.backlog = backlog self.log = Log("%s => %s" % (src, dst)) super().__init__()
class Checker: last_checked = None last_status = None, "<no checks were performed yet>" def __init__(self, interval=60, name="<no name>", descr=None, histlen=10): global all_checks all_checks.append(self) self.interval = interval self.name = name self.descr = descr self.history = deque(maxlen=histlen) self.log = Log("checker %s" % self.__class__.__name__) def _check(self): if self.last_checked: delta = time.time() - self.last_checked if delta > (self.interval + 1): # tolerate one second delay self.log.critical("behind schedule for %ss" % delta) self.last_status = self.check() self.last_checked = time.time() self.history.append(self.last_status) self.last_checked = time.time() return self.last_status def check(self): return ERR, "<you need to override this method>" def get_next_check(self): if not self.last_checked: # was never checked, requesting immediate check" next_check = -1 elif self.last_status[0] == OK: next_check = self.last_checked + self.interval else: # reduce interval if last status was bad next_check = self.interval / 3 next_check = max(next_check, 10) # not less than 10 seconds next_check = min(next_check, 120) # no more than two minutes next_check += self.last_checked return next_check def __lt__(self, other): """ This is for PriorityQueue that requires added elements to support ordering. """ return True
def __init__(self, ifname): self.log = Log("monitor") mon_path = "/tmp/wpa_mon_%s" % getpid() atexit.register(lambda: unlink(mon_path)) server_path = "/var/run/wpa_supplicant/%s" % ifname self.log.debug("connecting to %s" % server_path) self.socket = socket(AF_UNIX, SOCK_DGRAM) self.socket.bind(mon_path) self.socket.connect(server_path) super().__init__(daemon=True)
def __init__(self, wm, wid, atoms, mapped=True): from wm import WM # TODO: dirtyhack to avoid circular imports assert isinstance(wm, WM), "wm must be an instance of WM" assert isinstance(wid, int), "wid must be int" self.wid = wid self.wm = wm self._conn = self.wm._conn self.prev_geometry = None self.props = Props(conn=self._conn, window=self, atoms=atoms) self.update_name() # TODO: this is not updated self.update_window_type() # do it after self.name is set (so repr works) self.log = Log(self) self.mapped = mapped self.hints = {} self.update_wm_hints() # subscribe for notifications self._conn.core.ChangeWindowAttributesChecked(wid, CW.EventMask, [EventMask.EnterWindow])
def __init__(self, **kwargs): self.__dict__.update(kwargs) self.pidfile = os.path.join(PREFIX, "kvm_%s.pid"%self.name) self.monfile = os.path.join(PREFIX, "kvm_%s.mon"%self.name) self.log = Log("KVM %s" % self.name) self.qmpsock = None assert self.name, "name is mandatory" if self.mgr: #self.log.debug("adding %s to %s" % (self, self.mgr)) self.mgr.add_instance(self)
def __init__(self, workers=5): super().__init__(daemon=True) self.lock = Lock() # lock queue manipulations self.inq = PriorityQueue() self.outq = Queue() # fired checks to be executed self.timer = MyTimer(0) # timer placeholder for .schedule() so it can call self.timer.cancel() during first time self.log = Log("scheduler") for i in range(workers): worker = Worker(self) worker.start()
def __init__(self, ifname): self.log = Log("WPA %s" % ifname) client_path = "/tmp/wpa_client_%s" % getpid() atexit.register(lambda: unlink(client_path)) server_path = "/var/run/wpa_supplicant/%s" % ifname self.socket = socket(AF_UNIX, SOCK_DGRAM) self.socket.bind(client_path) self.log.debug("using %s wpa socket..." % server_path) self.socket.connect(server_path) self.lock = Lock()
def __init__(self, workers=5, histlen=10000): super().__init__(daemon=True) self.pending = PriorityQueue() self.ready = Queue() # checks to be executed self.timer = MyTimer(0) # timer placeholder for .schedule() so it can call self.timer.cancel() during the first call self.lock = Lock() # lock pending queue self.lockev = Event() # set by .run() when lock is acquired self.history = deque(maxlen=histlen) # keep track of the history self.log = Log("scheduler") for i in range(workers): worker = Worker(self) worker.start()
class Checker: last_checked = None last_status = None, "<no check were performed yet>" def __init__(self, interval=60, name="<no name>", descr=None): self.interval = interval self.name = name self.descr = descr self.statuses = deque(maxlen=6) self.log = Log("checker %s" % self.__class__.__name__) global checks; checks += [self] def _check(self): if self.last_checked: delta = time.time() - self.last_checked if delta > (self.interval+1): log.critical("behind schedule for %ss" % delta) self.last_status = self.check() self.last_checked = time.time() self.statuses += [self.last_status] return self.last_status def check(self): return ERR, "<this is a generic check>" def get_next_check(self): if not self.last_checked: self.log.debug("was never checked, requesting immediate check") return -1 now = time.time() next_check = self.last_checked + self.interval if next_check < now: return now return next_check def __lt__(self, other): return True
class Scheduler(Thread): """ Schedules and executes tasks using threaded workers """ def __init__(self, workers=5): super().__init__(daemon=True) self.lock = Lock() # lock queue manipulations self.inq = PriorityQueue() self.outq = Queue() # fired checks to be executed self.timer = MyTimer(0) # timer placeholder for .schedule() so it can call self.timer.cancel() during first time self.log = Log("scheduler") for i in range(workers): worker = Worker(self) worker.start() def schedule(self, checker): time = checker.get_next_check() with self.lock: self.inq.put((time, checker)) self.timer.cancel() # trigger recalculate because this task may go before pending def run(self): while True: t, c = self.inq.get() with self.lock: now = time.time() self.timer = MyTimer(t-now) try: self.timer.start() self.log.debug("sleeping for %s" % self.timer.interval) self.timer.join() except TimerCanceled: self.log.notice("timer aborted, recalculating timeouts") with self.lock: print(t,c) self.inq.put((t,c)) continue self.outq.put(c)
class Tun(threading.Thread): def __init__(self, s, dst, buf, timeout=1): global cnt cnt += 1 super().__init__() self.s1 = s self.dst = dst self.buf = buf self.timeout = timeout self.log = Log("{} {}".format(dst, cnt)) def run(self): s1 = self.s1 try: s2 = socket.create_connection(self.dst, self.timeout) except Exception as err: self.log.error(err) s1.close() return ss = [s1,s2] while True: #if __debug__: self.log.debug("waiting for events") rlist, _, xlist = select.select(ss, [], ss) if xlist: selg.log.info("connection problems on %s" % xlist) for s in ss: s.close() return for FROM in rlist: TO = s1 if FROM == s2 else s2 data = FROM.recv(self.buf) #if __debug__: self.log.debug("got %s: %s" % (FROM.getpeername(), data)) if not data: #if __debug__: self.log.debug("connection closed by {}, closing connection".format(FROM)) for s in ss: s.close() return TO.sendall(data)
class Proxy(threading.Thread): def __init__(self, src=None, dst=None, buf=128*Kb, backlog=128): assert src and dst, \ "please specify src and dst addresses" self.src = src self.dst = dst self.buf = buf self.backlog = backlog self.log = Log("%s => %s" % (src, dst)) super().__init__() def run(self): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.log.info("listening on {}".format(self.src)) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind(self.src) s.listen(self.backlog) while True: #if __debug__: self.log.debug("waiting for incoming connections on {}" # .format(self.src)) conn, addr = s.accept() #if __debug__: self.log.debug("connection from {}".format(addr)) tun = Tun(conn, self.dst, self.buf) tun.start()
class WPAClient: def __init__(self, ifname): self.log = Log("WPA %s" % ifname) client_path = "/tmp/wpa_client_%s" % getpid() atexit.register(lambda: unlink(client_path)) server_path = "/var/run/wpa_supplicant/%s" % ifname self.socket = socket(AF_UNIX, SOCK_DGRAM) self.socket.bind(client_path) self.log.debug("using %s wpa socket..." % server_path) self.socket.connect(server_path) self.lock = Lock() def send(self, msg): self.log.debug("sending: %s" % msg) if isinstance(msg, str): msg = msg.encode('ascii', errors='ignore') self.socket.send(msg) def recv(self, bufsize=65535): r = self.socket.recv(bufsize) return r.strip().decode('ascii', errors='ignore') def run(self, cmd, check=True): with self.lock: self.send(cmd) r = self.recv() self.log.debug("received: %s" % r) if check: assert r not in ['FAIL', 'UNKNOWN COMMAND'] return r def status(self): raw_status = self.run("STATUS", check=False) return parse_status(raw_status) def scan_results(self): result = [] raw_results = self.run("SCAN_RESULTS") for line in raw_results.splitlines()[1:]: bssid, freq, signal, flags, ssid = line.split() r = Struct(ssid=ssid, signal=signal, bssid=bssid, freq=freq, flags=flags) result.append(r) return result def connect(self, network): nid = self.run("ADD_NETWORK") for cmd in network.wpacfg(): self.run(s("SET_NETWORK ${nid} ${cmd}")) self.run(s("SELECT_NETWORK ${nid}")) self.run(s("ENABLE_NETWORK ${nid}"))
def __init__(self, wm, wid, atoms, mapped=True): from wm import WM # TODO: dirtyhack to avoid circular imports assert isinstance(wm, WM), "wm must be an instance of WM" assert isinstance(wid, int), "wid must be int" self.wid = wid self.wm = wm self._conn = self.wm._conn self.prev_geometry = None self.props = Props(conn=self._conn, window=self, atoms=atoms) self.update_name() # TODO: this is not updated self.update_window_type() # do it after self.name is set (so repr works) self.log = Log(self) self.mapped = mapped self.hints = {} self.update_wm_hints() # subscribe for notifications self._conn.core.ChangeWindowAttributesChecked( wid, CW.EventMask, [EventMask.EnterWindow])
def __init__(self, xcb_setup, conn): self._conn = conn self.code_to_syms = {} self.first_sym_to_code = {} self.log = Log("keyboard") first = xcb_setup.min_keycode count = xcb_setup.max_keycode - xcb_setup.min_keycode + 1 q = self._conn.core.GetKeyboardMapping(first, count).reply() assert len(q.keysyms) % q.keysyms_per_keycode == 0, \ "Wrong keyboard mapping from X server??" for i in range(len(q.keysyms) // q.keysyms_per_keycode): self.code_to_syms[first + i] = \ q.keysyms[ i * q.keysyms_per_keycode:(i + 1) * q.keysyms_per_keycode] for k, s in self.code_to_syms.items(): if s[0] and not s[0] in self.first_sym_to_code: self.first_sym_to_code[s[0]] = k
class Passwords: def __init__(self, path, secret): assert secret, "password cannot be empty" self.log = Log("passwords") self.set_secret(secret) self.passwords = OrderedDict() self.path = os.path.expanduser(path) if not os.path.exists(self.path): return print("No passwords stored yet.\n" "%s will be created on first save" % self.path) with MyPipe() as (r,w): os.write(w, self._secret) try: cmd = DECRYPT.format(fd=r, path=self.path) with MyPopen(cmd, stdout=PIPE, close_fds=False) as p: try: self.passwords = pickle.load(p.stdout) except Exception as err: self.log.critical("cannot load db: it is truncated, \n" "corrupted or wrong password provided: %s" % err) raise except FileNotFoundError as err: self.log.critical(err) sys.exit("Please install openssl or check it's in your $PATH.") except CalledProcessError as err: self.log.critical("Can't load DB. Wrong password or no openssl? %s" % err) raise def delkey(self, key): if key in self.passwords: del self.passwords[key] self.sync() def add(self, key, text): if key in self.passwords: return print("I will not overwrite key %s" % key) self.passwords[key] = text self.sync() def sync(self): with MyPipe() as (r,w): os.write(w, self._secret) cmd = ENCRYPT.format(fd=r, path=self.path) with MyPopen(cmd, close_fds=False, stdin=PIPE) as p: pickle.dump(self.passwords, file=p.stdin) def set_secret(self, secret): self._secret = secret.encode()+ b'\n' # this way it can be directly sent to pipe
class WPAMonitor(Thread): def __init__(self, ifname): self.log = Log("monitor") mon_path = "/tmp/wpa_mon_%s" % getpid() atexit.register(lambda: unlink(mon_path)) server_path = "/var/run/wpa_supplicant/%s" % ifname self.log.debug("connecting to %s" % server_path) self.socket = socket(AF_UNIX, SOCK_DGRAM) self.socket.bind(mon_path) self.socket.connect(server_path) super().__init__(daemon=True) def run(self): self.socket.send(b"AUTOSCAN periodic:10") self.socket.send(b"AP_SCAN 1") self.socket.send(b"ATTACH") while True: try: data = self.socket.recv(65535).strip().decode('ascii', 'ignore') self.log.debug("got %s" % data) if data == 'OK': continue if data == 'FAIL': raise Exception("Failure detected") mask, evtype = data.split('>', 1) if evtype == 'CTRL-EVENT-SCAN-RESULTS': print("scan results") for cb in events['scan_results']: cb() else: self.log.info("unknown event %s" % data) except Exception as e: self.log.critical(e) sys.exit(e)
from useful.log import Log from defs import ModMasks from subprocess import Popen from itertools import chain import shlex import os log = Log("utils") def run(cmd): if isinstance(cmd, str): cmd = shlex.split(cmd) # os.setpgrp supressses signal forwarding to children # TODO: test this return Popen(cmd, preexec_fn=os.setpgrp) def run_(cmd): try: return run(cmd) except Exception as err: log.run_.error("failed to exec %s: %s" % (cmd, err)) def get_modmask(modifiers): result = 0 for m in modifiers: assert m in ModMasks, "unknown modifier %s" % m result |= ModMasks[m] return result
def __init__(self, display=None, desktops=None, loop=None): self.log = Log("WM") # INIT SOME BASIC STUFF self.hook = Hook() self.windows = {} # mapping between window id and Window self.win2desk = {} if not display: display = os.environ.get("DISPLAY") try: self._conn = xcffib.connect(display=display) except xcffib.ConnectionException: sys.exit("cannot connect to %s" % display) self.atoms = AtomVault(self._conn) self.desktops = desktops or [Desktop()] self.cur_desktop = self.desktops[0] self.cur_desktop.show() # CREATE ROOT WINDOW xcb_setup = self._conn.get_setup() xcb_screens = [i for i in xcb_setup.roots] self.xcb_default_screen = xcb_screens[self._conn.pref_screen] root_wid = self.xcb_default_screen.root self.root = Window(self, wid=root_wid, atoms=self.atoms, mapped=True) self.windows[root_wid] = self.root # for desktop in self.desktops: # desktop.windows.append(self.root) self.root.set_attr( eventmask=( EventMask.StructureNotify | EventMask.SubstructureNotify | EventMask.FocusChange # | EventMask.SubstructureRedirect | EventMask.EnterWindow # | EventMask.LeaveWindow # | EventMask.PropertyChange | EventMask.OwnerGrabButton ) ) # INFORM X WHICH FEATURES WE SUPPORT self.root.props[self.atoms._NET_SUPPORTED] = [self.atoms[a] for a in SUPPORTED_ATOMS] # PRETEND TO BE A WINDOW MANAGER supporting_wm_check_window = self.create_window(-1, -1, 1, 1) supporting_wm_check_window.props['_NET_WM_NAME'] = "SWM" self.root.props['_NET_SUPPORTING_WM_CHECK'] = supporting_wm_check_window.wid self.root.props['_NET_NUMBER_OF_DESKTOPS'] = len(self.desktops) self.root.props['_NET_CURRENT_DESKTOP'] = 0 # TODO: set cursor # EVENTS THAT HAVE LITTLE USE FOR US... self.ignoreEvents = { "KeyRelease", "ReparentNotify", # "CreateNotify", # DWM handles this to help "broken focusing windows". # "MapNotify", "ConfigureNotify", "LeaveNotify", "FocusOut", "FocusIn", "NoExposure", } # KEYBOARD self.kbd = Keyboard(xcb_setup, self._conn) self.mouse = Mouse(conn=self._conn, root=self.root) # FLUSH XCB BUFFER self.xsync() # apply settings # the event loop is not yet there, but we might have some pending # events... self._xpoll() # TODO: self.grabMouse # NOW IT'S TIME TO GET PHYSICAL SCREEN CONFIGURATION self.xrandr = Xrandr(root=self.root, conn=self._conn) # TODO: self.update_net_desktops() # SETUP EVENT LOOP if not loop: loop = asyncio.new_event_loop() self._eventloop = loop self._eventloop.add_signal_handler(signal.SIGINT, self.stop) self._eventloop.add_signal_handler(signal.SIGTERM, self.stop) self._eventloop.add_signal_handler(signal.SIGCHLD, self.on_sigchld) self._eventloop.set_exception_handler( lambda loop, ctx: self.log.error( "Got an exception in {}: {}".format(loop, ctx)) ) fd = self._conn.get_file_descriptor() self._eventloop.add_reader(fd, self._xpoll) # HANDLE STANDARD EVENTS self.hook.register("MapRequest", self.on_map_request) self.hook.register("MapNotify", self.on_map_notify) self.hook.register("UnmapNotify", self.on_window_unmap) self.hook.register("KeyPress", self.on_key_press) # self.hook.register("KeyRelease", self.on_key_release) # self.hook.register("CreateNotify", self.on_window_create) self.hook.register("PropertyNotify", self.on_property_notify) self.hook.register("ClientMessage", self.on_client_message) self.hook.register("DestroyNotify", self.on_window_destroy) self.hook.register("EnterNotify", self.on_window_enter) self.hook.register("ConfigureRequest", self.on_configure_window) self.hook.register("MotionNotify", self.on_mouse_event) self.hook.register("ButtonPress", self.on_mouse_event) self.hook.register("ButtonRelease", self.on_mouse_event)
class WM: """ Provides basic building blocks to make a window manager. It hides many dirty details about XCB. It was intended to provide minimum functionality, the rest supposed to be implemented by user in configuration file. """ root = None # type: Window atoms = None # type: AtomVault def __init__(self, display=None, desktops=None, loop=None): self.log = Log("WM") # INIT SOME BASIC STUFF self.hook = Hook() self.windows = {} # mapping between window id and Window self.win2desk = {} if not display: display = os.environ.get("DISPLAY") try: self._conn = xcffib.connect(display=display) except xcffib.ConnectionException: sys.exit("cannot connect to %s" % display) self.atoms = AtomVault(self._conn) self.desktops = desktops or [Desktop()] self.cur_desktop = self.desktops[0] self.cur_desktop.show() # CREATE ROOT WINDOW xcb_setup = self._conn.get_setup() xcb_screens = [i for i in xcb_setup.roots] self.xcb_default_screen = xcb_screens[self._conn.pref_screen] root_wid = self.xcb_default_screen.root self.root = Window(self, wid=root_wid, atoms=self.atoms, mapped=True) self.windows[root_wid] = self.root # for desktop in self.desktops: # desktop.windows.append(self.root) self.root.set_attr( eventmask=( EventMask.StructureNotify | EventMask.SubstructureNotify | EventMask.FocusChange # | EventMask.SubstructureRedirect | EventMask.EnterWindow # | EventMask.LeaveWindow # | EventMask.PropertyChange | EventMask.OwnerGrabButton ) ) # INFORM X WHICH FEATURES WE SUPPORT self.root.props[self.atoms._NET_SUPPORTED] = [self.atoms[a] for a in SUPPORTED_ATOMS] # PRETEND TO BE A WINDOW MANAGER supporting_wm_check_window = self.create_window(-1, -1, 1, 1) supporting_wm_check_window.props['_NET_WM_NAME'] = "SWM" self.root.props['_NET_SUPPORTING_WM_CHECK'] = supporting_wm_check_window.wid self.root.props['_NET_NUMBER_OF_DESKTOPS'] = len(self.desktops) self.root.props['_NET_CURRENT_DESKTOP'] = 0 # TODO: set cursor # EVENTS THAT HAVE LITTLE USE FOR US... self.ignoreEvents = { "KeyRelease", "ReparentNotify", # "CreateNotify", # DWM handles this to help "broken focusing windows". # "MapNotify", "ConfigureNotify", "LeaveNotify", "FocusOut", "FocusIn", "NoExposure", } # KEYBOARD self.kbd = Keyboard(xcb_setup, self._conn) self.mouse = Mouse(conn=self._conn, root=self.root) # FLUSH XCB BUFFER self.xsync() # apply settings # the event loop is not yet there, but we might have some pending # events... self._xpoll() # TODO: self.grabMouse # NOW IT'S TIME TO GET PHYSICAL SCREEN CONFIGURATION self.xrandr = Xrandr(root=self.root, conn=self._conn) # TODO: self.update_net_desktops() # SETUP EVENT LOOP if not loop: loop = asyncio.new_event_loop() self._eventloop = loop self._eventloop.add_signal_handler(signal.SIGINT, self.stop) self._eventloop.add_signal_handler(signal.SIGTERM, self.stop) self._eventloop.add_signal_handler(signal.SIGCHLD, self.on_sigchld) self._eventloop.set_exception_handler( lambda loop, ctx: self.log.error( "Got an exception in {}: {}".format(loop, ctx)) ) fd = self._conn.get_file_descriptor() self._eventloop.add_reader(fd, self._xpoll) # HANDLE STANDARD EVENTS self.hook.register("MapRequest", self.on_map_request) self.hook.register("MapNotify", self.on_map_notify) self.hook.register("UnmapNotify", self.on_window_unmap) self.hook.register("KeyPress", self.on_key_press) # self.hook.register("KeyRelease", self.on_key_release) # self.hook.register("CreateNotify", self.on_window_create) self.hook.register("PropertyNotify", self.on_property_notify) self.hook.register("ClientMessage", self.on_client_message) self.hook.register("DestroyNotify", self.on_window_destroy) self.hook.register("EnterNotify", self.on_window_enter) self.hook.register("ConfigureRequest", self.on_configure_window) self.hook.register("MotionNotify", self.on_mouse_event) self.hook.register("ButtonPress", self.on_mouse_event) self.hook.register("ButtonRelease", self.on_mouse_event) def on_property_notify(self, evname, xcb_event): # TODO: messy ugly code wid = xcb_event.window atom = self.atoms.get_name(xcb_event.atom) # window = self.windows.get(wid, Window(wm=self, wid=wid, mapped=True)) self.log.error("PropertyNotify: %s" % atom) run_("xprop -id %s %s" % (wid, atom)) # TODO: dirty code, relocate to config def on_client_message(self, evname, xcb_event): self.log.error(dir(xcb_event)) data = xcb_event.data resp_type = self.atoms.get_name(xcb_event.response_type) type = self.atoms.get_name(xcb_event.type) wid = xcb_event.window self.log.error("client message: resp_type={resp_type} window={wid} type={type} data={data}" \ .format(**locals())) # 'bufsize', 'data', 'format', 'pack', 'response_type', 'sequence', 'synthetic', 'type', 'window'] if type == '_NET_ACTIVE_WINDOW': window = self.windows[wid] window.rise() self.focus_on(window) def on_sigchld(self): """ Rip orphans. """ while True: try: pid, status = os.waitpid(-1, os.WNOHANG) if (pid, status) == (0, 0): # no child to rip break self.log.notice("ripped child PID=%s" % pid) except ChildProcessError: break def on_map_request(self, evname, xcb_event): """ Map request is a request to draw the window on screen. """ wid = xcb_event.window if wid not in self.windows: window = self.on_new_window(wid) else: window = self.windows[wid] self.log.on_map_request.debug("map request for %s" % window) window.show() if window.above_all: window.rise() if window.can_focus: window.focus() def on_new_window(self, wid): """ Registers new window. """ window = Window(wm=self, wid=wid, atoms=self.atoms, mapped=True) # call configuration hood first # to setup attributes like 'sticky' self.hook.fire("new_window", window) self.log.on_new_window.debug( "new window is ready: %s" % window) self.windows[wid] = window self.win2desk[window] = self.cur_desktop if window.sticky: for desktop in self.desktops: desktop.add(window) else: self.cur_desktop.windows.append(window) return window def on_map_notify(self, evname, xcb_event): wid = xcb_event.window if wid not in self.windows: window = self.on_new_window(wid) else: window = self.windows[wid] window.mapped = True if window.above_all: window.rise() # if window.can_focus: # window.focus() self.log.on_map_notify.debug("map notify for %s" % window) def on_window_unmap(self, evname, xcb_event): wid = xcb_event.window if wid not in self.windows: return window = self.windows[wid] window.mapped = False self.hook.fire("window_unmap", window) def on_window_destroy(self, evname, xcb_event): wid = xcb_event.window if wid not in self.windows: return window = self.windows[wid] assert isinstance(window, Window), "it's not a window: %s (%s)" % ( window, type(window)) for desktop in self.desktops: try: desktop.windows.remove(window) self.log.debug("%s removed from %s" % (self, desktop)) except ValueError: pass del self.windows[wid] if window in self.win2desk: del self.win2desk[window] def on_window_enter(self, evname, xcb_event): wid = xcb_event.event if wid not in self.windows: # self.log.on_window_enter.error("no window with wid=%s" % wid) self.hook.fire("unknown_window", wid) return window = self.windows[wid] # self.log.on_window_enter("window_enter: %s %s" % (wid, window)) self.hook.fire("window_enter", window) def grab_key(self, modifiers, key, owner_events=False, window=None): """ Intercept this key when it is pressed. If owner_events=False then the window in focus will not receive it. This is useful from WM hotkeys. """ # TODO: check if key already grabbed? # Here is how X works with keys: # key => keysym => keycode # where `key' is something like 'a', 'b' or 'Enter', # `keysum' is what should be written on they key cap (physical keyboard) # and `keycode' is a number reported by the keyboard when the key is pressed. # Modifiers are keys like Shift, Alt, Win and some other buttons. self.log.grab_key.debug("intercept keys: %s %s" % (modifiers, key)) if window is None: window = self.root keycode = self.kbd.key_to_code(key) modmask = get_modmask(modifiers) # TODO: move to Keyboard event = ("on_key_press", modmask, keycode) pointer_mode = xproto.GrabMode.Async keyboard_mode = xproto.GrabMode.Async self._conn.core.GrabKey( owner_events, window.wid, modmask, keycode, pointer_mode, keyboard_mode ) self.flush() # TODO: do we need this? return event def on_key_press(self, evname, xcb_event): # TODO: ignore capslock, scrolllock and other modifiers? modmap = xcb_event.state keycode = xcb_event.detail event = ("on_key_press", modmap, keycode) self.hook.fire(event) def on_key_release(self, evname, xcb_event): modmap = xcb_event.state keycode = xcb_event.detail event = ("on_key_release", modmap, keycode) self.hook.fire(event) def grab_mouse(self, modifiers, button, owner_events=False, window=None): # http://www.x.org/archive/X11R7.7/doc/man/man3/xcb_grab_button.3.xhtml wid = (window or self.root).wid event_mask = xcffib.xproto.EventMask.ButtonPress | \ xcffib.xproto.EventMask.ButtonRelease | \ xcffib.xproto.EventMask.Button1Motion modmask = get_modmask(modifiers) pointer_mode = xproto.GrabMode.Async # I don't know what it is keyboard_mode = xproto.GrabMode.Async # do not block other keyboard events confine_to = xcffib.xproto.Atom._None # do not restrict cursor movements cursor = xcffib.xproto.Atom._None # do not change cursor event = ("on_mouse", modmask, button) # event to be used in hooks self._conn.core.GrabButton( owner_events, wid, event_mask, pointer_mode, keyboard_mode, confine_to, cursor, button, modmask, ) self.flush() # TODO: do we need this? return event def hotkey(self, keys, cmd): """ Setup hook to launch a command on specific hotkeys. """ @self.hook(self.grab_key(*keys)) def cb(event): run_(cmd) def focus_on(self, window, warp=False): """ Focuses on given window. """ self.cur_desktop.focus_on(window, warp) self.root.set_prop('_NET_ACTIVE_WINDOW', window.wid) def switch_to(self, desktop: Desktop): """ Switches to another desktop. """ if isinstance(desktop, int): desktop = self.desktops[desktop] if self.cur_desktop == desktop: self.log.notice("attempt to switch to the same desktop") return self.log.debug("switching from {} to {}".format( self.cur_desktop, desktop)) self.cur_desktop.hide() self.cur_desktop = desktop self.cur_desktop.show() # TODO: move this code to Desktop.show() self.root.props[self.atom._NET_CURRENT_DESKTOP] = desktop.id def relocate_to(self, window: Window, to_desktop: Desktop): """ Relocates window to a specific desktop. """ if window.sticky: self.log.debug( "%s is meant to be on all desktops, cannot relocate to specific one" % window) return from_desktop = self.cur_desktop if from_desktop == to_desktop: self.log.debug( "no need to relocate %s because remains on the same desktop" % window) return from_desktop.remove(window) to_desktop.add(window) def on_mouse_event(self, evname, xcb_event): """evname is one of ButtonPress, ButtonRelease or MotionNotify.""" # l = [(attr, getattr(xcb_event, attr)) for attr in sorted(dir(xcb_event)) if not attr.startswith('_')] # print(evname) # print(l) modmask = xcb_event.state & 0xff # TODO: is the mask correct? if evname == 'MotionNotify': button = 1 # TODO else: button = xcb_event.detail event = ('on_mouse', modmask, button) # print(event) self.hook.fire(event, evname, xcb_event) def on_configure_window(self, _, event): # This code is so trivial that I just took it from fpwm as is :) values = [] if event.value_mask & ConfigWindow.X: values.append(event.x) if event.value_mask & ConfigWindow.Y: values.append(event.y) if event.value_mask & ConfigWindow.Width: values.append(event.width) if event.value_mask & ConfigWindow.Height: values.append(event.height) if event.value_mask & ConfigWindow.BorderWidth: values.append(event.border_width) if event.value_mask & ConfigWindow.Sibling: values.append(event.sibling) if event.value_mask & ConfigWindow.StackMode: values.append(event.stack_mode) self._conn.core.ConfigureWindow(event.window, event.value_mask, values) def create_window(self, x, y, width, height): """ Create a window. Right now only used for initialization, see __init__. """ wid = self._conn.generate_id() self._conn.core.CreateWindow( self.xcb_default_screen.root_depth, wid, self.xcb_default_screen.root, x, y, width, height, 0, WindowClass.InputOutput, self.xcb_default_screen.root_visual, CW.BackPixel | CW.EventMask, [ self.xcb_default_screen.black_pixel, EventMask.StructureNotify | EventMask.Exposure ] ) return Window(self, wid=wid, atoms=self.atoms) def scan(self, focus=True): """ Gets all windows in the system. """ self.log.debug("performing scan of all mapped windows") q = self._conn.core.QueryTree(self.root.wid).reply() for wid in q.children: # attrs=self._conn.core.GetWindowAttributes(wid).reply() # print(attrs, type(attrs)) # if attrs.map_state == xproto.MapState.Unmapped: # self.log.scan.debug( # "window %s is not mapped, skipping" % wid) # TODO # continue if wid not in self.windows: self.on_new_window(wid) self.log.scan.info("the following windows are active: %s" % sorted(self.windows.values())) if focus: windows = sorted(self.windows.values()) windows = list(filter(lambda w: w != self.root and not w.skip, windows)) if windows: # on empty desktop there is nothing to focus on self.cur_desktop.focus_on(windows[-1], warp=True) def finalize(self): """ This code is run when event loop is terminated. """ pass # currently nothing to do here def flush(self): """ Force pending X request to be sent. By default XCB aggressevly buffers for performance reasons. """ return self._conn.flush() def xsync(self): """ Flush XCB queue and wait till it is processed by X server. """ # The idea here is that pushing an innocuous request through the queue # and waiting for a response "syncs" the connection, since requests are # serviced in order. self._conn.core.GetInputFocus().reply() def stop(self, xserver_dead=False): """ Stop WM to quit. """ self.hook.fire("on_exit") # display all hidden windows try: if not xserver_dead: for window in self.windows.values(): window.show() self.xsync() except Exception as err: self.log.stop.error("error on stop: %s" % err) self.log.stop.debug("stopping event loop") self._eventloop.stop() def replace(self, execv_args): self.log.notice("replacing current process with %s" % (execv_args,)) self.stop() import os os.execv(*execv_args) def loop(self): """ DITTO """ self.scan() try: self._eventloop.run_forever() finally: self.finalize() def _xpoll(self): """ Fetch incomming events (if any) and call hooks. """ # OK, kids, today I'll teach you how to write reliable enterprise # software! You just catch all the exceptions in the top-level loop # and ignore them. No, I'm kidding, these exceptions are no use # for us because we don't care if a window cannot be drawn or something. # We actually only need to handle just a few events and ignore the rest. # Exceptions happen because of the async nature of X. while True: try: xcb_event = self._conn.poll_for_event() if not xcb_event: break evname = xcb_event.__class__.__name__ if evname.endswith("Event"): evname = evname[:-5] if evname in self.ignoreEvents: self.log._xpoll.info("ignoring %s" % xcb_event) continue self.log._xpoll.critical("got %s %s" % (evname, xcb_event)) self.hook.fire(evname, xcb_event) self.flush() # xcb doesn't flush implicitly except (WindowError, AccessError, DrawableError): self.log.debug("(minor exception)") except Exception as e: self.log._xpoll.error(traceback.format_exc()) error_code = self._conn.has_error() if error_code: error_string = XCB_CONN_ERRORS[error_code] self.log.critical("Shutting down due to X connection error %s (%s)" % (error_string, error_code)) self.stop(xserver_dead=True) break
class Window: sticky = False can_focus = True above_all = False mapped = False skip = False # do not handle show() and hide() for this event type = "normal" name = "<no name>" def __init__(self, wm, wid, atoms, mapped=True): from wm import WM # TODO: dirtyhack to avoid circular imports assert isinstance(wm, WM), "wm must be an instance of WM" assert isinstance(wid, int), "wid must be int" self.wid = wid self.wm = wm self._conn = self.wm._conn self.prev_geometry = None self.props = Props(conn=self._conn, window=self, atoms=atoms) self.update_name() # TODO: this is not updated self.update_window_type() # do it after self.name is set (so repr works) self.log = Log(self) self.mapped = mapped self.hints = {} self.update_wm_hints() # subscribe for notifications self._conn.core.ChangeWindowAttributesChecked(wid, CW.EventMask, [EventMask.EnterWindow]) def show(self): assert not self.skip self.log.show.debug("showing") self._conn.core.MapWindow(self.wid) self.wm.flush() self.mapped = True def hide(self): assert not self.skip self._conn.core.UnmapWindow(self.wid) self.wm.flush() self.mapped = False def rise(self): """ Put window on top of others. TODO: what about focus? """ return self.stackmode(xproto.StackMode.Above) def lower(self): """ Put window on top of others. TODO: what about focus? """ return self.stackmode(xproto.StackMode.Below) def raiseorlower(self): """ Put window on top of others. TODO: what about focus? """ return self.stackmode(xproto.StackMode.Opposite) def stackmode(self, mode): self._conn.core.ConfigureWindowChecked(self.wid, xproto.ConfigWindow.StackMode, [mode]).check() self.wm.flush() def focus(self): """ Let window receive mouse and keyboard events. X expects window to be mapped. """ if not self.mapped: self.show() #self.wm.cur_desktop.cur_focus = self # TODO: self.wm.root.set_property("_NET_ACTIVE_WINDOW", self.wid) self._conn.core.SetInputFocus(xproto.InputFocus.PointerRoot, self.wid, xproto.Time.CurrentTime) self.wm.flush() # it is here mandatory :( return self def kill(self): """ This is what happens to windows when Alt-F4 or Ctrl-w is pressed. """ self._conn.core.KillClient(self.wid) def move(self, x=None, y=None, dx=0, dy=0): """ Like set_geometry, but with sanity check. """ if dx or dy: x, y, width, height = self.geometry x += dx y += dy x = max(x, 0) y = max(y, 0) self.set_geometry(x=x, y=y) return self def resize(self, x=None, y=None, dx=0, dy=0): """ Like set_geometry, but with sanity check. """ assert not ((x and y) and (dx or dy)), "wrong arguments" if x and y: width = x height = y else: x, y, width, height = self.geometry width += dx height += dy width = max(5, width) height = max(5, height) self.set_geometry(width=width, height=height) return self def toggle_maximize(self): if self.prev_geometry: self.set_geometry(*self.prev_geometry) self.prev_geometry = None else: self.prev_geometry = self.geometry screen = self.wm.xrandr.screen self.set_geometry(x=0, y=0, width=screen.width - 1, height=screen.height - 1 - 18) # TODO: 18 dirtyhack to place bottom panel self.rise() @property def geometry(self): geom = self._conn.core.GetGeometry(self.wid).reply() return [geom.x, geom.y, geom.width, geom.height] def set_geometry(self, x=None, y=None, width=None, height=None): mask = 0 values = [] if x is not None: mask |= xproto.ConfigWindow.X values.append(x) if y is not None: mask |= xproto.ConfigWindow.Y values.append(y) if width is not None: mask |= xproto.ConfigWindow.Width values.append(width) if height is not None: mask |= xproto.ConfigWindow.Height values.append(height) # TODO: what the hell is *Checked and check? # filter negative values values = [max(value, 0) for value in values] self._conn.core.ConfigureWindowChecked(self.wid, mask, values).check() def update_name(self): name = "(no name)" for prop in ["_NET_WM_VISIBLE_NAME", "_NET_WM_NAME"]: new_name = self.props[prop] if new_name: name = new_name break self.name = name return name def warp(self): """ Warps pointer to the middle of the window. Does not work under Xephyr :( """ x, y, width, height = self.geometry self._conn.core.WarpPointer( 0, self.wid, # src_window, dst_window 0, 0, # src_x, src_y 0, 0, # src_width, src_height width // 2, height // 2 # dest_x, dest_y ) self.wm.flush() return self def get_attributes(self): """ Returns https://tronche.com/gui/x/xlib/window-information/XGetWindowAttributes.html . """ return self._conn.core.GetWindowAttributes(self.wid).reply() def set_attr(self, **kwargs): mask, values = AttributeMasks(**kwargs) self.wm._conn.core.ChangeWindowAttributesChecked( self.wid, mask, values) def get_prop(self, prop, typ=None, unpack=None): """ Return the contents of a property as a GetPropertyReply. If unpack is specified, a tuple of values is returned. The type to unpack, either `str` or `int` must be specified. """ if typ is None: if prop not in PROPERTYMAP: raise ValueError("Must specify type for unknown property.") else: typ, _ = PROPERTYMAP[prop] prop = self.wm.atoms[prop] if isinstance(prop, str) else prop typ = self.wm.atoms[typ] if isinstance(typ, str) else typ r = self._conn.core.GetProperty( False, # delete self.wid, # window id prop, typ, 0, # long_offset, (2**32) - 1 # long_length ).reply() if not r.value_len: if unpack: return [] return None elif unpack: # Should we allow more options for unpacking? if unpack is int: return r.value.to_atoms() elif unpack is str: return r.value.to_string() else: return r # TODO: move this code to WM def set_prop(self, name, value, type=None, format=None): """ name: String Atom name type: String Atom name format: 8, 16, 32 """ if name in PROPERTYMAP: if type or format: raise ValueError( "Over-riding default type or format for property.") type, format = PROPERTYMAP[name] else: if None in (type, format): raise ValueError( "Must specify type and format for unknown property.") if isinstance(value, str): # xcffib will pack the bytes, but we should encode them properly value = value.encode() elif isinstance(value, int): value = [value] self.wm._conn.core.ChangePropertyChecked( xproto.PropMode.Replace, self.wid, self.wm.atoms[name], self.wm.atoms[type], format, # Format - 8, 16, 32 len(value), value).check() def list_props(self): reply = self.wm._conn.core.ListProperties(self.wid).reply() atoms = reply.atoms.list return [self.wm.atoms.get_name(atom) for atom in atoms] def update_window_type(self): window_types = self.props['_NET_WM_WINDOW_TYPE'] if not types: self.skip = True for atom in window_types: if atom.name in WINDOW_TYPES: self.type = WINDOW_TYPES[raw_type.name] break else: self.type = "normal" return self.type def update_wm_hints(self): # TODO: dirty code borowwed from qtile l = self.get_prop(Atom.WM_HINTS, typ=xproto.GetPropertyType.Any, unpack=int) # self.log.error("WM_HINTS: {}".format(l)) if not l: return flags = set(k for k, v in HintsFlags.items() if l[0] & v) hints = dict(flags=flags, input=l[1], initial_state=l[2], icon_pixmap=l[3], icon_window=l[4], icon_x=l[5], icon_y=l[6], icon_mask=l[7], window_group=l[8]) self.log.error("parsed hints: {}".format(hints)) if "InputHint" not in hints['flags']: self.log.notice("input hint off") self.can_focus = False self.hints = hints self.flags = flags def __lt__(self, other): # used for sorting and comparison return True def __repr__(self): name = self.name if len(name) > 20: name = name[:17] + '...' return "Window(%s, \"%s\")" % (self.wid, name)
class Hook: """ Simple callback dispatcher. """ def __init__(self): self.cb_map = defaultdict(list) self.log = Log("hook") self.suppressed = set() def decor(self, event): def wrap(cb): self.register(event, cb) return cb return wrap __call__ = decor def register(self, event, cb): self.cb_map[event].append(cb) def has_hook(self, event): return event in self.cb_map def suppress(self, event): hook = self class Context: def __enter__(self): hook.log.debug("suppressing %s" % event) hook.suppressed.add(event) def __exit__(self, *args): hook.log.debug("un-suppressing %s" % event) if event in hook.suppressed: hook.suppressed.remove(event) else: hook.log.notice("uhm, event is not suppressed: %s" % event) return Context() def fire(self, event, *args, **kwargs): self.log.debug("{} {} {}".format(event, args, kwargs)) if event not in self.cb_map: self.log.notice("no handler for {}".format(event)) return if event in self.suppressed: self.log.debug("event suppressed: {} {} {}".format( event, args, kwargs)) return handlers = self.cb_map[event] for handler in handlers: try: handler(event, *args, **kwargs) # except SupressEvent: # break except Exception as err: # msg = "error on event {ev}: {err} ({typ}) (in {hdl})" \ # .format(err=err, typ=type(err), ev=event, hdl=handler) msg = traceback.format_exc() self.log.error(msg)
#!/usr/bin/env python3 from useful.log import Log from .utils import read_val, str2list from .utils import run log = Log(['perf', 'numa']) PREFIX = "/sys/devices/system/" def read_int_list(path): return str2list(read_val(path)) def get_online_nodes(): return read_int_list(PREFIX + "node/online") def get_cpu_name(): cpus_raw = run("cat /proc/cpuinfo | grep 'model name' | cut -d' ' -f3-", shell=True) cpus = set([item.strip() for item in cpus_raw.split('\n') if item]) assert len(cpus) == 1, "Omg, different CPUs installed in this machine??" return cpus.pop() def filter_ht(cpus: list): virtuals = set() for cpu in cpus: siblings = read_int_list(PREFIX + "cpu/cpu%s/topology/thread_siblings_list" % cpu) assert len(siblings) == 1 or len(siblings) == 2, \ "Hmm... Hyper-Threading with more than two siblings??" if len(siblings) == 2:
def __init__(self): self.cb_map = defaultdict(list) self.log = Log("hook") self.suppressed = set()
class Window: sticky = False can_focus = True above_all = False mapped = False skip = False # do not handle show() and hide() for this event type = "normal" name = "<no name>" def __init__(self, wm, wid, atoms, mapped=True): from wm import WM # TODO: dirtyhack to avoid circular imports assert isinstance(wm, WM), "wm must be an instance of WM" assert isinstance(wid, int), "wid must be int" self.wid = wid self.wm = wm self._conn = self.wm._conn self.prev_geometry = None self.props = Props(conn=self._conn, window=self, atoms=atoms) self.update_name() # TODO: this is not updated self.update_window_type() # do it after self.name is set (so repr works) self.log = Log(self) self.mapped = mapped self.hints = {} self.update_wm_hints() # subscribe for notifications self._conn.core.ChangeWindowAttributesChecked( wid, CW.EventMask, [EventMask.EnterWindow]) def show(self): assert not self.skip self.log.show.debug("showing") self._conn.core.MapWindow(self.wid) self.wm.flush() self.mapped = True def hide(self): assert not self.skip self._conn.core.UnmapWindow(self.wid) self.wm.flush() self.mapped = False def rise(self): """ Put window on top of others. TODO: what about focus? """ return self.stackmode(xproto.StackMode.Above) def lower(self): """ Put window on top of others. TODO: what about focus? """ return self.stackmode(xproto.StackMode.Below) def raiseorlower(self): """ Put window on top of others. TODO: what about focus? """ return self.stackmode(xproto.StackMode.Opposite) def stackmode(self, mode): self._conn.core.ConfigureWindowChecked(self.wid, xproto.ConfigWindow.StackMode, [mode]).check() self.wm.flush() def focus(self): """ Let window receive mouse and keyboard events. X expects window to be mapped. """ if not self.mapped: self.show() #self.wm.cur_desktop.cur_focus = self # TODO: self.wm.root.set_property("_NET_ACTIVE_WINDOW", self.wid) self._conn.core.SetInputFocus(xproto.InputFocus.PointerRoot, self.wid, xproto.Time.CurrentTime) self.wm.flush() # it is here mandatory :( return self def kill(self): """ This is what happens to windows when Alt-F4 or Ctrl-w is pressed. """ self._conn.core.KillClient(self.wid) def move(self, x=None, y=None, dx=0, dy=0): """ Like set_geometry, but with sanity check. """ if dx or dy: x, y, width, height = self.geometry x += dx y += dy x = max(x, 0) y = max(y, 0) self.set_geometry(x=x, y=y) return self def resize(self, x=None, y=None, dx=0, dy=0): """ Like set_geometry, but with sanity check. """ assert not ((x and y) and (dx or dy)), "wrong arguments" if x and y: width = x height = y else: x, y, width, height = self.geometry width += dx height += dy width = max(5, width) height = max(5, height) self.set_geometry(width=width, height=height) return self def toggle_maximize(self): if self.prev_geometry: self.set_geometry(*self.prev_geometry) self.prev_geometry = None else: self.prev_geometry = self.geometry screen = self.wm.xrandr.screen self.set_geometry( x=0, y=0, width=screen.width - 1, height=screen.height - 1 - 18) # TODO: 18 dirtyhack to place bottom panel self.rise() @property def geometry(self): geom = self._conn.core.GetGeometry(self.wid).reply() return [geom.x, geom.y, geom.width, geom.height] def set_geometry(self, x=None, y=None, width=None, height=None): mask = 0 values = [] if x is not None: mask |= xproto.ConfigWindow.X values.append(x) if y is not None: mask |= xproto.ConfigWindow.Y values.append(y) if width is not None: mask |= xproto.ConfigWindow.Width values.append(width) if height is not None: mask |= xproto.ConfigWindow.Height values.append(height) # TODO: what the hell is *Checked and check? # filter negative values values = [max(value, 0) for value in values] self._conn.core.ConfigureWindowChecked(self.wid, mask, values).check() def update_name(self): name = "(no name)" for prop in ["_NET_WM_VISIBLE_NAME", "_NET_WM_NAME"]: new_name = self.props[prop] if new_name: name = new_name break self.name = name return name def warp(self): """ Warps pointer to the middle of the window. Does not work under Xephyr :( """ x, y, width, height = self.geometry self._conn.core.WarpPointer( 0, self.wid, # src_window, dst_window 0, 0, # src_x, src_y 0, 0, # src_width, src_height width // 2, height // 2 # dest_x, dest_y ) self.wm.flush() return self def get_attributes(self): """ Returns https://tronche.com/gui/x/xlib/window-information/XGetWindowAttributes.html . """ return self._conn.core.GetWindowAttributes(self.wid).reply() def set_attr(self, **kwargs): mask, values = AttributeMasks(**kwargs) self.wm._conn.core.ChangeWindowAttributesChecked( self.wid, mask, values ) def get_prop(self, prop, typ=None, unpack=None): """ Return the contents of a property as a GetPropertyReply. If unpack is specified, a tuple of values is returned. The type to unpack, either `str` or `int` must be specified. """ if typ is None: if prop not in PROPERTYMAP: raise ValueError( "Must specify type for unknown property." ) else: typ, _ = PROPERTYMAP[prop] prop = self.wm.atoms[prop] if isinstance(prop, str) else prop typ = self.wm.atoms[typ] if isinstance(typ, str) else typ r = self._conn.core.GetProperty( False, # delete self.wid, # window id prop, typ, 0, # long_offset, (2 ** 32) - 1 # long_length ).reply() if not r.value_len: if unpack: return [] return None elif unpack: # Should we allow more options for unpacking? if unpack is int: return r.value.to_atoms() elif unpack is str: return r.value.to_string() else: return r # TODO: move this code to WM def set_prop(self, name, value, type=None, format=None): """ name: String Atom name type: String Atom name format: 8, 16, 32 """ if name in PROPERTYMAP: if type or format: raise ValueError( "Over-riding default type or format for property." ) type, format = PROPERTYMAP[name] else: if None in (type, format): raise ValueError( "Must specify type and format for unknown property." ) if isinstance(value, str): # xcffib will pack the bytes, but we should encode them properly value = value.encode() elif isinstance(value, int): value = [value] self.wm._conn.core.ChangePropertyChecked( xproto.PropMode.Replace, self.wid, self.wm.atoms[name], self.wm.atoms[type], format, # Format - 8, 16, 32 len(value), value ).check() def list_props(self): reply = self.wm._conn.core.ListProperties(self.wid).reply() atoms = reply.atoms.list return [self.wm.atoms.get_name(atom) for atom in atoms] def update_window_type(self): window_types = self.props['_NET_WM_WINDOW_TYPE'] if not types: self.skip = True for atom in window_types: if atom.name in WINDOW_TYPES: self.type = WINDOW_TYPES[raw_type.name] break else: self.type = "normal" return self.type def update_wm_hints(self): # TODO: dirty code borowwed from qtile l = self.get_prop( Atom.WM_HINTS, typ=xproto.GetPropertyType.Any, unpack=int) # self.log.error("WM_HINTS: {}".format(l)) if not l: return flags = set(k for k, v in HintsFlags.items() if l[0] & v) hints = dict( flags=flags, input=l[1], initial_state=l[2], icon_pixmap=l[3], icon_window=l[4], icon_x=l[5], icon_y=l[6], icon_mask=l[7], window_group=l[8] ) self.log.error("parsed hints: {}".format(hints)) if "InputHint" not in hints['flags']: self.log.notice("input hint off") self.can_focus = False self.hints = hints self.flags = flags def __lt__(self, other): # used for sorting and comparison return True def __repr__(self): name = self.name if len(name) > 20: name = name[:17] + '...' return "Window(%s, \"%s\")" % (self.wid, name)
def __init__(self, conn, root): self.conn = conn self.root = root self.log = Log("mouse")
class Desktop: """ Support for virtual desktops. """ def __init__(self, id, windows=None, name=None): self.id = id if not name: name = "(desktop %s)" % id(self) self.log = Log("desktop %s" % name) if not windows: windows = [] self.windows = windows self.name = name self.cur_focus = None self.prev_focus = None self.were_mapped = [] self.hidden = True # TODO: rename to active def show(self): self.hidden = False for window in self.were_mapped: self.log.debug("showing window %s" % window) window.show() else: self.log.debug("no windows on this desktop to show") self.were_mapped.clear() if self.cur_focus: self.cur_focus.focus() # TODO: self.wm.root.set_prop('_NET_CURRENT_DESKTOP', self.id) def hide(self): self.hidden = True for window in self.windows: if window.mapped: self.log.debug("hiding window %s" % window) window.hide() self.were_mapped.append(window) self.log.debug("followind windows were hidden: %s" % self.were_mapped) def add(self, window): self.windows.append(window) if self.hidden: self.were_mapped.append(window) else: window.show() window.focus() self.cur_focus = window def remove(self, window): if window not in self.windows: self.log.error("NO WINDOW %s" % window) self.log.error("current windows: %s", self.windows) return self.windows.remove(window) if not self.hidden: window.hide() if window == self.cur_focus: self.cur_focus = None def focus_on(self, window, warp=False): assert window in self.windows, "window %s is not on current desktop" % window assert not self.hidden, "cannot focus while desktop is hidden" # if self.cur_focus: # self.cur_focus.lower() # Achtung! Order here is very important or focus will now work # correctly self.log("focusing on %s" % window) if warp: window.show() window.rise() window.warp() window.focus() self.cur_focus = window def __repr__(self): return "Desktop(%s)" % self.name
from useful.prettybt import prettybt sys.excepthook = prettybt # USEFUL ALIASES up, down, left, right = 'Up', 'Down', 'Left', 'Right' win = fail = 'mod4' ctrl = control = 'control' shift = 'shift' caps = 'Caps_Lock' alt = 'mod1' tab = 'Tab' MouseL = 1 MouseC = 2 MouseR = 3 log = Log("USER HOOKS") osd = OSD() mod = win # PRE-INIT # switch to english just in case run_("setxkbmap -layout en") # create event loop and setup text GUI loop = asyncio.new_event_loop() # logwidget = gui(loop=loop) # Log.file = logwidget # INIT num_desktops = 4
class Scheduler(Thread): """ Schedules and executes tasks using threaded workers. """ def __init__(self, workers=5, histlen=10000): super().__init__(daemon=True) self.pending = PriorityQueue() self.ready = Queue() # checks to be executed self.timer = MyTimer(0) # timer placeholder for .schedule() so it can call self.timer.cancel() during the first call self.lock = Lock() # lock pending queue self.lockev = Event() # set by .run() when lock is acquired self.history = deque(maxlen=histlen) # keep track of the history self.log = Log("scheduler") for i in range(workers): worker = Worker(self) worker.start() def flush(self): """ Request immidiate check. """ self.log.debug("flushing pending queue") with self.lock: self.lockev.clear() self.pending.put((-1000, None)) self.timer.cancel() self.lockev.wait() queued = [] while True: try: _, check = self.pending.get(block=False) queued.append(check) except Empty: break for checker in queued: self.ready.put(checker) self.log.debug("flushing done") def schedule(self, checker): t = checker.get_next_check() with self.lock: self.pending.put((t, checker)) self.timer.cancel() def run(self): pending = self.pending ready = self.ready while True: t, c = pending.get() if c is None: self.lockev.set() with self.lock: continue with self.lock: delta = t - time.time() self.log.debug("sleeping for %.2f" % delta) self.timer = MyTimer(delta) self.timer.start() try: self.timer.join() ready.put(c) except TimerCanceled: self.log.debug("new item scheduled, restarting scheduler (this is normal)") pending.put((t, c))
def __init__(self, scheduler): super().__init__(daemon=True) self.scheduler = scheduler self.log = Log("worker %s" % self.name)