Full Code of BurntSushi/pytyle3 for AI

master 8a1ca4189659 cached
17 files
35.6 KB
9.6k tokens
82 symbols
1 requests
Download .txt
Repository: BurntSushi/pytyle3
Branch: master
Commit: 8a1ca4189659
Files: 17
Total size: 35.6 KB

Directory structure:
gitextract__fk6cinx/

├── COPYING
├── INSTALL
├── README
├── config.py
├── keybind.py
├── pt3/
│   ├── __init__.py
│   ├── client.py
│   ├── config.py
│   ├── debug.py
│   ├── keybind.py
│   ├── layouts/
│   │   ├── __init__.py
│   │   ├── layout_vert_horz.py
│   │   └── store.py
│   ├── state.py
│   └── tile.py
├── pytyle3
└── setup.py

================================================
FILE CONTENTS
================================================

================================================
FILE: COPYING
================================================
            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
                    Version 2, December 2004

 Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>

 Everyone is permitted to copy and distribute verbatim or modified
 copies of this license document, and changing it is allowed as long
 as the name is changed.

            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. You just DO WHAT THE FUCK YOU WANT TO.


================================================
FILE: INSTALL
================================================
It should be as straight-forward as

  sudo python2 setup.py install

Two configuration files, config.py and keybind.py will be copied to 
/etc/xdg/pytyle3. 

