Exemple #1
0
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
        )
Exemple #2
0
 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
Exemple #3
0
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)
Exemple #4
0
  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
Exemple #5
0
 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]
Exemple #6
0
 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__)
Exemple #7
0
 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))
Exemple #8
0
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))
Exemple #9
0
 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__()
Exemple #10
0
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
Exemple #11
0
 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)
Exemple #12
0
 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])
Exemple #13
0
 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)
Exemple #14
0
 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()
Exemple #15
0
 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()
Exemple #16
0
  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()
Exemple #17
0
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
Exemple #18
0
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)
Exemple #19
0
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)
Exemple #20
0
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()
Exemple #21
0
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}"))
Exemple #22
0
 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])
Exemple #23
0
    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
Exemple #24
0
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
Exemple #25
0
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)
Exemple #26
0
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
Exemple #27
0
    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)
Exemple #28
0
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
Exemple #29
0
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)
Exemple #30
0
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)
Exemple #31
0
#!/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:
Exemple #32
0
 def __init__(self):
     self.cb_map = defaultdict(list)
     self.log = Log("hook")
     self.suppressed = set()
Exemple #33
0
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)
Exemple #34
0
 def __init__(self, conn, root):
     self.conn = conn
     self.root = root
     self.log = Log("mouse")
Exemple #35
0
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
Exemple #36
0
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
Exemple #37
0
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))
Exemple #38
0
 def __init__(self, scheduler):
   super().__init__(daemon=True)
   self.scheduler = scheduler
   self.log = Log("worker %s" % self.name)