[
  {
    "path": "COPYING",
    "content": "            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE\n                    Version 2, December 2004\n\n Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>\n\n Everyone is permitted to copy and distribute verbatim or modified\n copies of this license document, and changing it is allowed as long\n as the name is changed.\n\n            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. You just DO WHAT THE FUCK YOU WANT TO.\n"
  },
  {
    "path": "INSTALL",
    "content": "It should be as straight-forward as\n\n  sudo python2 setup.py install\n\nTwo configuration files, config.py and keybind.py will be copied to \n/etc/xdg/pytyle3. \n\nTo configure pytyle3 on a user basis, create ~/.config/pytyle3 and copy \n/etc/xdg/pytyle3/*py to that directory.\n\npytyle3 will require a restart if the configuration has changed.\n\n"
  },
  {
    "path": "README",
    "content": "An updated (and much faster) version of pytyle that uses xpybutil and is\ncompatible with Openbox Multihead.\n\nDue to using xpybutil much of the lower level XCB/xpyb stuff has been factored \nout of pytyle3. Also, because it relies on Openbox Multihead when there is more \nthan one monitor active, a lot less state needs to be saved.\n\nAs a result, pytyle3 is ~1000 lines while pytyle2 is ~7000 lines. Additionally, \nbecause of a simpler design, pytyle3's memory footprint is much smaller and is \nactually quite snappier in moving windows on the screen.\n\n"
  },
  {
    "path": "config.py",
    "content": "# A list of windows to ignore... \n# will search both the class and role of the WM_CLASS property\n# case-insensitive\nignore = ['gmrun', 'qjackctl', 'viewnior', 'gnome-screenshot', 'mplayer', 'file-roller']\n\n# If this list is non-empty, only windows in the list will be tiled.\n# The matching algorithm is precisely the same as for 'ignore'.\ntile_only = []\n\n# Whether to enable tiling on startup\ntile_on_startup = False\n\n# Whether tiled windows are below others\ntiles_below = True\n\n# Whether new windows should tile or float by default (default is False)\nfloats_default = True\n\n# Whether tiled windows should have their decorations removed\nremove_decorations = False\n\n# How much to increment the master area proportion size\nproportion_change = 0.05\n\n# If you have panels that don't set struts (*ahem* JWM's panel), then\n# setting a margin is the only way to force pytyle not to cover your panels.\n# IMPORTANT NOTE: If you set *any* margin, pytyle3 will automatically skip\n# all strut auto-detection. So your margins should account for all panels, even\n# if the others set struts.\n# The format here is to have one set of margins for each active physical\n# head. They should be in the following order: Left to right, top to bottom.\n# Make sure to set 'use_margins' to True!\nuse_margins = False\nmargins = [ {'top': 0, 'bottom': 1, 'left': 0, 'right': 0} ]\n\n# Leave some empty space between windows\ngap = 0\n\n# Whether to send any debug information to stdout\ndebug = False\n\n"
  },
  {
    "path": "keybind.py",
    "content": "# This is a python script. Pay attention to the syntax and indentation\nimport state\nimport tile\n\nbindings = {\n# You can use Control and Shift. Alt is Mod1, Super is Mod4.\n\n#Available commands :\n# tile: start tiling\n# untile: stop tiling and move the windows back to their original position\n# cycle: switch between horizontal and vertical tiling\n\n# increase_master: increase the space allocated to master windows\n# decrease_master: increase the space allocated to slave windows\n# add_master: send a window from the slave group to the master group\n# remove_master: send a window from the master group to the slave group\n\n# prev_client: Focus the previous window\n# next_client: Focus the next window\n# focus_master: Focus the master window\n\n# switch_prev_client: switch active window with previous\n# switch_next_client: switch active window with next\n# rotate: shift all windows' positions (clockwise)\n# make_master: send active window to the master position\n\n\t'Control-Mod1-v': tile.cmd('tile'),\n    'Control-Mod1-BackSpace': tile.cmd('untile'),\n    'Control-Mod1-s': tile.cmd('decrease_master'),\n    'Control-Mod1-r': tile.cmd('increase_master'),\n    'Control-Mod1-g': tile.cmd('remove_master'),\n    'Control-Mod1-d': tile.cmd('add_master'),\n\t'Control-Mod1-c': tile.cmd('rotate'),\n    'Control-Mod1-h': tile.cmd('cycle'),\n    'Control-Mod1-f': tile.cmd('toggle_float'),\n\n# quit pytyle\n    'Control-Mod1-q': state.quit,\n}\n\n"
  },
  {
    "path": "pt3/__init__.py",
    "content": ""
  },
  {
    "path": "pt3/client.py",
    "content": "import time\n\nimport xcb.xproto\n\nimport xpybutil\nimport xpybutil.event as event\nimport xpybutil.ewmh as ewmh\nimport xpybutil.motif as motif\nimport xpybutil.icccm as icccm\nimport xpybutil.rect as rect\nimport xpybutil.util as util\nimport xpybutil.window as window\n\nfrom debug import debug\n\nimport config\nimport state\nimport tile\n\nclients = {}\nignore = [] # Some clients are never gunna make it...\n\nclass Client(object):\n    def __init__(self, wid):\n        self.wid = wid\n\n        self.name = ewmh.get_wm_name(self.wid).reply() or 'N/A'\n        debug('Connecting to %s' % self)\n\n        window.listen(self.wid, 'PropertyChange', 'FocusChange')\n        event.connect('PropertyNotify', self.wid, self.cb_property_notify)\n        event.connect('FocusIn', self.wid, self.cb_focus_in)\n        event.connect('FocusOut', self.wid, self.cb_focus_out)\n\n        # This connects to the parent window (decorations)\n        # We get all resize AND move events... might be too much\n        self.parentid = window.get_parent_window(self.wid)\n        window.listen(self.parentid, 'StructureNotify')\n        event.connect('ConfigureNotify', self.parentid, \n                      self.cb_configure_notify)\n\n        # A window should only be floating if that is default\n        self.floating = getattr(config, 'floats_default', False)\n\n        # Not currently in a \"moving\" state\n        self.moving = False\n\n        # Load some data\n        self.desk = ewmh.get_wm_desktop(self.wid).reply()\n\n        # Add it to this desktop's tilers\n        tile.update_client_add(self)\n\n        # First cut at saving client geometry\n        self.save()\n\n    def remove(self):\n        tile.update_client_removal(self)\n        debug('Disconnecting from %s' % self)\n        event.disconnect('ConfigureNotify', self.parentid)\n        event.disconnect('PropertyNotify', self.wid)\n        event.disconnect('FocusIn', self.wid)\n        event.disconnect('FocusOut', self.wid)\n\n    def activate(self):\n        ewmh.request_active_window_checked(self.wid, source=1).check()\n\n    def unmaximize(self):\n        vatom = util.get_atom('_NET_WM_STATE_MAXIMIZED_VERT')\n        hatom = util.get_atom('_NET_WM_STATE_MAXIMIZED_HORZ')\n        ewmh.request_wm_state_checked(self.wid, 0, vatom, hatom).check()\n\n    def save(self):\n        self.saved_geom = window.get_geometry(self.wid)\n        self.saved_state = ewmh.get_wm_state(self.wid).reply()\n\n    def restore(self):\n        debug('Restoring %s' % self)\n        if getattr(config, 'remove_decorations', False):\n            motif.set_hints_checked(self.wid,2,decoration=1).check()\n        if getattr(config, 'tiles_below', False):\n            ewmh.request_wm_state_checked(self.wid,0,util.get_atom('_NET_WM_STATE_BELOW')).check()\n        if self.saved_state:\n            fullymaxed = False\n            vatom = util.get_atom('_NET_WM_STATE_MAXIMIZED_VERT')\n            hatom = util.get_atom('_NET_WM_STATE_MAXIMIZED_HORZ')\n\n            if vatom in self.saved_state and hatom in self.saved_state:\n                fullymaxed = True\n                ewmh.request_wm_state_checked(self.wid, 1, vatom, hatom).check()\n            elif vatom in self.saved_state:\n                ewmh.request_wm_state_checked(self.wid, 1, vatom).check()\n            elif hatom in self.saved_state:\n                ewmh.request_wm_state_checked(self.wid, 1, hatom).check()\n\n            # No need to continue if we've fully maximized the window\n            if fullymaxed:\n                return\n            \n        mnow = rect.get_monitor_area(window.get_geometry(self.wid),\n                                     state.monitors)\n        mold = rect.get_monitor_area(self.saved_geom, state.monitors)\n\n        x, y, w, h = self.saved_geom\n\n        # What if the client is on a monitor different than what it was before?\n        # Use the same algorithm in Openbox to convert one monitor's \n        # coordinates to another.\n        if mnow != mold:\n            nowx, nowy, noww, nowh = mnow\n            oldx, oldy, oldw, oldh = mold\n\n            xrat, yrat = float(noww) / float(oldw), float(nowh) / float(oldh)\n\n            x = nowx + (x - oldx) * xrat\n            y = nowy + (y - oldy) * yrat\n            w *= xrat\n            h *= yrat\n\n        window.moveresize(self.wid, x, y, w, h)\n\n    def moveresize(self, x=None, y=None, w=None, h=None):\n        # Ignore this if the user is moving the window...\n        if self.moving:\n            print 'Sorry but %s is moving...' % self\n            return\n\n        try:\n            window.moveresize(self.wid, x, y, w, h)\n        except:\n            pass\n\n    def is_button_pressed(self):\n        try:\n            pointer = xpybutil.conn.core.QueryPointer(self.wid).reply()\n            if pointer is None:\n                return False\n\n            if (xcb.xproto.KeyButMask.Button1 & pointer.mask or\n                xcb.xproto.KeyButMask.Button3 & pointer.mask):\n                return True\n        except xcb.xproto.BadWindow:\n            pass\n\n        return False\n\n    def cb_focus_in(self, e):\n        if self.moving and e.mode == xcb.xproto.NotifyMode.Ungrab:\n            state.GRAB = None\n            self.moving = False\n            tile.update_client_moved(self)\n\n    def cb_focus_out(self, e):\n        if e.mode == xcb.xproto.NotifyMode.Grab:\n            state.GRAB = self\n\n    def cb_configure_notify(self, e):\n        if state.GRAB is self and self.is_button_pressed():\n            self.moving = True\n\n    def cb_property_notify(self, e):\n        aname = util.get_atom_name(e.atom)\n\n        try:\n            if aname == '_NET_WM_DESKTOP':\n                if should_ignore(self.wid):\n                    untrack_client(self.wid)\n                    return\n\n                olddesk = self.desk\n                self.desk = ewmh.get_wm_desktop(self.wid).reply()\n\n                if self.desk is not None and self.desk != olddesk:\n                    tile.update_client_desktop(self, olddesk)\n                else:\n                    self.desk = olddesk\n            elif aname == '_NET_WM_STATE':\n                if should_ignore(self.wid):\n                    untrack_client(self.wid)\n                    return\n        except xcb.xproto.BadWindow:\n            pass # S'ok...\n\n    def __str__(self):\n        return '{%s (%d)}' % (self.name[0:30], self.wid)\n\ndef update_clients():\n    client_list = ewmh.get_client_list_stacking().reply()\n    client_list = list(reversed(client_list))\n    for c in client_list:\n        if c not in clients:\n            track_client(c)\n    for c in clients.keys():\n        if c not in client_list:\n            untrack_client(c)\n\ndef track_client(client):\n    assert client not in clients\n\n    try:\n        if not should_ignore(client):\n            if state.PYTYLE_STATE == 'running':\n                # This is truly unfortunate and only seems to be necessary when\n                # a client comes back from an iconified state. This causes a \n                # slight lag when a new window is mapped, though.\n                time.sleep(0.2)\n\n            clients[client] = Client(client)\n    except xcb.xproto.BadWindow:\n        debug('Window %s was destroyed before we could finish inspecting it. '\n              'Untracking it...' % client)\n        untrack_client(client)\n\ndef untrack_client(client):\n    if client not in clients:\n        return\n\n    c = clients[client]\n    del clients[client]\n    c.remove()\n\ndef should_ignore(client):\n    # Don't waste time on clients we'll never possibly tile\n    if client in ignore:\n        return True\n\n    nm = ewmh.get_wm_name(client).reply()\n\n    wm_class = icccm.get_wm_class(client).reply()\n    if wm_class is not None:\n        try:\n            inst, cls = wm_class\n            matchNames = set([inst.lower(), cls.lower()])\n\n            if matchNames.intersection(config.ignore):\n                debug('Ignoring %s because it is in the ignore list' % nm)\n                return True\n\n            if hasattr(config, 'tile_only') and config.tile_only:\n              if not matchNames.intersection(config.tile_only):\n                debug('Ignoring %s because it is not in the tile_only '\n                      'list' % nm)\n                return True\n        except ValueError:\n            pass\n\n    if icccm.get_wm_transient_for(client).reply() is not None:\n        debug('Ignoring %s because it is transient' % nm)\n        ignore.append(client)\n        return True\n\n    wtype = ewmh.get_wm_window_type(client).reply()\n    if wtype:\n        for atom in wtype:\n            aname = util.get_atom_name(atom)\n\n            if aname in ('_NET_WM_WINDOW_TYPE_DESKTOP',\n                         '_NET_WM_WINDOW_TYPE_DOCK',\n                         '_NET_WM_WINDOW_TYPE_TOOLBAR',\n                         '_NET_WM_WINDOW_TYPE_MENU',\n                         '_NET_WM_WINDOW_TYPE_UTILITY',\n                         '_NET_WM_WINDOW_TYPE_SPLASH',\n                         '_NET_WM_WINDOW_TYPE_DIALOG',\n                         '_NET_WM_WINDOW_TYPE_DROPDOWN_MENU',\n                         '_NET_WM_WINDOW_TYPE_POPUP_MENU',\n                         '_NET_WM_WINDOW_TYPE_TOOLTIP',\n                         '_NET_WM_WINDOW_TYPE_NOTIFICATION',\n                         '_NET_WM_WINDOW_TYPE_COMBO', \n                         '_NET_WM_WINDOW_TYPE_DND'):\n                debug('Ignoring %s because it has type %s' % (nm, aname))\n                ignore.append(client)\n                return True\n\n    wstate = ewmh.get_wm_state(client).reply()\n    if wstate is None:\n        debug('Ignoring %s because it does not have a state' % nm)\n        return True\n\n    for atom in wstate:\n        aname = util.get_atom_name(atom)\n\n        # For now, while I decide how to handle these guys\n        if aname == '_NET_WM_STATE_STICKY':\n            debug('Ignoring %s because it is sticky and they are weird' % nm)\n            return True\n        if aname in ('_NET_WM_STATE_SHADED', '_NET_WM_STATE_HIDDEN',\n                     '_NET_WM_STATE_FULLSCREEN', '_NET_WM_STATE_MODAL'):\n            debug('Ignoring %s because it has state %s' % (nm, aname))\n            return True\n\n    d = ewmh.get_wm_desktop(client).reply()\n    if d == 0xffffffff:\n        debug('Ignoring %s because it\\'s on all desktops' \\\n              '(not implemented)' % nm)\n        return True\n\n    return False\n\ndef cb_property_notify(e):\n    aname = util.get_atom_name(e.atom)\n\n    if aname == '_NET_CLIENT_LIST_STACKING':\n        update_clients()\n\nevent.connect('PropertyNotify', xpybutil.root, cb_property_notify)\n\n"
  },
  {
    "path": "pt3/config.py",
    "content": "import os\nimport os.path\nimport sys\n\nxdg = os.getenv('XDG_CONFIG_HOME') or os.path.join(os.getenv('HOME'), '.config')\nconffile = os.path.join(xdg, 'pytyle3', 'config.py')\n\nif not os.access(conffile, os.R_OK):\n    conffile = os.path.join('/', 'etc', 'xdg', 'pytyle3', 'config.py')\n    if not os.access(conffile, os.R_OK):\n        print >> sys.stderr, 'UNRECOVERABLE ERROR: ' \\\n                             'No configuration file found at %s' % conffile\n        sys.exit(1)\n\nexecfile(conffile)\n\n"
  },
  {
    "path": "pt3/debug.py",
    "content": "import sys\n\nimport config\n\ndef debug(s):\n    if not config.debug:\n        return\n    print s\n    sys.stdout.flush()\n\n"
  },
  {
    "path": "pt3/keybind.py",
    "content": "import os\nimport os.path\nimport sys\n\n# from xpybutil import conn, root \n# import xpybutil.event as event \nimport xpybutil.keybind as keybind\n\nbindings = None\n\n#####################\n# Get key bindings\nxdg = os.getenv('XDG_CONFIG_HOME') or os.path.join(os.getenv('HOME'), '.config')\nconffile = os.path.join(xdg, 'pytyle3', 'keybind.py')\n\nif not os.access(conffile, os.R_OK):\n    conffile = os.path.join('/', 'etc', 'xdg', 'pytyle3', 'keybind.py')\n    if not os.access(conffile, os.R_OK):\n        print >> sys.stderr, 'UNRECOVERABLE ERROR: ' \\\n                             'No configuration file found at %s' % conffile\n        sys.exit(1)\n\nexecfile(conffile)\n#####################\n\nassert bindings is not None\n\nfor key_string, fun in bindings.iteritems():\n    if not keybind.bind_global_key('KeyPress', key_string, fun):\n        print >> sys.stderr, 'Could not bind %s' % key_string\n\n"
  },
  {
    "path": "pt3/layouts/__init__.py",
    "content": "import abc\n\nimport pt3.state as state\n\nclass Layout(object):\n    __metaclass__ = abc.ABCMeta\n\n    @abc.abstractmethod\n    def __init__(self, desk):\n        self.desk = desk # Should never change\n        self.active = False\n        self.tiling = False\n\n    @abc.abstractmethod\n    def add(self, c): pass\n\n    @abc.abstractmethod\n    def remove(self, c): pass\n\n    @abc.abstractmethod\n    def tile(self, save=True):\n        if not self.active or self.desk not in state.visibles:\n            return False\n\n        if not self.tiling and save:\n            for c in self.clients():\n                c.save()\n#                c.unmaximize()\n\n        for c in self.clients(): #unmaximize all windows while tiling\n            c.unmaximize()\n        self.tiling = True\n\n        return True\n\n    @abc.abstractmethod\n    def untile(self): pass\n\n    @abc.abstractmethod\n    def next_client(self): pass\n\n    @abc.abstractmethod\n    def switch_next_client(self): pass\n\n    @abc.abstractmethod\n    def prev_client(self): pass\n\n    @abc.abstractmethod\n    def switch_prev_client(self): pass\n\n    @abc.abstractmethod\n    def clients(self): pass\n\n    def get_workarea(self):\n        if self.desk not in state.visibles:\n            return None\n\n        mon = state.workarea[state.visibles.index(self.desk)]\n\n        return mon\n\n    def __str__(self):\n        wa = self.get_workarea()\n        if wa is None:\n            wastr = 'which isn\\'t visible'\n        else:\n            wx, wy, ww, wh = wa\n            wastr = '%dx%d+%d+%d' % (ww, wh, wx, wy)\n\n        istiling = '- TILING' if self.tiling else ''\n\n        return '%s (desk %d) %s%s' % (\n                    self.__class__.__name__, self.desk, wastr, istiling)\n\nfrom layout_vert_horz import VerticalLayout, HorizontalLayout\n\nlayouts = [VerticalLayout, HorizontalLayout]\n\n"
  },
  {
    "path": "pt3/layouts/layout_vert_horz.py",
    "content": "import xpybutil\n\nfrom pt3.debug import debug\n\nimport pt3.config as config\nimport pt3.client as client\nimport pt3.state as state\nfrom pt3.layouts import Layout\n\nimport store\n\nclass OrientLayout(Layout):\n    # Start implementing abstract methods\n    def __init__(self, desk):\n        super(OrientLayout, self).__init__(desk)\n        self.store = store.Store()\n        self.proportion = 0.5\n\n    def add(self, c):\n        debug('%s being added to %s' % (c, self))\n        self.store.add(c)\n\n        if self.tiling:\n            self.tile()\n\n    def remove(self, c):\n        debug('%s being removed from %s' % (c, self))\n        self.store.remove(c)\n\n        if self.tiling:\n            self.tile()\n\n    def untile(self):\n        debug('Untiling %s' % (self))\n        for c in self.store.masters + self.store.slaves:\n\t\t\tc.restore()\n\n        self.tiling = False\n        xpybutil.conn.flush()\n\n    def next_client(self):\n        nxt = self._get_next()\n        if nxt:\n            nxt.activate()\n\n    def switch_next_client(self):\n        assert self.tiling\n\n        awin = self._get_focused()\n        nxt = self._get_next()\n        if None not in (awin, nxt):\n            self.store.switch(awin, nxt)\n            self.tile()\n\n    def prev_client(self):\n        prv = self._get_prev()\n        if prv:\n            prv.activate()\n\n    def switch_prev_client(self):\n        assert self.tiling\n\n        awin = self._get_focused()\n        prv = self._get_prev()\n        if None not in (awin, prv):\n            self.store.switch(awin, prv)\n            self.tile()\n            \n    def rotate(self):\n\t\tassert self.tiling\n\t\t\n\t\tself.store.slaves.insert(0,self.store.masters.pop(0)) # move the first master to slave\n\t\tself.store.masters.append(self.store.slaves.pop(-1)) # move the last slave to master\n\t\tself.tile()\n\n    def clients(self):\n        return self.store.masters + self.store.slaves\n\n    # End abstract methods; begin OrientLayout specific methods\n\n    def decrease_master(self):\n        self.proportion = max(0.0, self.proportion - config.proportion_change)\n        self.tile()\n\n    def increase_master(self):\n        self.proportion = min(1.0, self.proportion + config.proportion_change)\n        self.tile()\n\n    def add_master(self):\n        assert self.tiling\n\n        self.store.inc_masters(self._get_focused())\n        self.tile()\n\n    def remove_master(self):\n        assert self.tiling\n\n        self.store.dec_masters(self._get_focused())\n        self.tile()\n\n    def make_master(self):\n        assert self.tiling\n\n        if not self.store.masters: # no masters right now, so don't add any!\n            return\n\n        awin = self._get_focused()\n        if awin is None:\n            return\n\n        self.store.switch(self.store.masters[0], awin)\n        self.tile()\n\n    def focus_master(self):\n        assert self.tiling\n\n        if not self.store.masters:\n            return\n\n        self.store.masters[0].activate()\n\n    def toggle_float(self):\n        assert self.tiling\n\n        self.store.toggle_float(self._get_focused())\n        self.tile()\n    \n    # Begin private methods that should not be called by the user directly\n\n    def _get_focused(self):\n        \n        if type(state.activewin) == list:\n            state.activewin = state.activewin[0]\n        \n        if state.activewin not in client.clients:\n            return None\n\n        awin = client.clients[state.activewin]\n        if awin not in self.store.masters + self.store.slaves + self.store.floats:\n            return None\n\n        return awin\n\n    def _get_next(self):\n        ms, ss = self.store.masters, self.store.slaves\n        awin = self._get_focused()\n        if awin is None:\n            return None\n\n        nxt = None\n        try:\n            i = ms.index(awin)\n            if i == 0:\n                nxt = ss[0] if ss else ms[-1]\n            else:\n                nxt = ms[i - 1]\n        except ValueError:\n            i = ss.index(awin)\n            if i == len(ss) - 1:\n                nxt = ms[-1] if ms else ss[0]\n            else:\n                nxt = ss[i + 1]\n\n        return nxt\n\n    def _get_prev(self):\n        ms, ss = self.store.masters, self.store.slaves\n        awin = self._get_focused()\n        if awin is None:\n            return None\n\n        prv = None\n        try:\n            i = ms.index(awin)\n            if i == len(ms) - 1:\n                prv = ss[-1] if ss else ms[0]\n            else:\n                prv = ms[i + 1]\n        except ValueError:\n            i = ss.index(awin)\n            if i == 0:\n                prv = ms[0] if ms else ss[-1]\n            else:\n                prv = ss[i - 1]\n\n        return prv\n\nclass VerticalLayout(OrientLayout):\n    def tile(self, save=True):\n        if not super(VerticalLayout, self).tile(save):\n            return\n\n        wx, wy, ww, wh = self.get_workarea()\n        msize = len(self.store.masters)\n        ssize = len(self.store.slaves)\n\n        if not msize and not ssize:\n            return\n\n        mx = wx # left limit\n        mw = int(ww * self.proportion) # width of the master\n        sx = mx + mw\n        sw = ww - mw\n        g = config.gap # Gap between windows\n\n        if mw <= 0 or mw > ww or sw <= 0 or sw > ww:\n            return\n\t\t\n#Masters\n        if msize:\n            mh = (wh - (msize + 1) * g) / msize # Height of each window\n            mw = ww if not ssize else mw\n            for i, c in enumerate(self.store.masters): # i is the number of the window in the list\n                c.moveresize(x=mx + g, y= g + wy + i * (mh + g), w=mw - 2 * g, h=mh)\n\n# Slaves\n        if ssize:\n            sh = (wh - (ssize + 1) * g)/ ssize # Height of each window\n            if not msize:\n                sx, sw = wx, ww\n            for i, c in enumerate(self.store.slaves):\n                c.moveresize(x=sx, y= g + wy + i * (sh + g), w=sw - g, h=sh)\n\n        xpybutil.conn.flush()\n\nclass HorizontalLayout(OrientLayout):\n    def tile(self, save=True):\n        if not super(HorizontalLayout, self).tile(save):\n            return\n\n        wx, wy, ww, wh = self.get_workarea()\n        msize = len(self.store.masters)\n        ssize = len(self.store.slaves)\n\n        if not msize and not ssize:\n            return\n\n        my = wy\n        mh = int(wh * self.proportion)\n        sy = my + mh\n        sh = wh - mh\n        g = config.gap # Gap between windows\n\n        if mh <= 0 or mh > wh or sh <= 0 or sh > wh:\n            return\n\n# Masters\n        if msize:\n            #mw = ww / msize\n            mw = (ww - (msize + 1) * g) / msize # Height of each window\n            mh = wh if not ssize else mh\n            for i, c in enumerate(self.store.masters):\n                c.moveresize(x=g + wx + i * (g + mw), y=my + g, w=mw, h=mh - 2 * g)\n\n#Slaves\n        if ssize:\n            #sw = ww / ssize\n            sw = (ww - (ssize + 1) * g) / ssize # Height of each window\n            if not msize:\n                sy, sh = wy, wh\n            for i, c in enumerate(self.store.slaves):\n                c.moveresize(x= g + wx + i * (g + sw), y=sy, w=sw, h=sh - g)\n\n        xpybutil.conn.flush()\n\n"
  },
  {
    "path": "pt3/layouts/store.py",
    "content": "import pt3.config as config\nimport xpybutil.ewmh as ewmh\nimport xpybutil.util as util\nimport xpybutil.motif as motif\n\nclass Store(object):\n    def __init__(self):\n        self.masters, self.slaves, self.floats = [], [], []\n        self.mcnt = 1 # Number of masters allowed\n\n    def add(self, c, above=None):\n        if c.floating:\n            if getattr(config, 'remove_decorations', False):\n                motif.set_hints_checked(c.wid,2,decoration=1).check() # add decorations\n            if getattr(config, 'tiles_below', False):\n                ewmh.request_wm_state_checked(c.wid,0,util.get_atom('_NET_WM_STATE_BELOW')).check()\n            self.floats.append(c)\n        else:\n            #restore window if maximized\n\t\t\t#ewmh.request_wm_state_checked(c.wid,0,util.get_atom('_NET_WM_STATE_MAXIMIZED_VERT')).check()\n\t\t\t#ewmh.request_wm_state_checked(c.wid,0,util.get_atom('_NET_WM_STATE_MAXIMIZED_HORZ')).check()\n            if getattr(config, 'remove_decorations', False):\n                motif.set_hints_checked(c.wid,2,decoration=2).check() #remove decorations\n            if getattr(config, 'tiles_below', False):\n                ewmh.request_wm_state_checked(c.wid,1,util.get_atom('_NET_WM_STATE_BELOW')).check()\n            if len(self.masters) < self.mcnt:\n                if c in self.slaves:\n                    self.slaves.remove(c)\n                self.masters.append(c)\n            elif c not in self.slaves:\n                self.slaves.append(c)\n\n    def remove(self, c):\n        if c in self.floats:\n            self.floats.remove(c)\n        else:\n            if c in self.masters:\n                self.masters.remove(c)\n                if len(self.masters) < self.mcnt and self.slaves:\n                    self.masters.append(self.slaves.pop(0))\n            elif c in self.slaves:\n                self.slaves.remove(c)\n\n    def reset(self):\n        self.__init__()\n\n    def inc_masters(self, current=None):\n        self.mcnt = min(self.mcnt + 1, len(self))\n        if len(self.masters) < self.mcnt and self.slaves:\n            try:\n                newmast = self.slaves.index(current)\n            except ValueError:\n                newmast = 0\n            self.masters.append(self.slaves.pop(newmast))\n\n    def dec_masters(self, current=None):\n        self.mcnt = max(self.mcnt - 1, 0)\n        if len(self.masters) > self.mcnt:\n            try:\n                newslav = self.masters.index(current)\n            except ValueError:\n                newslav = -1\n            self.slaves.append(self.masters.pop(newslav))\n\n    def switch(self, c1, c2):\n        ms, ss = self.masters, self.slaves # alias\n        if c1 in ms and c2 in ms:\n            i1, i2 = ms.index(c1), ms.index(c2)\n            ms[i1], ms[i2] = ms[i2], ms[i1]\n        elif c1 in self.slaves and c2 in self.slaves:\n            i1, i2 = ss.index(c1), ss.index(c2)\n            ss[i1], ss[i2] = ss[i2], ss[i1]\n        elif c1 in ms: # and c2 in self.slaves\n            i1, i2 = ms.index(c1), ss.index(c2)\n            ms[i1], ss[i2] = ss[i2], ms[i1]\n        else: # c1 in ss and c2 in ms\n            i1, i2 = ss.index(c1), ms.index(c2)\n            ss[i1], ms[i2] = ms[i2], ss[i1]\n\n    def toggle_float(self, c):\n        self.remove(c)\n        c.floating = not c.floating\n        self.add(c)\n\n    def __len__(self):\n        return len(self.masters) + len(self.slaves)\n\n    def __str__(self):\n        s = ['Masters: %s' % [str(c) for c in self.masters],\n             'Slaves: %s' % [str(c) for c in self.slaves]]\n        return '\\n'.join(s)\n\n"
  },
  {
    "path": "pt3/state.py",
    "content": "import sys\nimport time\n\nimport xpybutil\nimport xpybutil.event as event\nimport xpybutil.ewmh as ewmh\nimport xpybutil.rect as rect\nimport xpybutil.util as util\nimport xpybutil.window as window\nimport xpybutil.xinerama as xinerama\n\nimport config\n\nPYTYLE_STATE = 'startup'\nGRAB = None\n\n_wmrunning = False\n\nwm = 'N/A'\nutilwm = window.WindowManagers.Unknown\nwhile not _wmrunning:\n    w = ewmh.get_supporting_wm_check(xpybutil.root).reply()\n    if w:\n        childw = ewmh.get_supporting_wm_check(w).reply()\n        if childw == w:\n            _wmrunning = True\n            wm = ewmh.get_wm_name(childw).reply()\n            if wm.lower() == 'openbox':\n                utilwm = window.WindowManagers.Openbox\n            elif wm.lower() == 'kwin':\n                utilwm = window.WindowManagers.KWin\n\n            print '%s window manager is running...' % wm\n            sys.stdout.flush()\n\n    if not _wmrunning:\n        time.sleep(1)\n\nroot_geom = ewmh.get_desktop_geometry().reply()\nmonitors = xinerama.get_monitors()\nphys_monitors = xinerama.get_physical_mapping(monitors)\ndesk_num = ewmh.get_number_of_desktops().reply()\nactivewin = ewmh.get_active_window().reply()\ndesktop = ewmh.get_current_desktop().reply()\nvisibles = ewmh.get_visible_desktops().reply() or [desktop]\nstacking = ewmh.get_client_list_stacking().reply()\nworkarea = []\n\ndef quit():\n    print 'Exiting...'\n    import tile\n    for tiler in tile.tilers:\n        tile.get_active_tiler(tiler)[0].untile()\n    sys.exit(0)\n\ndef update_workarea():\n    '''\n    We update the current workarea either by autodetecting struts, or by\n    using margins specified in the config file. Never both, though.\n    '''\n    global workarea\n\n    if hasattr(config, 'use_margins') and config.use_margins:\n        workarea = monitors[:]\n        for physm, margins in enumerate(config.margins):\n            if physm == len(phys_monitors):\n                break\n            i = phys_monitors[physm]\n            mx, my, mw, mh = workarea[i]\n            workarea[i] = (mx + margins['left'], my + margins['top'],\n                           mw - (margins['left'] + margins['right']),\n                           mh - (margins['top'] + margins['bottom']))\n    else:\n        workarea = rect.monitor_rects(monitors)\n\ndef cb_property_notify(e):\n    global activewin, desk_num, desktop, monitors, phys_monitors, root_geom, \\\n           stacking, visibles, workarea\n\n    aname = util.get_atom_name(e.atom)\n    if aname == '_NET_DESKTOP_GEOMETRY':\n        root_geom = ewmh.get_desktop_geometry().reply()\n        monitors = xinerama.get_monitors()\n        phys_monitors = xinerama.get_physical_mapping(monitors)\n    elif aname == '_NET_ACTIVE_WINDOW':\n        activewin = ewmh.get_active_window().reply()\n    elif aname == '_NET_CURRENT_DESKTOP':\n        desktop = ewmh.get_current_desktop().reply()\n        if visibles is None or len(visibles) == 1:\n            visibles = [desktop]\n    elif aname == '_NET_VISIBLE_DESKTOPS':\n        visibles = ewmh.get_visible_desktops().reply()\n    elif aname == '_NET_NUMBER_OF_DESKTOPS':\n        desk_num = ewmh.get_number_of_desktops().reply()\n    elif aname == '_NET_CLIENT_LIST_STACKING':\n        stacking = ewmh.get_client_list_stacking().reply()\n    elif aname == '_NET_WORKAREA':\n        update_workarea()\n\nwindow.listen(xpybutil.root, 'PropertyChange')\nevent.connect('PropertyNotify', xpybutil.root, cb_property_notify)\n\nupdate_workarea()\n\n"
  },
  {
    "path": "pt3/tile.py",
    "content": "import xpybutil\nimport xpybutil.event as event\nimport xpybutil.util as util\n\nfrom debug import debug\n\nimport state\nfrom layouts import layouts\n\ntry:\n    from config import tile_on_startup\nexcept ImportError:\n    tile_on_startup = False\n\ntilers = {}\n\ndef debug_state():\n    debug('-' * 45)\n    for d in tilers:\n        if d not in state.visibles:\n            continue\n        tiler, _ = get_active_tiler(d)\n        debug(tiler)\n        debug(tiler.store)\n        debug('-' * 45)\n\ndef cmd(action):\n    def _cmd():\n        if state.desktop not in tilers:\n            return\n\n        tiler, _ = get_active_tiler(state.desktop)\n\n        if action == 'tile':\n            tiler.tile()\n        elif tiler.tiling:\n            if action == 'cycle':\n                cycle_current_tiler()\n            else:\n                getattr(tiler, action)()\n\n    return _cmd\n\ndef cycle_current_tiler():\n    assert state.desktop in tilers\n\n    tiler, i = get_active_tiler(state.desktop)\n    newtiler = tilers[state.desktop][(i + 1) % len(tilers[state.desktop])]\n\n    tiler.active = False\n    tiler.tiling = False\n    newtiler.active = True\n\n    debug('Switching tiler from %s to %s on desktop %d' % (\n           tiler.__class__.__name__, newtiler.__class__.__name__, \n           state.desktop))\n\n    newtiler.tile(save=False)\n\ndef get_active_tiler(desk):\n    assert desk in tilers\n\n    for i, tiler in enumerate(tilers[desk]):\n        if tiler.active:\n            return tiler, i\n\ndef update_client_moved(c):\n    assert c.desk in tilers\n\n    tiler, _ = get_active_tiler(c.desk)\n    if tiler.tiling:\n        tiler.tile()\n\ndef update_client_desktop(c, olddesk):\n    assert c.desk in tilers\n\n    if olddesk in tilers:\n        for tiler in tilers[olddesk]:\n            tiler.remove(c)\n\n    for tiler in tilers[c.desk]:\n        tiler.add(c)\n\ndef update_client_add(c):\n    assert c.desk in tilers\n    \n    for tiler in tilers[c.desk]:\n        tiler.add(c)\n\ndef update_client_removal(c):\n    assert c.desk in tilers\n\n    for tiler in tilers[c.desk]:\n        tiler.remove(c)\n\ndef update_tilers():\n    for d in xrange(state.desk_num):\n        if d not in tilers:\n            debug('Adding tilers to desktop %d' % d)\n            tilers[d] = []\n            for lay in layouts:\n                t = lay(d)\n                tilers[d].append(t)\n            tilers[d][0].active = True\n            if tile_on_startup:\n                tilers[d][0].tiling = True\n                tilers[d][0].tile()\n    for d in tilers.keys():\n        if d >= state.desk_num:\n            debug('Removing tilers from desktop %d' % d)\n            del tilers[d]\n\ndef cb_property_notify(e):\n    aname = util.get_atom_name(e.atom)\n\n    if aname == '_NET_NUMBER_OF_DESKTOPS':\n        update_tilers()\n    elif aname == '_NET_CURRENT_DESKTOP':\n        if len(state.visibles) == 1:\n            tiler, _ = get_active_tiler(state.desktop)\n            if tiler.tiling:\n                tiler.tile()\n    elif aname == '_NET_VISIBLE_DESKTOPS':\n        for d in state.visibles:\n            tiler, _ = get_active_tiler(d)\n            if tiler.tiling:\n                tiler.tile()\n\nevent.connect('PropertyNotify', xpybutil.root, cb_property_notify)\n\n"
  },
  {
    "path": "pytyle3",
    "content": "#!/usr/bin/env python2\n\nimport sys\n\nimport xpybutil.event as event\n\nimport pt3.config as config\n\n# Perhaps we want to debug...\nif '--debug' in sys.argv:\n    config.debug = True\n\nimport pt3.keybind\nimport pt3.state as state\nimport pt3.tile as tile\nimport pt3.client as client\n\ntile.update_tilers()\nclient.update_clients()\n\n# Get those juices flowing...\nstate.PYTYLE_STATE = 'running'\n\n# Start the event loop\nevent.main()\n\n"
  },
  {
    "path": "setup.py",
    "content": "import os\nimport os.path\nimport sys\n\nfrom distutils import sysconfig\nfrom distutils.core import setup\n\ntry:\n    import xpybutil\nexcept:\n    print ''\n    print 'pytyle3 requires xpybutil'\n    print 'See: https://github.com/BurntSushi/xpybutil'\n    sys.exit(1)\n\nsetup(\n    name = 'pytyle3',\n    author = 'Andrew Gallant',\n    author_email = 'andrew@pytyle.com',\n    version = '3.0.0',\n    license = 'WTFPL',\n    description = 'A new and much more lightweight pytyle that supports Openbox Multihead',\n    long_description = 'See README',\n    url = 'https://github.com/BurntSushi/pytyle3',\n    platforms = 'POSIX',\n    packages = ['pt3', 'pt3/layouts'],\n    data_files = [('share/doc/pytyle3', ['README', 'COPYING', 'INSTALL']),\n                  ('/etc/xdg/pytyle3', \n                   ['config.py', 'keybind.py'])],\n    scripts = ['pytyle3']\n)\n\n"
  }
]