To configure pytyle3 on a user basis, create ~/.config/pytyle3 and copy 
/etc/xdg/pytyle3/*py to that directory.

pytyle3 will require a restart if the configuration has changed.



================================================
FILE: README
================================================
An updated (and much faster) version of pytyle that uses xpybutil and is
compatible with Openbox Multihead.

Due to using xpybutil much of the lower level XCB/xpyb stuff has been factored 
out of pytyle3. Also, because it relies on Openbox Multihead when there is more 
than one monitor active, a lot less state needs to be saved.

As a result, pytyle3 is ~1000 lines while pytyle2 is ~7000 lines. Additionally, 
because of a simpler design, pytyle3's memory footprint is much smaller and is 
actually quite snappier in moving windows on the screen.



================================================
FILE: config.py
================================================
# A list of windows to ignore... 
# will search both the class and role of the WM_CLASS property
# case-insensitive
ignore = ['gmrun', 'qjackctl', 'viewnior', 'gnome-screenshot', 'mplayer', 'file-roller']

# If this list is non-empty, only windows in the list will be tiled.
# The matching algorithm is precisely the same as for 'ignore'.
tile_only = []

# Whether to enable tiling on startup
tile_on_startup = False

# Whether tiled windows are below others
tiles_below = True

# Whether new windows should tile or float by default (default is False)
floats_default = True

# Whether tiled windows should have their decorations removed
remove_decorations = False

# How much to increment the master area proportion size
proportion_change = 0.05

# If you have panels that don't set struts (*ahem* JWM's panel), then
# setting a margin is the only way to force pytyle not to cover your panels.
# IMPORTANT NOTE: If you set *any* margin, pytyle3 will automatically skip
# all strut auto-detection. So your margins should account for all panels, even
# if the others set struts.
# The format here is to have one set of margins for each active physical
# head. They should be in the following order: Left to right, top to bottom.
# Make sure to set 'use_margins' to True!
use_margins = False
margins = [ {'top': 0, 'bottom': 1, 'left': 0, 'right': 0} ]

# Leave some empty space between windows
gap = 0

# Whether to send any debug information to stdout
debug = False



================================================
FILE: keybind.py
================================================
# This is a python script. Pay attention to the syntax and indentation
import state
import tile

bindings = {
# You can use Control and Shift. Alt is Mod1, Super is Mod4.

#Available commands :
# tile: start tiling
# untile: stop tiling and move the windows back to their original position
# cycle: switch between horizontal and vertical tiling

# increase_master: increase the space allocated to master windows
# decrease_master: increase the space allocated to slave windows
# add_master: send a window from the slave group to the master group
# remove_master: send a window from the master group to the slave group

# prev_client: Focus the previous window
# next_client: Focus the next window
# focus_master: Focus the master window

# switch_prev_client: switch active window with previous
# switch_next_client: switch active window with next
# rotate: shift all windows' positions (clockwise)
# make_master: send active window to the master position

	'Control-Mod1-v': tile.cmd('tile'),
    'Control-Mod1-BackSpace': tile.cmd('untile'),
    'Control-Mod1-s': tile.cmd('decrease_master'),
    'Control-Mod1-r': tile.cmd('increase_master'),
    'Control-Mod1-g': tile.cmd('remove_master'),
    'Control-Mod1-d': tile.cmd('add_master'),
	'Control-Mod1-c': tile.cmd('rotate'),
    'Control-Mod1-h': tile.cmd('cycle'),
    'Control-Mod1-f': tile.cmd('toggle_float'),

# quit pytyle
    'Control-Mod1-q': state.quit,
}



================================================
FILE: pt3/__init__.py
================================================


================================================
FILE: pt3/client.py
================================================
import time

import xcb.xproto

import xpybutil
import xpybutil.event as event
import xpybutil.ewmh as ewmh
import xpybutil.motif as motif
import xpybutil.icccm as icccm
import xpybutil.rect as rect
import xpybutil.util as util
import xpybutil.window as window

from debug import debug

import config
import state
import tile

clients = {}
ignore = [] # Some clients are never gunna make it...

class Client(object):
    def __init__(self, wid):
        self.wid = wid

        self.name = ewmh.get_wm_name(self.wid).reply() or 'N/A'
        debug('Connecting to %s' % self)

        window.listen(self.wid, 'PropertyChange', 'FocusChange')
        event.connect('PropertyNotify', self.wid, self.cb_property_notify)
        event.connect('FocusIn', self.wid, self.cb_focus_in)
        event.connect('FocusOut', self.wid, self.cb_focus_out)

        # This connects to the parent window (decorations)
        # We get all resize AND move events... might be too much
        self.parentid = window.get_parent_window(self.wid)
        window.listen(self.parentid, 'StructureNotify')
        event.connect('ConfigureNotify', self.parentid, 
                      self.cb_configure_notify)

        # A window should only be floating if that is default
        self.floating = getattr(config, 'floats_default', False)

        # Not currently in a "moving" state
        self.moving = False

        # Load some data
        self.desk = ewmh.get_wm_desktop(self.wid).reply()

        # Add it to this desktop's tilers
        tile.update_client_add(self)

        # First cut at saving client geometry
        self.save()

    def remove(self):
        tile.update_client_removal(self)
        debug('Disconnecting from %s' % self)
        event.disconnect('ConfigureNotify', self.parentid)
        event.disconnect('PropertyNotify', self.wid)
        event.disconnect('FocusIn', self.wid)
        event.disconnect('FocusOut', self.wid)

    def activate(self):
        ewmh.request_active_window_checked(self.wid, source=1).check()

    def unmaximize(self):
        vatom = util.get_atom('_NET_WM_STATE_MAXIMIZED_VERT')
        hatom = util.get_atom('_NET_WM_STATE_MAXIMIZED_HORZ')
        ewmh.request_wm_state_checked(self.wid, 0, vatom, hatom).check()

    def save(self):
        self.saved_geom = window.get_geometry(self.wid)
        self.saved_state = ewmh.get_wm_state(self.wid).reply()

    def restore(self):
        debug('Restoring %s' % self)
        if getattr(config, 'remove_decorations', False):
            motif.set_hints_checked(self.wid,2,decoration=1).check()
        if getattr(config, 'tiles_below', False):
            ewmh.request_wm_state_checked(self.wid,0,util.get_atom('_NET_WM_STATE_BELOW')).check()
        if self.saved_state:
            fullymaxed = False
            vatom = util.get_atom('_NET_WM_STATE_MAXIMIZED_VERT')
            hatom = util.get_atom('_NET_WM_STATE_MAXIMIZED_HORZ')

            if vatom in self.saved_state and hatom in self.saved_state:
                fullymaxed = True
                ewmh.request_wm_state_checked(self.wid, 1, vatom, hatom).check()
            elif vatom in self.saved_state:
                ewmh.request_wm_state_checked(self.wid, 1, vatom).check()
            elif hatom in self.saved_state:
                ewmh.request_wm_state_checked(self.wid, 1, hatom).check()

            # No need to continue if we've fully maximized the window
            if fullymaxed:
                return
            
        mnow = rect.get_monitor_area(window.get_geometry(self.wid),
                                     state.monitors)
        mold = rect.get_monitor_area(self.saved_geom, state.monitors)

        x, y, w, h = self.saved_geom

        # What if the client is on a monitor different than what it was before?
        # Use the same algorithm in Openbox to convert one monitor's 
        # coordinates to another.
        if mnow != mold:
            nowx, nowy, noww, nowh = mnow
            oldx, oldy, oldw, oldh = mold

            xrat, yrat = float(noww) / float(oldw), float(nowh) / float(oldh)

            x = nowx + (x - oldx) * xrat
            y = nowy + (y - oldy) * yrat
            w *= xrat
            h *= yrat

        window.moveresize(self.wid, x, y, w, h)

    def moveresize(self, x=None, y=None, w=None, h=None):
        # Ignore this if the user is moving the window...
        if self.moving:
            print 'Sorry but %s is moving...' % self
            return

        try:
            window.moveresize(self.wid, x, y, w, h)
        except:
            pass

    def is_button_pressed(self):
        try:
            pointer = xpybutil.conn.core.QueryPointer(self.wid).reply()
            if pointer is None:
                return False

            if (xcb.xproto.KeyButMask.Button1 & pointer.mask or
                xcb.xproto.KeyButMask.Button3 & pointer.mask):
                return True
        except xcb.xproto.BadWindow:
            pass

        return False

    def cb_focus_in(self, e):
        if self.moving and e.mode == xcb.xproto.NotifyMode.Ungrab:
            state.GRAB = None
            self.moving = False
            tile.update_client_moved(self)

    def cb_focus_out(self, e):
        if e.mode == xcb.xproto.NotifyMode.Grab:
            state.GRAB = self

    def cb_configure_notify(self, e):
        if state.GRAB is self and self.is_button_pressed():
            self.moving = True

    def cb_property_notify(self, e):
        aname = util.get_atom_name(e.atom)

        try:
            if aname == '_NET_WM_DESKTOP':
                if should_ignore(self.wid):
                    untrack_client(self.wid)
                    return

                olddesk = self.desk
                self.desk = ewmh.get_wm_desktop(self.wid).reply()

                if self.desk is not None and self.desk != olddesk:
                    tile.update_client_desktop(self, olddesk)
                else:
                    self.desk = olddesk
            elif aname == '_NET_WM_STATE':
                if should_ignore(self.wid):
                    untrack_client(self.wid)
                    return
        except xcb.xproto.BadWindow:
            pass # S'ok...

    def __str__(self):
        return '{%s (%d)}' % (self.name[0:30], self.wid)

def update_clients():
    client_list = ewmh.get_client_list_stacking().reply()
    client_list = list(reversed(client_list))
    for c in client_list:
        if c not in clients:
            track_client(c)
    for c in clients.keys():
        if c not in client_list:
            untrack_client(c)

def track_client(client):
    assert client not in clients

    try:
        if not should_ignore(client):
            if state.PYTYLE_STATE == 'running':
                # This is truly unfortunate and only seems to be necessary when
                # a client comes back from an iconified state. This causes a 
                # slight lag when a new window is mapped, though.
                time.sleep(0.2)

            clients[client] = Client(client)
    except xcb.xproto.BadWindow:
        debug('Window %s was destroyed before we could finish inspecting it. '
              'Untracking it...' % client)
        untrack_client(client)

def untrack_client(client):
    if client not in clients:
        return

    c = clients[client]
    del clients[client]
    c.remove()

def should_ignore(client):
    # Don't waste time on clients we'll never possibly tile
    if client in ignore:
        return True

    nm = ewmh.get_wm_name(client).reply()

    wm_class = icccm.get_wm_class(client).reply()
    if wm_class is not None:
        try:
            inst, cls = wm_class
            matchNames = set([inst.lower(), cls.lower()])

            if matchNames.intersection(config.ignore):
                debug('Ignoring %s because it is in the ignore list' % nm)
                return True

            if hasattr(config, 'tile_only') and config.tile_only:
              if not matchNames.intersection(config.tile_only):
                debug('Ignoring %s because it is not in the tile_only '
                      'list' % nm)
                return True
        except ValueError:
            pass

    if icccm.get_wm_transient_for(client).reply() is not None:
        debug('Ignoring %s because it is transient' % nm)
        ignore.append(client)
        return True

    wtype = ewmh.get_wm_window_type(client).reply()
    if wtype:
        for atom in wtype:
            aname = util.get_atom_name(atom)

            if aname in ('_NET_WM_WINDOW_TYPE_DESKTOP',
                         '_NET_WM_WINDOW_TYPE_DOCK',
                         '_NET_WM_WINDOW_TYPE_TOOLBAR',
                         '_NET_WM_WINDOW_TYPE_MENU',
                         '_NET_WM_WINDOW_TYPE_UTILITY',
                         '_NET_WM_WINDOW_TYPE_SPLASH',
                         '_NET_WM_WINDOW_TYPE_DIALOG',
                         '_NET_WM_WINDOW_TYPE_DROPDOWN_MENU',
                         '_NET_WM_WINDOW_TYPE_POPUP_MENU',
                         '_NET_WM_WINDOW_TYPE_TOOLTIP',
                         '_NET_WM_WINDOW_TYPE_NOTIFICATION',
                         '_NET_WM_WINDOW_TYPE_COMBO', 
                         '_NET_WM_WINDOW_TYPE_DND'):
                debug('Ignoring %s because it has type %s' % (nm, aname))
                ignore.append(client)
                return True

    wstate = ewmh.get_wm_state(client).reply()
    if wstate is None:
        debug('Ignoring %s because it does not have a state' % nm)
        return True

    for atom in wstate:
        aname = util.get_atom_name(atom)

        # For now, while I decide how to handle these guys
        if aname == '_NET_WM_STATE_STICKY':
            debug('Ignoring %s because it is sticky and they are weird' % nm)
            return True
        if aname in ('_NET_WM_STATE_SHADED', '_NET_WM_STATE_HIDDEN',
                     '_NET_WM_STATE_FULLSCREEN', '_NET_WM_STATE_MODAL'):
            debug('Ignoring %s because it has state %s' % (nm, aname))
            return True

    d = ewmh.get_wm_desktop(client).reply()
    if d == 0xffffffff:
        debug('Ignoring %s because it\'s on all desktops' \
              '(not implemented)' % nm)
        return True

    return False

def cb_property_notify(e):
    aname = util.get_atom_name(e.atom)

    if aname == '_NET_CLIENT_LIST_STACKING':
        update_clients()

event.connect('PropertyNotify', xpybutil.root, cb_property_notify)



================================================
FILE: pt3/config.py
================================================
import os
import os.path
import sys

xdg = os.getenv('XDG_CONFIG_HOME') or os.path.join(os.getenv('HOME'), '.config')
conffile = os.path.join(xdg, 'pytyle3', 'config.py')

if not os.access(conffile, os.R_OK):
    conffile = os.path.join('/', 'etc', 'xdg', 'pytyle3', 'config.py')
    if not os.access(conffile, os.R_OK):
        print >> sys.stderr, 'UNRECOVERABLE ERROR: ' \
                             'No configuration file found at %s' % conffile
        sys.exit(1)

execfile(conffile)



================================================
FILE: pt3/debug.py
================================================
import sys

import config

def debug(s):
    if not config.debug:
        return
    print s
    sys.stdout.flush()



================================================
FILE: pt3/keybind.py
================================================
import os
import os.path
import sys

# from xpybutil import conn, root 
# import xpybutil.event as event 
import xpybutil.keybind as keybind

bindings = None

#####################
# Get key bindings
xdg = os.getenv('XDG_CONFIG_HOME') or os.path.join(os.getenv('HOME'), '.config')
conffile = os.path.join(xdg, 'pytyle3', 'keybind.py')

if not os.access(conffile, os.R_OK):
    conffile = os.path.join('/', 'etc', 'xdg', 'pytyle3', 'keybind.py')
    if not os.access(conffile, os.R_OK):
        print >> sys.stderr, 'UNRECOVERABLE ERROR: ' \
                             'No configuration file found at %s' % conffile
        sys.exit(1)

execfile(conffile)
#####################

assert bindings is not None

for key_string, fun in bindings.iteritems():
    if not keybind.bind_global_key('KeyPress', key_string, fun):
        print >> sys.stderr, 'Could not bind %s' % key_string



================================================
FILE: pt3/layouts/__init__.py
================================================
import abc

import pt3.state as state

class Layout(object):
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def __init__(self, desk):
        self.desk = desk # Should never change
        self.active = False
        self.tiling = False

    @abc.abstractmethod
    def add(self, c): pass

    @abc.abstractmethod
    def remove(self, c): pass

    @abc.abstractmethod
    def tile(self, save=True):
        if not self.active or self.desk not in state.visibles:
            return False

        if not self.tiling and save:
            for c in self.clients():
                c.save()
#                c.unmaximize()

        for c in self.clients(): #unmaximize all windows while tiling
            c.unmaximize()
        self.tiling = True

        return True

    @abc.abstractmethod
    def untile(self): pass

    @abc.abstractmethod
    def next_client(self): pass

    @abc.abstractmethod
    def switch_next_client(self): pass

    @abc.abstractmethod
    def prev_client(self): pass

    @abc.abstractmethod
    def switch_prev_client(self): pass

    @abc.abstractmethod
    def clients(self): pass

    def get_workarea(self):
        if self.desk not in state.visibles:
            return None

        mon = state.workarea[state.visibles.index(self.desk)]

        return mon

    def __str__(self):
        wa = self.get_workarea()
        if wa is None:
            wastr = 'which isn\'t visible'
        else:
            wx, wy, ww, wh = wa
            wastr = '%dx%d+%d+%d' % (ww, wh, wx, wy)

        istiling = '- TILING' if self.tiling else ''

        return '%s (desk %d) %s%s' % (
                    self.__class__.__name__, self.desk, wastr, istiling)

from layout_vert_horz import VerticalLayout, HorizontalLayout

layouts = [VerticalLayout, HorizontalLayout]



================================================
FILE: pt3/layouts/layout_vert_horz.py
================================================
import xpybutil

from pt3.debug import debug

import pt3.config as config
import pt3.client as client
import pt3.state as state
from pt3.layouts import Layout

import store

class OrientLayout(Layout):
    # Start implementing abstract methods
    def __init__(self, desk):
        super(OrientLayout, self).__init__(desk)
        self.store = store.Store()
        self.proportion = 0.5

    def add(self, c):
        debug('%s being added to %s' % (c, self))
        self.store.add(c)

        if self.tiling:
            self.tile()

    def remove(self, c):
        debug('%s being removed from %s' % (c, self))
        self.store.remove(c)

        if self.tiling:
            self.tile()

    def untile(self):
        debug('Untiling %s' % (self))
        for c in self.store.masters + self.store.slaves:
			c.restore()

        self.tiling = False
        xpybutil.conn.flush()

    def next_client(self):
        nxt = self._get_next()
        if nxt:
            nxt.activate()

    def switch_next_client(self):
        assert self.tiling

        awin = self._get_focused()
        nxt = self._get_next()
        if None not in (awin, nxt):
            self.store.switch(awin, nxt)
            self.tile()

    def prev_client(self):
        prv = self._get_prev()
        if prv:
            prv.activate()

    def switch_prev_client(self):
        assert self.tiling

        awin = self._get_focused()
        prv = self._get_prev()
        if None not in (awin, prv):
            self.store.switch(awin, prv)
            self.tile()
            
    def rotate(self):
		assert self.tiling
		
		self.store.slaves.insert(0,self.store.masters.pop(0)) # move the first master to slave
		self.store.masters.append(self.store.slaves.pop(-1)) # move the last slave to master
		self.tile()

    def clients(self):
        return self.store.masters + self.store.slaves

    # End abstract methods; begin OrientLayout specific methods

    def decrease_master(self):
        self.proportion = max(0.0, self.proportion - config.proportion_change)
        self.tile()

    def increase_master(self):
        self.proportion = min(1.0, self.proportion + config.proportion_change)
        self.tile()

    def add_master(self):
        assert self.tiling

        self.store.inc_masters(self._get_focused())
        self.tile()

    def remove_master(self):
        assert self.tiling

        self.store.dec_masters(self._get_focused())
        self.tile()

    def make_master(self):
        assert self.tiling

        if not self.store.masters: # no masters right now, so don't add any!
            return

        awin = self._get_focused()
        if awin is None:
            return

        self.store.switch(self.store.masters[0], awin)
        self.tile()

    def focus_master(self):
        assert self.tiling

        if not self.store.masters:
            return

        self.store.masters[0].activate()

    def toggle_float(self):
        assert self.tiling

        self.store.toggle_float(self._get_focused())
        self.tile()
    
    # Begin private methods that should not be called by the user directly

    def _get_focused(self):
        
        if type(state.activewin) == list:
            state.activewin = state.activewin[0]
        
        if state.activewin not in client.clients:
            return None

        awin = client.clients[state.activewin]
        if awin not in self.store.masters + self.store.slaves + self.store.floats:
            return None

        return awin

    def _get_next(self):
        ms, ss = self.store.masters, self.store.slaves
        awin = self._get_focused()
        if awin is None:
            return None

        nxt = None
        try:
            i = ms.index(awin)
            if i == 0:
                nxt = ss[0] if ss else ms[-1]
            else:
                nxt = ms[i - 1]
        except ValueError:
            i = ss.index(awin)
            if i == len(ss) - 1:
                nxt = ms[-1] if ms else ss[0]
            else:
                nxt = ss[i + 1]

        return nxt

    def _get_prev(self):
        ms, ss = self.store.masters, self.store.slaves
        awin = self._get_focused()
        if awin is None:
            return None

        prv = None
        try:
            i = ms.index(awin)
            if i == len(ms) - 1:
                prv = ss[-1] if ss else ms[0]
            else:
                prv = ms[i + 1]
        except ValueError:
            i = ss.index(awin)
            if i == 0:
                prv = ms[0] if ms else ss[-1]
            else:
                prv = ss[i - 1]

        return prv

class VerticalLayout(OrientLayout):
    def tile(self, save=True):
        if not super(VerticalLayout, self).tile(save):
            return

        wx, wy, ww, wh = self.get_workarea()
        msize = len(self.store.masters)
        ssize = len(self.store.slaves)

        if not msize and not ssize:
            return

        mx = wx # left limit
        mw = int(ww * self.proportion) # width of the master
        sx = mx + mw
        sw = ww - mw
        g = config.gap # Gap between windows

        if mw <= 0 or mw > ww or sw <= 0 or sw > ww:
            return
		
#Masters
        if msize:
            mh = (wh - (msize + 1) * g) / msize # Height of each window
            mw = ww if not ssize else mw
            for i, c in enumerate(self.store.masters): # i is the number of the window in the list
                c.moveresize(x=mx + g, y= g + wy + i * (mh + g), w=mw - 2 * g, h=mh)

# Slaves
        if ssize:
            sh = (wh - (ssize + 1) * g)/ ssize # Height of each window
            if not msize:
                sx, sw = wx, ww
            for i, c in enumerate(self.store.slaves):
                c.moveresize(x=sx, y= g + wy + i * (sh + g), w=sw - g, h=sh)

        xpybutil.conn.flush()

class HorizontalLayout(OrientLayout):
    def tile(self, save=True):
        if not super(HorizontalLayout, self).tile(save):
            return

        wx, wy, ww, wh = self.get_workarea()
        msize = len(self.store.masters)
        ssize = len(self.store.slaves)

        if not msize and not ssize:
            return

        my = wy
        mh = int(wh * self.proportion)
        sy = my + mh
        sh = wh - mh
        g = config.gap # Gap between windows

        if mh <= 0 or mh > wh or sh <= 0 or sh > wh:
            return

# Masters
        if msize:
            #mw = ww / msize
            mw = (ww - (msize + 1) * g) / msize # Height of each window
            mh = wh if not ssize else mh
            for i, c in enumerate(self.store.masters):
                c.moveresize(x=g + wx + i * (g + mw), y=my + g, w=mw, h=mh - 2 * g)

#Slaves
        if ssize:
            #sw = ww / ssize
            sw = (ww - (ssize + 1) * g) / ssize # Height of each window
            if not msize:
                sy, sh = wy, wh
            for i, c in enumerate(self.store.slaves):
                c.moveresize(x= g + wx + i * (g + sw), y=sy, w=sw, h=sh - g)

        xpybutil.conn.flush()



================================================
FILE: pt3/layouts/store.py
================================================
import pt3.config as config
import xpybutil.ewmh as ewmh
import xpybutil.util as util
import xpybutil.motif as motif

class Store(object):
    def __init__(self):
        self.masters, self.slaves, self.floats = [], [], []
        self.mcnt = 1 # Number of masters allowed

    def add(self, c, above=None):
        if c.floating:
            if getattr(config, 'remove_decorations', False):
                motif.set_hints_checked(c.wid,2,decoration=1).check() # add decorations
            if getattr(config, 'tiles_below', False):
                ewmh.request_wm_state_checked(c.wid,0,util.get_atom('_NET_WM_STATE_BELOW')).check()
            self.floats.append(c)
        else:
            #restore window if maximized
			#ewmh.request_wm_state_checked(c.wid,0,util.get_atom('_NET_WM_STATE_MAXIMIZED_VERT')).check()
			#ewmh.request_wm_state_checked(c.wid,0,util.get_atom('_NET_WM_STATE_MAXIMIZED_HORZ')).check()
            if getattr(config, 'remove_decorations', False):
                motif.set_hints_checked(c.wid,2,decoration=2).check() #remove decorations
            if getattr(config, 'tiles_below', False):
                ewmh.request_wm_state_checked(c.wid,1,util.get_atom('_NET_WM_STATE_BELOW')).check()
            if len(self.masters) < self.mcnt:
                if c in self.slaves:
                    self.slaves.remove(c)
                self.masters.append(c)
            elif c not in self.slaves:
                self.slaves.append(c)

    def remove(self, c):
        if c in self.floats:
            self.floats.remove(c)
        else:
            if c in self.masters:
                self.masters.remove(c)
                if len(self.masters) < self.mcnt and self.slaves:
                    self.masters.append(self.slaves.pop(0))
            elif c in self.slaves:
                self.slaves.remove(c)

    def reset(self):
        self.__init__()

    def inc_masters(self, current=None):
        self.mcnt = min(self.mcnt + 1, len(self))
        if len(self.masters) < self.mcnt and self.slaves:
            try:
                newmast = self.slaves.index(current)
            except ValueError:
                newmast = 0
            self.masters.append(self.slaves.pop(newmast))

    def dec_masters(self, current=None):
        self.mcnt = max(self.mcnt - 1, 0)
        if len(self.masters) > self.mcnt:
            try:
                newslav = self.masters.index(current)
            except ValueError:
                newslav = -1
            self.slaves.append(self.masters.pop(newslav))

    def switch(self, c1, c2):
        ms, ss = self.masters, self.slaves # alias
        if c1 in ms and c2 in ms:
            i1, i2 = ms.index(c1), ms.index(c2)
            ms[i1], ms[i2] = ms[i2], ms[i1]
        elif c1 in self.slaves and c2 in self.slaves:
            i1, i2 = ss.index(c1), ss.index(c2)
            ss[i1], ss[i2] = ss[i2], ss[i1]
        elif c1 in ms: # and c2 in self.slaves
            i1, i2 = ms.index(c1), ss.index(c2)
            ms[i1], ss[i2] = ss[i2], ms[i1]
        else: # c1 in ss and c2 in ms
            i1, i2 = ss.index(c1), ms.index(c2)
            ss[i1], ms[i2] = ms[i2], ss[i1]

    def toggle_float(self, c):
        self.remove(c)
        c.floating = not c.floating
        self.add(c)

    def __len__(self):
        return len(self.masters) + len(self.slaves)

    def __str__(self):
        s = ['Masters: %s' % [str(c) for c in self.masters],
             'Slaves: %s' % [str(c) for c in self.slaves]]
        return '\n'.join(s)



================================================
FILE: pt3/state.py
================================================
import sys
import time

import xpybutil
import xpybutil.event as event
import xpybutil.ewmh as ewmh
import xpybutil.rect as rect
import xpybutil.util as util
import xpybutil.window as window
import xpybutil.xinerama as xinerama

import config

PYTYLE_STATE = 'startup'
GRAB = None

_wmrunning = False

wm = 'N/A'
utilwm = window.WindowManagers.Unknown
while not _wmrunning:
    w = ewmh.get_supporting_wm_check(xpybutil.root).reply()
    if w:
        childw = ewmh.get_supporting_wm_check(w).reply()
        if childw == w:
            _wmrunning = True
            wm = ewmh.get_wm_name(childw).reply()
            if wm.lower() == 'openbox':
                utilwm = window.WindowManagers.Openbox
            elif wm.lower() == 'kwin':
                utilwm = window.WindowManagers.KWin

            print '%s window manager is running...' % wm
            sys.stdout.flush()

    if not _wmrunning:
        time.sleep(1)

root_geom = ewmh.get_desktop_geometry().reply()
monitors = xinerama.get_monitors()
phys_monitors = xinerama.get_physical_mapping(monitors)
desk_num = ewmh.get_number_of_desktops().reply()
activewin = ewmh.get_active_window().reply()
desktop = ewmh.get_current_desktop().reply()
visibles = ewmh.get_visible_desktops().reply() or [desktop]
stacking = ewmh.get_client_list_stacking().reply()
workarea = []

def quit():
    print 'Exiting...'
    import tile
    for tiler in tile.tilers:
        tile.get_active_tiler(tiler)[0].untile()
    sys.exit(0)

def update_workarea():
    '''
    We update the current workarea either by autodetecting struts, or by
    using margins specified in the config file. Never both, though.
    '''
    global workarea

    if hasattr(config, 'use_margins') and config.use_margins:
        workarea = monitors[:]
        for physm, margins in enumerate(config.margins):
            if physm == len(phys_monitors):
                break
            i = phys_monitors[physm]
            mx, my, mw, mh = workarea[i]
            workarea[i] = (mx + margins['left'], my + margins['top'],
                           mw - (margins['left'] + margins['right']),
                           mh - (margins['top'] + margins['bottom']))
    else:
        workarea = rect.monitor_rects(monitors)

def cb_property_notify(e):
    global activewin, desk_num, desktop, monitors, phys_monitors, root_geom, \
           stacking, visibles, workarea

    aname = util.get_atom_name(e.atom)
    if aname == '_NET_DESKTOP_GEOMETRY':
        root_geom = ewmh.get_desktop_geometry().reply()
        monitors = xinerama.get_monitors()
        phys_monitors = xinerama.get_physical_mapping(monitors)
    elif aname == '_NET_ACTIVE_WINDOW':
        activewin = ewmh.get_active_window().reply()
    elif aname == '_NET_CURRENT_DESKTOP':
        desktop = ewmh.get_current_desktop().reply()
        if visibles is None or len(visibles) == 1:
            visibles = [desktop]
    elif aname == '_NET_VISIBLE_DESKTOPS':
        visibles = ewmh.get_visible_desktops().reply()
    elif aname == '_NET_NUMBER_OF_DESKTOPS':
        desk_num = ewmh.get_number_of_desktops().reply()
    elif aname == '_NET_CLIENT_LIST_STACKING':
        stacking = ewmh.get_client_list_stacking().reply()
    elif aname == '_NET_WORKAREA':
        update_workarea()

window.listen(xpybutil.root, 'PropertyChange')
event.connect('PropertyNotify', xpybutil.root, cb_property_notify)

update_workarea()



================================================
FILE: pt3/tile.py
================================================
import xpybutil
import xpybutil.event as event
import xpybutil.util as util

from debug import debug

import state
from layouts import layouts

try:
    from config import tile_on_startup
except ImportError:
    tile_on_startup = False

tilers = {}

def debug_state():
    debug('-' * 45)
    for d in tilers:
        if d not in state.visibles:
            continue
        tiler, _ = get_active_tiler(d)
        debug(tiler)
        debug(tiler.store)
        debug('-' * 45)

def cmd(action):
    def _cmd():
        if state.desktop not in tilers:
            return

        tiler, _ = get_active_tiler(state.desktop)

        if action == 'tile':
            tiler.tile()
        elif tiler.tiling:
            if action == 'cycle':
                cycle_current_tiler()
            else:
                getattr(tiler, action)()

    return _cmd

def cycle_current_tiler():
    assert state.desktop in tilers

    tiler, i = get_active_tiler(state.desktop)
    newtiler = tilers[state.desktop][(i + 1) % len(tilers[state.desktop])]

    tiler.active = False
    tiler.tiling = False
    newtiler.active = True

    debug('Switching tiler from %s to %s on desktop %d' % (
           tiler.__class__.__name__, newtiler.__class__.__name__, 
           state.desktop))

    newtiler.tile(save=False)

def get_active_tiler(desk):
    assert desk in tilers

    for i, tiler in enumerate(tilers[desk]):
        if tiler.active:
            return tiler, i

def update_client_moved(c):
    assert c.desk in tilers

    tiler, _ = get_active_tiler(c.desk)
    if tiler.tiling:
        tiler.tile()

def update_client_desktop(c, olddesk):
    assert c.desk in tilers

    if olddesk in tilers:
        for tiler in tilers[olddesk]:
            tiler.remove(c)

    for tiler in tilers[c.desk]:
        tiler.add(c)

def update_client_add(c):
    assert c.desk in tilers
    
    for tiler in tilers[c.desk]:
        tiler.add(c)

def update_client_removal(c):
    assert c.desk in tilers

    for tiler in tilers[c.desk]:
        tiler.remove(c)

def update_tilers():
    for d in xrange(state.desk_num):
        if d not in tilers:
            debug('Adding tilers to desktop %d' % d)
            tilers[d] = []
            for lay in layouts:
                t = lay(d)
                tilers[d].append(t)
            tilers[d][0].active = True
            if tile_on_startup:
                tilers[d][0].tiling = True
                tilers[d][0].tile()
    for d in tilers.keys():
        if d >= state.desk_num:
            debug('Removing tilers from desktop %d' % d)
            del tilers[d]

def cb_property_notify(e):
    aname = util.get_atom_name(e.atom)

    if aname == '_NET_NUMBER_OF_DESKTOPS':
        update_tilers()
    elif aname == '_NET_CURRENT_DESKTOP':
        if len(state.visibles) == 1:
            tiler, _ = get_active_tiler(state.desktop)
            if tiler.tiling:
                tiler.tile()
    elif aname == '_NET_VISIBLE_DESKTOPS':
        for d in state.visibles:
            tiler, _ = get_active_tiler(d)
            if tiler.tiling:
                tiler.tile()

event.connect('PropertyNotify', xpybutil.root, cb_property_notify)



================================================
FILE: pytyle3
================================================
#!/usr/bin/env python2

import sys

import xpybutil.event as event

import pt3.config as config

# Perhaps we want to debug...
if '--debug' in sys.argv:
    config.debug = True

import pt3.keybind
import pt3.state as state
import pt3.tile as tile
import pt3.client as client

tile.update_tilers()
client.update_clients()

# Get those juices flowing...
state.PYTYLE_STATE = 'running'

# Start the event loop
event.main()



================================================
FILE: setup.py
================================================
import os
import os.path
import sys

from distutils import sysconfig
from distutils.core import setup

try:
    import xpybutil
except:
    print ''
    print 'pytyle3 requires xpybutil'
    print 'See: https://github.com/BurntSushi/xpybutil'
    sys.exit(1)

setup(
    name = 'pytyle3',
    author = 'Andrew Gallant',
    author_email = 'andrew@pytyle.com',
    version = '3.0.0',
    license = 'WTFPL',
    description = 'A new and much more lightweight pytyle that supports Openbox Multihead',
    long_description = 'See README',
    url = 'https://github.com/BurntSushi/pytyle3',
    platforms = 'POSIX',
    packages = ['pt3', 'pt3/layouts'],
    data_files = [('share/doc/pytyle3', ['README', 'COPYING', 'INSTALL']),
                  ('/etc/xdg/pytyle3', 
                   ['config.py', 'keybind.py'])],
    scripts = ['pytyle3']
)

Download .txt
gitextract__fk6cinx/

├── COPYING
├── INSTALL
├── README
├── config.py
├── keybind.py
├── pt3/
│   ├── __init__.py
│   ├── client.py
│   ├── config.py
│   ├── debug.py
│   ├── keybind.py
│   ├── layouts/
│   │   ├── __init__.py
│   │   ├── layout_vert_horz.py
│   │   └── store.py
│   ├── state.py
│   └── tile.py
├── pytyle3
└── setup.py
Download .txt
SYMBOL INDEX (82 symbols across 7 files)

FILE: pt3/client.py
  class Client (line 23) | class Client(object):
    method __init__ (line 24) | def __init__(self, wid):
    method remove (line 57) | def remove(self):
    method activate (line 65) | def activate(self):
    method unmaximize (line 68) | def unmaximize(self):
    method save (line 73) | def save(self):
    method restore (line 77) | def restore(self):
    method moveresize (line 122) | def moveresize(self, x=None, y=None, w=None, h=None):
    method is_button_pressed (line 133) | def is_button_pressed(self):
    method cb_focus_in (line 147) | def cb_focus_in(self, e):
    method cb_focus_out (line 153) | def cb_focus_out(self, e):
    method cb_configure_notify (line 157) | def cb_configure_notify(self, e):
    method cb_property_notify (line 161) | def cb_property_notify(self, e):
    method __str__ (line 184) | def __str__(self):
  function update_clients (line 187) | def update_clients():
  function track_client (line 197) | def track_client(client):
  function untrack_client (line 214) | def untrack_client(client):
  function should_ignore (line 222) | def should_ignore(client):
  function cb_property_notify (line 299) | def cb_property_notify(e):

FILE: pt3/debug.py
  function debug (line 5) | def debug(s):

FILE: pt3/layouts/__init__.py
  class Layout (line 5) | class Layout(object):
    method __init__ (line 9) | def __init__(self, desk):
    method add (line 15) | def add(self, c): pass
    method remove (line 18) | def remove(self, c): pass
    method tile (line 21) | def tile(self, save=True):
    method untile (line 37) | def untile(self): pass
    method next_client (line 40) | def next_client(self): pass
    method switch_next_client (line 43) | def switch_next_client(self): pass
    method prev_client (line 46) | def prev_client(self): pass
    method switch_prev_client (line 49) | def switch_prev_client(self): pass
    method clients (line 52) | def clients(self): pass
    method get_workarea (line 54) | def get_workarea(self):
    method __str__ (line 62) | def __str__(self):

FILE: pt3/layouts/layout_vert_horz.py
  class OrientLayout (line 12) | class OrientLayout(Layout):
    method __init__ (line 14) | def __init__(self, desk):
    method add (line 19) | def add(self, c):
    method remove (line 26) | def remove(self, c):
    method untile (line 33) | def untile(self):
    method next_client (line 41) | def next_client(self):
    method switch_next_client (line 46) | def switch_next_client(self):
    method prev_client (line 55) | def prev_client(self):
    method switch_prev_client (line 60) | def switch_prev_client(self):
    method rotate (line 69) | def rotate(self):
    method clients (line 76) | def clients(self):
    method decrease_master (line 81) | def decrease_master(self):
    method increase_master (line 85) | def increase_master(self):
    method add_master (line 89) | def add_master(self):
    method remove_master (line 95) | def remove_master(self):
    method make_master (line 101) | def make_master(self):
    method focus_master (line 114) | def focus_master(self):
    method toggle_float (line 122) | def toggle_float(self):
    method _get_focused (line 130) | def _get_focused(self):
    method _get_next (line 144) | def _get_next(self):
    method _get_prev (line 166) | def _get_prev(self):
  class VerticalLayout (line 188) | class VerticalLayout(OrientLayout):
    method tile (line 189) | def tile(self, save=True):
  class HorizontalLayout (line 226) | class HorizontalLayout(OrientLayout):
    method tile (line 227) | def tile(self, save=True):

FILE: pt3/layouts/store.py
  class Store (line 6) | class Store(object):
    method __init__ (line 7) | def __init__(self):
    method add (line 11) | def add(self, c, above=None):
    method remove (line 33) | def remove(self, c):
    method reset (line 44) | def reset(self):
    method inc_masters (line 47) | def inc_masters(self, current=None):
    method dec_masters (line 56) | def dec_masters(self, current=None):
    method switch (line 65) | def switch(self, c1, c2):
    method toggle_float (line 80) | def toggle_float(self, c):
    method __len__ (line 85) | def __len__(self):
    method __str__ (line 88) | def __str__(self):

FILE: pt3/state.py
  function quit (line 49) | def quit():
  function update_workarea (line 56) | def update_workarea():
  function cb_property_notify (line 76) | def cb_property_notify(e):

FILE: pt3/tile.py
  function debug_state (line 17) | def debug_state():
  function cmd (line 27) | def cmd(action):
  function cycle_current_tiler (line 44) | def cycle_current_tiler():
  function get_active_tiler (line 60) | def get_active_tiler(desk):
  function update_client_moved (line 67) | def update_client_moved(c):
  function update_client_desktop (line 74) | def update_client_desktop(c, olddesk):
  function update_client_add (line 84) | def update_client_add(c):
  function update_client_removal (line 90) | def update_client_removal(c):
  function update_tilers (line 96) | def update_tilers():
  function cb_property_notify (line 113) | def cb_property_notify(e):
Condensed preview — 17 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (38K chars).
[
  {
    "path": "COPYING",
    "chars": 483,
    "preview": "            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE\n                    Version 2, December 2004\n\n Copyright (C) 200"
  },
  {
    "path": "INSTALL",
    "chars": 339,
    "preview": "It should be as straight-forward as\n\n  sudo python2 setup.py install\n\nTwo configuration files, config.py and keybind.py "
  },
  {
    "path": "README",
    "chars": 551,
    "preview": "An updated (and much faster) version of pytyle that uses xpybutil and is\ncompatible with Openbox Multihead.\n\nDue to usin"
  },
  {
    "path": "config.py",
    "chars": 1466,
    "preview": "# A list of windows to ignore... \n# will search both the class and role of the WM_CLASS property\n# case-insensitive\nigno"
  },
  {
    "path": "keybind.py",
    "chars": 1421,
    "preview": "# This is a python script. Pay attention to the syntax and indentation\nimport state\nimport tile\n\nbindings = {\n# You can "
  },
  {
    "path": "pt3/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "pt3/client.py",
    "chars": 10453,
    "preview": "import time\n\nimport xcb.xproto\n\nimport xpybutil\nimport xpybutil.event as event\nimport xpybutil.ewmh as ewmh\nimport xpybu"
  },
  {
    "path": "pt3/config.py",
    "chars": 493,
    "preview": "import os\nimport os.path\nimport sys\n\nxdg = os.getenv('XDG_CONFIG_HOME') or os.path.join(os.getenv('HOME'), '.config')\nco"
  },
  {
    "path": "pt3/debug.py",
    "chars": 117,
    "preview": "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",
    "chars": 882,
    "preview": "import os\nimport os.path\nimport sys\n\n# from xpybutil import conn, root \n# import xpybutil.event as event \nimport xpybuti"
  },
  {
    "path": "pt3/layouts/__init__.py",
    "chars": 1806,
    "preview": "import abc\n\nimport pt3.state as state\n\nclass Layout(object):\n    __metaclass__ = abc.ABCMeta\n\n    @abc.abstractmethod\n  "
  },
  {
    "path": "pt3/layouts/layout_vert_horz.py",
    "chars": 7052,
    "preview": "import xpybutil\n\nfrom pt3.debug import debug\n\nimport pt3.config as config\nimport pt3.client as client\nimport pt3.state a"
  },
  {
    "path": "pt3/layouts/store.py",
    "chars": 3519,
    "preview": "import pt3.config as config\nimport xpybutil.ewmh as ewmh\nimport xpybutil.util as util\nimport xpybutil.motif as motif\n\ncl"
  },
  {
    "path": "pt3/state.py",
    "chars": 3406,
    "preview": "import sys\nimport time\n\nimport xpybutil\nimport xpybutil.event as event\nimport xpybutil.ewmh as ewmh\nimport xpybutil.rect"
  },
  {
    "path": "pt3/tile.py",
    "chars": 3172,
    "preview": "import xpybutil\nimport xpybutil.event as event\nimport xpybutil.util as util\n\nfrom debug import debug\n\nimport state\nfrom "
  },
  {
    "path": "pytyle3",
    "chars": 421,
    "preview": "#!/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 de"
  },
  {
    "path": "setup.py",
    "chars": 844,
    "preview": "import os\nimport os.path\nimport sys\n\nfrom distutils import sysconfig\nfrom distutils.core import setup\n\ntry:\n    import x"
  }
]

About this extraction

This page contains the full source code of the BurntSushi/pytyle3 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 17 files (35.6 KB), approximately 9.6k tokens, and a symbol index with 82 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!