Hello community,

here is the log from the commit of package qtile for openSUSE:Factory checked 
in at 2018-07-31 15:59:02
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/qtile (Old)
 and      /work/SRC/openSUSE:Factory/.qtile.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "qtile"

Tue Jul 31 15:59:02 2018 rev:7 rq:625973 version:0.12.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/qtile/qtile.changes      2018-05-16 
18:44:44.532466801 +0200
+++ /work/SRC/openSUSE:Factory/.qtile.new/qtile.changes 2018-07-31 
15:59:09.987522715 +0200
@@ -1,0 +2,15 @@
+Sat Jul 21 20:35:00 UTC 2018 - [email protected]
+
+- Update to version 0.12.0:
+  * Fix floating bug in bsp layout
+  * Fix name of `test_common`
+  * Fix syntax error in mpd widget
+  * Add error handling to mpd widget
+  * Fix caps lock affected behaviour of key bindings
+  * Use python2/3 switch for os.makedirs
+  * Create cache dir if necessary
+  * Fix typo in stack layout documentation
+  * Fix mypy checks for mypy 0.600
+  * Check for existence of BAT_DIR before listing it
+
+-------------------------------------------------------------------

Old:
----
  qtile-0.11.1+20180513.39ced15a.tar.xz

New:
----
  qtile-0.12.0.tar.xz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ qtile.spec ++++++
--- /var/tmp/diff_new_pack.441z1e/_old  2018-07-31 15:59:10.591523738 +0200
+++ /var/tmp/diff_new_pack.441z1e/_new  2018-07-31 15:59:10.595523746 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           qtile
-Version:        0.11.1+20180513.39ced15a
+Version:        0.12.0
 Release:        0
 Summary:        A pure-Python tiling window manager
 # All MIT except for: libqtile/widget/pacman.py:GPL (v3 or later)

++++++ _service ++++++
--- /var/tmp/diff_new_pack.441z1e/_old  2018-07-31 15:59:10.619523786 +0200
+++ /var/tmp/diff_new_pack.441z1e/_new  2018-07-31 15:59:10.619523786 +0200
@@ -4,8 +4,8 @@
     <param name="scm">git</param>
     <param name="changesgenerate">enable</param>
     <param name="filename">qtile</param>
-    <param name="revision">develop</param>
-    <param name="versionformat">0.11.1+%cd.%h</param>
+    <param name="revision">master</param>
+    <param name="versionformat">0.12.0</param>
   </service>
   <service mode="disabled" name="recompress">
     <param name="file">*.tar</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.441z1e/_old  2018-07-31 15:59:10.631523807 +0200
+++ /var/tmp/diff_new_pack.441z1e/_new  2018-07-31 15:59:10.635523814 +0200
@@ -1,4 +1,4 @@
 <servicedata>
 <service name="tar_scm">
                 <param name="url">https://github.com/qtile/qtile.git</param>
-              <param 
name="changesrevision">39ced15a03a5ffe9903381183cb2fc80b13176e7</param></service></servicedata>
\ No newline at end of file
+              <param 
name="changesrevision">95919912b11eabedb59ad3215747544c48c522f9</param></service></servicedata>
\ No newline at end of file

++++++ qtile-0.11.1+20180513.39ced15a.tar.xz -> qtile-0.12.0.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/qtile-0.11.1+20180513.39ced15a/CHANGELOG 
new/qtile-0.12.0/CHANGELOG
--- old/qtile-0.11.1+20180513.39ced15a/CHANGELOG        2018-05-14 
02:02:22.000000000 +0200
+++ new/qtile-0.12.0/CHANGELOG  2018-07-21 02:02:29.000000000 +0200
@@ -1,4 +1,4 @@
-qtile 0.x.x, released xxxx-xx-xx:
+qtile 0.12.0, released 2018-07-20:
     !!! Config breakage !!!
         - Tile layout commands up/down/shuffle_up/shuffle_down changed to be
           more consistent with other layouts
@@ -7,10 +7,20 @@
     * features
         - add `add_after_last` option to Tile layout to add windows to the end
           of the list.
+        - add new formatting options to TaskList
+        - allow Volume to open app on right click
     * bugfixes
         - fix floating of file transfer windows and java drop-downs
         - fix exception when calling `cmd_next` and `cmd_previous` on layout
           without windows
+        - fix caps lock affected behaviour of key bindings
+        - re-create cache dir if it is deleted while qtile is running
+        - fix CheckUpdates widget color when no updates
+        - handle cases where BAT_DIR does not exist
+        - fix the wallpaper widget when using `wallpaper_command`
+        - fix Tile layout order to not reverse on reset
+        - fix calling `focus_previous/next` with no windows
+        - fix floating bug is BSP layout
 
 qtile 0.11.1, released 2018-03-01:
     * bug fix
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/qtile-0.11.1+20180513.39ced15a/README.rst 
new/qtile-0.12.0/README.rst
--- old/qtile-0.11.1+20180513.39ced15a/README.rst       2018-05-14 
02:02:22.000000000 +0200
+++ new/qtile-0.12.0/README.rst 2018-07-21 02:02:29.000000000 +0200
@@ -28,7 +28,7 @@
 Current Release
 ===============
 
-The current stable version of qtile is 0.11.0, released 2018-02-28. See the
+The current stable version of qtile is 0.12.0, released 2018-07-20. See the
 `documentation <http://docs.qtile.org/en/latest/manual/install/index.html>`_
 for installation instructions.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/qtile-0.11.1+20180513.39ced15a/docs/conf.py 
new/qtile-0.12.0/docs/conf.py
--- old/qtile-0.11.1+20180513.39ced15a/docs/conf.py     2018-05-14 
02:02:22.000000000 +0200
+++ new/qtile-0.12.0/docs/conf.py       2018-07-21 02:02:29.000000000 +0200
@@ -90,14 +90,14 @@
 
 # General information about the project.
 project = u'Qtile'
-copyright = u'2008-2016, Aldo Cortesi and contributers'
+copyright = u'2008-2018, Aldo Cortesi and contributers'
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
 # built documents.
 #
 # The short X.Y version.
-version = '0.11.0'
+version = '0.12.0'
 # The full version, including alpha/beta/rc tags.
 release = version
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/qtile-0.11.1+20180513.39ced15a/libqtile/layout/bsp.py 
new/qtile-0.12.0/libqtile/layout/bsp.py
--- old/qtile-0.11.1+20180513.39ced15a/libqtile/layout/bsp.py   2018-05-14 
02:02:22.000000000 +0200
+++ new/qtile-0.12.0/libqtile/layout/bsp.py     2018-07-21 02:02:29.000000000 
+0200
@@ -189,14 +189,17 @@
 
     def remove(self, client):
         node = self.get_node(client)
-        if node.parent:
-            node = node.parent.remove(node)
-            newclient = next(node.clients(), None)
-            if newclient is None:
-                self.current = self.root
-            return newclient
-        node.client = None
-        self.current = self.root
+        if node:
+            if node.parent:
+                node = node.parent.remove(node)
+                newclient = next(node.clients(), None)
+                if newclient is None:
+                    self.current = self.root
+                else:
+                    self.current = self.get_node(newclient)
+                return newclient
+            node.client = None
+            self.current = self.root
 
     def configure(self, client, screen):
         self.root.calc_geom(screen.x, screen.y, screen.width,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/qtile-0.11.1+20180513.39ced15a/libqtile/layout/columns.py 
new/qtile-0.12.0/libqtile/layout/columns.py
--- old/qtile-0.11.1+20180513.39ced15a/libqtile/layout/columns.py       
2018-05-14 02:02:22.000000000 +0200
+++ new/qtile-0.12.0/libqtile/layout/columns.py 2018-07-21 02:02:29.000000000 
+0200
@@ -77,11 +77,11 @@
     """Extension of the Stack layout.
 
     The screen is split into columns, which can be dynamically added or
-    removed.  Each column displays either a sigle window at a time from a
-    stack of windows or all of them simultaneously, spliting the column
-    space.  Columns and windows can be resized and windows can be shuffled
-    around.  This layout can also emulate "Wmii", "Verical", and "Max",
-    depending on the default parameters.
+    removed.  Each column displays either a single window at a time from a
+    stack of windows or all of them simultaneously, spliting the column space.
+    Columns and windows can be resized and windows can be shuffled around.
+    This layout can also emulate "Wmii", "Verical", and "Max", depending on the
+    default parameters.
 
     An example key configuration is::
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/qtile-0.11.1+20180513.39ced15a/libqtile/manager.py 
new/qtile-0.12.0/libqtile/manager.py
--- old/qtile-0.11.1+20180513.39ced15a/libqtile/manager.py      2018-05-14 
02:02:22.000000000 +0200
+++ new/qtile-0.12.0/libqtile/manager.py        2018-07-21 02:02:29.000000000 
+0200
@@ -22,8 +22,9 @@
 
 try:
     import tracemalloc
+    has_tracemalloc = True
 except ImportError:
-    tracemalloc = None
+    has_tracemalloc = False
 
 from libqtile.dgroups import DGroups
 from xcffib.xproto import EventMask, WindowError, AccessError, DrawableError
@@ -421,27 +422,20 @@
             )
             self.screens.append(s)
 
+    def _auto_modmasks(self):
+        yield 0
+        yield xcbq.ModMasks["lock"]
+        if self.numlockMask:
+            yield self.numlockMask
+            yield self.numlockMask | xcbq.ModMasks["lock"]
+
     def mapKey(self, key):
         self.keyMap[(key.keysym, key.modmask & self.validMask)] = key
         code = self.conn.keysym_to_keycode(key.keysym)
-        self.root.grab_key(
-            code,
-            key.modmask,
-            True,
-            xcffib.xproto.GrabMode.Async,
-            xcffib.xproto.GrabMode.Async,
-        )
-        if self.numlockMask:
-            self.root.grab_key(
-                code,
-                key.modmask | self.numlockMask,
-                True,
-                xcffib.xproto.GrabMode.Async,
-                xcffib.xproto.GrabMode.Async,
-            )
+        for amask in self._auto_modmasks():
             self.root.grab_key(
                 code,
-                key.modmask | self.numlockMask | xcbq.ModMasks["lock"],
+                key.modmask | amask,
                 True,
                 xcffib.xproto.GrabMode.Async,
                 xcffib.xproto.GrabMode.Async,
@@ -453,13 +447,8 @@
             return
 
         code = self.conn.keysym_to_keycode(key.keysym)
-        self.root.ungrab_key(code, key.modmask)
-        if self.numlockMask:
-            self.root.ungrab_key(code, key.modmask | self.numlockMask)
-            self.root.ungrab_key(
-                code,
-                key.modmask | self.numlockMask | xcbq.ModMasks["lock"]
-            )
+        for amask in self._auto_modmasks():
+            self.root.ungrab_key(code, key.modmask | amask)
         del(self.keyMap[key_index])
 
     def update_net_desktops(self):
@@ -669,26 +658,10 @@
             eventmask = EventMask.ButtonPress
             if isinstance(i, Drag):
                 eventmask |= EventMask.ButtonRelease
-            self.root.grab_button(
-                i.button_code,
-                i.modmask,
-                True,
-                eventmask,
-                grabmode,
-                xcffib.xproto.GrabMode.Async,
-            )
-            if self.numlockMask:
+            for amask in self._auto_modmasks():
                 self.root.grab_button(
                     i.button_code,
-                    i.modmask | self.numlockMask,
-                    True,
-                    eventmask,
-                    grabmode,
-                    xcffib.xproto.GrabMode.Async,
-                )
-                self.root.grab_button(
-                    i.button_code,
-                    i.modmask | self.numlockMask | xcbq.ModMasks["lock"],
+                    i.modmask | amask,
                     True,
                     eventmask,
                     grabmode,
@@ -1822,6 +1795,10 @@
 
         Running tracemalloc is required for qtile-top
         """
+        if not has_tracemalloc:
+            logger.warning('No tracemalloc module')
+            raise command.CommandError("No tracemalloc module")
+
         if not tracemalloc.is_tracing():
             tracemalloc.start()
         else:
@@ -1829,9 +1806,10 @@
 
     def cmd_tracemalloc_dump(self):
         """Dump tracemalloc snapshot"""
-        if not tracemalloc:
+        if not has_tracemalloc:
             logger.warning('No tracemalloc module')
             raise command.CommandError("No tracemalloc module")
+
         if not tracemalloc.is_tracing():
             return [False, "Trace not started"]
         cache_directory = get_cache_dir()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/qtile-0.11.1+20180513.39ced15a/libqtile/notify.py 
new/qtile-0.12.0/libqtile/notify.py
--- old/qtile-0.11.1+20180513.39ced15a/libqtile/notify.py       2018-05-14 
02:02:22.000000000 +0200
+++ new/qtile-0.12.0/libqtile/notify.py 2018-07-21 02:02:29.000000000 +0200
@@ -33,13 +33,14 @@
     from dbus import service
     from dbus.mainloop.glib import DBusGMainLoop
     DBusGMainLoop(set_as_default=True)
+    has_dbus = True
 except ImportError:
-    dbus = None
+    has_dbus = False
 
 BUS_NAME = 'org.freedesktop.Notifications'
 SERVICE_PATH = '/org/freedesktop/Notifications'
 
-if dbus:
+if has_dbus:
     class NotificationService(service.Object):
         def __init__(self, manager):
             bus = dbus.SessionBus()
@@ -89,7 +90,7 @@
 
     @property
     def service(self):
-        if dbus and self._service is None:
+        if has_dbus and self._service is None:
             try:
                 self._service = NotificationService(self)
             except Exception:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/qtile-0.11.1+20180513.39ced15a/libqtile/scripts/qtile.py 
new/qtile-0.12.0/libqtile/scripts/qtile.py
--- old/qtile-0.11.1+20180513.39ced15a/libqtile/scripts/qtile.py        
2018-05-14 02:02:22.000000000 +0200
+++ new/qtile-0.12.0/libqtile/scripts/qtile.py  2018-07-21 02:02:29.000000000 
+0200
@@ -28,7 +28,7 @@
 from libqtile.log_utils import init_log, logger
 from libqtile import confreader
 
-locale.setlocale(locale.LC_ALL, locale.getdefaultlocale())
+locale.setlocale(locale.LC_ALL, locale.getdefaultlocale())  # type: ignore
 
 try:
     import pkg_resources
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/qtile-0.11.1+20180513.39ced15a/libqtile/widget/battery.py 
new/qtile-0.12.0/libqtile/widget/battery.py
--- old/qtile-0.11.1+20180513.39ced15a/libqtile/widget/battery.py       
2018-05-14 02:02:22.000000000 +0200
+++ new/qtile-0.12.0/libqtile/widget/battery.py 2018-07-21 02:02:29.000000000 
+0200
@@ -63,12 +63,11 @@
 
 
 def _get_battery_name():
-    bats = [f for f in os.listdir(BAT_DIR) if f.startswith('BAT')]
-
-    if bats:
-        return bats[0]
-    else:
-        return 'BAT0'
+    if os.path.isdir(BAT_DIR):
+        bats = [f for f in os.listdir(BAT_DIR) if f.startswith('BAT')]
+        if bats:
+            return bats[0]
+    return 'BAT0'
 
 
 class _Battery(base._TextBox):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/qtile-0.11.1+20180513.39ced15a/libqtile/widget/check_updates.py 
new/qtile-0.12.0/libqtile/widget/check_updates.py
--- old/qtile-0.11.1+20180513.39ced15a/libqtile/widget/check_updates.py 
2018-05-14 02:02:22.000000000 +0200
+++ new/qtile-0.12.0/libqtile/widget/check_updates.py   2018-07-21 
02:02:29.000000000 +0200
@@ -61,26 +61,30 @@
             self.cmd = None
 
     def _check_updates(self):
+        # type: () -> str
         try:
             updates = self.call_process(self.cmd)
         except CalledProcessError:
             updates = ""
-        num_updates = str(len(updates.splitlines()) - self.subtr)
+        num_updates = len(updates.splitlines()) - self.subtr
         self._set_colour(num_updates)
-        return self.display_format.format(**{"updates": num_updates})
+        return self.display_format.format(**{"updates": str(num_updates)})
 
     def _set_colour(self, num_updates):
+        # type: (int) -> None
         if num_updates:
             self.layout.colour = self.colour_have_updates
         else:
             self.layout.colour = self.colour_no_updates
 
     def poll(self):
+        # type: () -> str
         if not self.cmd:
             return "N/A"
         return self._check_updates()
 
     def button_press(self, x, y, button):
+        # type: (int, int, int) -> None
         base.ThreadedPollText.button_press(self, x, y, button)
         if button == 1 and self.execute is not None:
             Popen(self.execute, shell=True)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/qtile-0.11.1+20180513.39ced15a/libqtile/widget/generic_poll_text.py 
new/qtile-0.12.0/libqtile/widget/generic_poll_text.py
--- old/qtile-0.11.1+20180513.39ced15a/libqtile/widget/generic_poll_text.py     
2018-05-14 02:02:22.000000000 +0200
+++ new/qtile-0.12.0/libqtile/widget/generic_poll_text.py       2018-07-21 
02:02:29.000000000 +0200
@@ -17,6 +17,11 @@
     def xmlparse(body):
         raise Exception("no xmltodict library")
 
+try:
+    from typing import Any, List, Tuple  # noqa: F401
+except ImportError:
+    pass
+
 
 class GenPollText(base.ThreadedPollText):
     """A generic text widget that polls using poll function to get the text"""
@@ -46,7 +51,7 @@
         ('user_agent', 'Qtile', 'Set the user agent'),
         ('headers', {}, 'Extra Headers'),
         ('xml', False, 'Is XML?'),
-    ]
+    ]  # type: List[Tuple[str, Any, str]]
 
     def __init__(self, **config):
         base.ThreadedPollText.__init__(self, **config)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/qtile-0.11.1+20180513.39ced15a/libqtile/widget/mpd2widget.py 
new/qtile-0.12.0/libqtile/widget/mpd2widget.py
--- old/qtile-0.11.1+20180513.39ced15a/libqtile/widget/mpd2widget.py    
2018-05-14 02:02:22.000000000 +0200
+++ new/qtile-0.12.0/libqtile/widget/mpd2widget.py      2018-07-21 
02:02:29.000000000 +0200
@@ -1,4 +1,5 @@
 from . import base
+from libqtile.log_utils import logger
 
 from six import u, text_type
 from socket import error as socket_error
@@ -179,7 +180,12 @@
         if not isinstance(fmt, text_type):
             fmt = u(fmt)
 
-        return fmt.format(play_status=play_status, **status)
+        try:
+            formatted = fmt.format(play_status=play_status, **status)
+            return formatted
+        except KeyError as e:
+            logger.exception("mpd client did not return status: 
{}".format(e.args[0]))
+            return "ERROR"
 
     def prepare_formatting(self, status, currentsong):
         for key in self.prepare_status:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/qtile-0.11.1+20180513.39ced15a/libqtile/widget/prompt.py 
new/qtile-0.12.0/libqtile/widget/prompt.py
--- old/qtile-0.11.1+20180513.39ced15a/libqtile/widget/prompt.py        
2018-05-14 02:02:22.000000000 +0200
+++ new/qtile-0.12.0/libqtile/widget/prompt.py  2018-07-21 02:02:29.000000000 
+0200
@@ -34,6 +34,7 @@
 import glob
 import os
 import pickle
+import six
 import string
 from collections import OrderedDict, deque
 
@@ -561,6 +562,13 @@
 
                 if self.position < self.max_history:
                     self.position += 1
+                if six.PY3:
+                    os.makedirs(os.path.dirname(self.history_path), 
exist_ok=True)
+                else:
+                    try:
+                        os.makedirs(os.path.dirname(self.history_path))
+                    except OSError:  # file exists
+                        pass
                 with open(self.history_path, mode='wb') as f:
                     pickle.dump(self.history, f, protocol=2)
             self.callback(self.userInput)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/qtile-0.11.1+20180513.39ced15a/resources/qshell.1 
new/qtile-0.12.0/resources/qshell.1
--- old/qtile-0.11.1+20180513.39ced15a/resources/qshell.1       2018-05-14 
02:02:22.000000000 +0200
+++ new/qtile-0.12.0/resources/qshell.1 2018-07-21 02:02:29.000000000 +0200
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "QSHELL" "1" "Feb 28, 2018" "0.11.0" "Qtile"
+.TH "QSHELL" "1" "Jul 20, 2018" "0.12.0" "Qtile"
 .SH NAME
 qshell \- Qtile Documentation
 .
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/qtile-0.11.1+20180513.39ced15a/resources/qtile.1 
new/qtile-0.12.0/resources/qtile.1
--- old/qtile-0.11.1+20180513.39ced15a/resources/qtile.1        2018-05-14 
02:02:22.000000000 +0200
+++ new/qtile-0.12.0/resources/qtile.1  2018-07-21 02:02:29.000000000 +0200
@@ -1,6 +1,6 @@
 .\" Man page generated from reStructuredText.
 .
-.TH "QTILE" "1" "Feb 28, 2018" "0.11.0" "Qtile"
+.TH "QTILE" "1" "Jul 20, 2018" "0.12.0" "Qtile"
 .SH NAME
 qtile \- Qtile Documentation
 .
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/qtile-0.11.1+20180513.39ced15a/rpm/qtile.spec 
new/qtile-0.12.0/rpm/qtile.spec
--- old/qtile-0.11.1+20180513.39ced15a/rpm/qtile.spec   2018-05-14 
02:02:22.000000000 +0200
+++ new/qtile-0.12.0/rpm/qtile.spec     2018-07-21 02:02:29.000000000 +0200
@@ -1,7 +1,7 @@
 Summary: A pure-Python tiling window manager
 Name: qtile
-Version: 0.10.7
-Release: 3%{?dist}
+Version: 0.12.0
+Release: 1%{?dist}
 Source0: https://github.com/qtile/qtile/archive/v%{version}.tar.gz
 License: MIT and GPLv3+
 # All MIT except for:
@@ -70,13 +70,37 @@
 %{_bindir}/qtile
 %{_bindir}/qtile-run
 %{_bindir}/qtile-top
+%{_bindir}/dqtile-cmd
+%{_bindir}/qtile-cmd
 %{python3_sitelib}/qtile-%{version}-py%{python3_version}.egg-info
 %{python3_sitelib}/libqtile
 %{_datadir}/xsessions/qtile.desktop
 
 
 %changelog
-* Wed Feb 28 2018 John Dulaney <[email protected]> - 0.11.0-1
+* Wed Jul 18 2018 John Dulaney <[email protected]> - 0.12.0-1
+- !!! Config breakage !!!
+-   Tile layout commands up/down/shuffle_up/shuffle_down changed to be
+-   more consistent with other layouts
+-   move qcmd to qtile-cmd because of conflict with renameutils, move
+-   dqcmd to dqtile-cmd for symmetry
+- add `add_after_last` option to Tile layout to add windows to the end of the 
list
+- add new formatting options to TaskList
+- allow Volume to open app on right click
+- fix floating of file transfer windows and java drop-downs
+- fix exception when calling `cmd_next` and `cmd_previous` on layout without 
windows
+- fix caps lock affected behaviour of key bindings
+- re-create cache dir if it is deleted while qtile is running
+- fix CheckUpdates widget color when no updates
+- handle cases where BAT_DIR does not exist
+- fix the wallpaper widget when using `wallpaper_command`
+- fix Tile layout order to not reverse on reset
+- fix calling `focus_previous/next` with no windows
+
+* Fri Mar 30 2018 John Dulaney <[email protected]> - 0.11.1-2
+- Add unpackaged files %#{_bindir}/dqcmd %#{_bindir}/qcmd
+
+* Wed Feb 28 2018 John Dulaney <[email protected]> - 0.11.1-1
 - !!! Completely changed extension configuration, see the documentation !!!
 - !!! `extention` subpackage renamed to `extension` !!!
 - !!! `extentions` configuration variable changed to `extension_defaults` !!!
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/qtile-0.11.1+20180513.39ced15a/setup.cfg 
new/qtile-0.12.0/setup.cfg
--- old/qtile-0.11.1+20180513.39ced15a/setup.cfg        2018-05-14 
02:02:22.000000000 +0200
+++ new/qtile-0.12.0/setup.cfg  2018-07-21 02:02:29.000000000 +0200
@@ -14,11 +14,15 @@
 [mypy]
 mypy_path = stubs
 python_version = 2.7
-[mypy-libqtile/_ffi_*]
-ignore_errors = True
-[mypy-trollius]
+[mypy-_cffi_backend]
+ignore_missing_imports = True
+[mypy-cairocffi._ffi]
 ignore_missing_imports = True
 [mypy-libqtile._ffi_pango]
 ignore_missing_imports = True
 [mypy-libqtile._ffi_xcursors]
 ignore_missing_imports = True
+[mypy-trollius]
+ignore_missing_imports = True
+[mypy-xcffib._ffi]
+ignore_missing_imports = True
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/qtile-0.11.1+20180513.39ced15a/setup.py 
new/qtile-0.12.0/setup.py
--- old/qtile-0.11.1+20180513.39ced15a/setup.py 2018-05-14 02:02:22.000000000 
+0200
+++ new/qtile-0.12.0/setup.py   2018-07-21 02:02:29.000000000 +0200
@@ -96,7 +96,7 @@
 
 setup(
     name="qtile",
-    version="0.11.1",
+    version="0.12.0",
     description="A pure-Python tiling window manager.",
     long_description=long_description,
     classifiers=[
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/qtile-0.11.1+20180513.39ced15a/test/layouts/test__common.py 
new/qtile-0.12.0/test/layouts/test__common.py
--- old/qtile-0.11.1+20180513.39ced15a/test/layouts/test__common.py     
2018-05-14 02:02:22.000000000 +0200
+++ new/qtile-0.12.0/test/layouts/test__common.py       1970-01-01 
01:00:00.000000000 +0100
@@ -1,431 +0,0 @@
-# Copyright (c) 2017 Dario Giovannetti
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-
-import pytest
-
-from libqtile import layout
-import libqtile.manager
-import libqtile.config
-import libqtile.hook
-from .layout_utils import assertFocused, assertFocusPathUnordered
-
-
-class AllLayoutsConfig(object):
-    """
-    Ensure that all layouts behave consistently in some common scenarios.
-    """
-    auto_fullscreen = True
-    main = None
-    groups = [
-        libqtile.config.Group("a"),
-        libqtile.config.Group("b"),
-        libqtile.config.Group("c"),
-        libqtile.config.Group("d"),
-    ]
-    floating_layout = libqtile.layout.floating.Floating()
-    keys = []
-    mouse = []
-    screens = []
-
-    @staticmethod
-    def iter_layouts():
-        # Retrieve the layouts dynamically (i.e. do not hard-code a list) to
-        # prevent forgetting to add new future layouts
-        for layout_name in dir(layout):
-            Layout = getattr(layout, layout_name)
-            try:
-                test = issubclass(Layout, layout.base.Layout)
-            except TypeError:
-                pass
-            else:
-                # Explicitly exclude the Slice layout, since it depends on
-                # other layouts (tested here) and has its own specific tests
-                if test and layout_name != 'Slice':
-                    yield layout_name, Layout
-
-    @classmethod
-    def generate(cls):
-        """
-        Generate a configuration for each layout currently in the repo.
-        Each configuration has only the tested layout (i.e. 1 item) in the
-        'layouts' variable.
-        """
-        return [type(layout_name, (cls, ), {'layouts': [Layout()]})
-                for layout_name, Layout in cls.iter_layouts()]
-
-
-class AllLayouts(AllLayoutsConfig):
-    """
-    Like AllLayoutsConfig, but all the layouts in the repo are installed
-    together in the 'layouts' variable.
-    """
-    layouts = [Layout() for layout_name, Layout
-               in AllLayoutsConfig.iter_layouts()]
-
-
-class AllLayoutsConfigEvents(AllLayoutsConfig):
-    """
-    Extends AllLayoutsConfig to test events.
-    """
-    def main(self, c):
-        # TODO: Test more events
-
-        c.test_data = {
-            'focus_change': 0,
-        }
-
-        def handle_focus_change():
-            c.test_data['focus_change'] += 1
-
-        libqtile.hook.subscribe.focus_change(handle_focus_change)
-
-
-each_layout_config = pytest.mark.parametrize("qtile", 
AllLayoutsConfig.generate(), indirect=True)
-all_layouts_config = pytest.mark.parametrize("qtile", [AllLayouts], 
indirect=True)
-each_layout_config_events = pytest.mark.parametrize("qtile", 
AllLayoutsConfigEvents.generate(), indirect=True)
-
-
-@each_layout_config
-def test_window_types(qtile):
-    pytest.importorskip("Tkinter")
-    qtile.testWindow("one")
-
-    # A dialog should take focus and be floating
-    qtile.testDialog("dialog")
-    qtile.c.window.info()['floating'] is True
-    assertFocused(qtile, "dialog")
-
-    # A notification shouldn't steal focus and should be floating
-    qtile.testNotification("notification")
-    assert qtile.c.group.info()['focus'] != 'notification'
-    qtile.c.group.info_by_name('notification')['floating'] is True
-
-
-@each_layout_config
-def test_focus_cycle(qtile):
-    pytest.importorskip("Tkinter")
-
-    qtile.testWindow("one")
-    qtile.testWindow("two")
-    qtile.testDialog("float1")
-    qtile.testDialog("float2")
-    qtile.testWindow("three")
-
-    # Test preconditions (the order of items in 'clients' is managed by each 
layout)
-    assert set(qtile.c.layout.info()['clients']) == {'one', 'two', 'three'}
-    assertFocused(qtile, "three")
-
-    # Assert that the layout cycles the focus on all windows
-    assertFocusPathUnordered(qtile, 'float1', 'float2', 'one', 'two', 'three')
-
-
-@each_layout_config
-def test_focus_back(qtile):
-    # No exception must be raised without windows
-    qtile.c.group.focus_back()
-
-    # Nothing must happen with only one window
-    one = qtile.testWindow("one")
-    qtile.c.group.focus_back()
-    assertFocused(qtile, "one")
-
-    # 2 windows
-    two = qtile.testWindow("two")
-    assertFocused(qtile, "two")
-    qtile.c.group.focus_back()
-    assertFocused(qtile, "one")
-    qtile.c.group.focus_back()
-    assertFocused(qtile, "two")
-
-    # Float a window
-    three = qtile.testWindow("three")
-    qtile.c.group.focus_back()
-    assertFocused(qtile, "two")
-    qtile.c.window.toggle_floating()
-    qtile.c.group.focus_back()
-    assertFocused(qtile, "three")
-
-    # If the previous window is killed, the further previous one must be 
focused
-    four = qtile.testWindow("four")
-    qtile.kill_window(two)
-    qtile.kill_window(three)
-    assertFocused(qtile, "four")
-    qtile.c.group.focus_back()
-    assertFocused(qtile, "one")
-
-
-# TODO: Test more events
-@each_layout_config_events
-def test_focus_change_event(qtile):
-    # Test that the correct number of focus_change events are fired e.g. when
-    # opening, closing or switching windows.
-    # If for example a layout explicitly fired a focus_change event even though
-    # group._Group.focus() or group._Group.remove() already fire one, the other
-    # installed layouts would wrongly react to it and cause misbehaviour.
-    # In short, this test prevents layouts from influencing each other in
-    # unexpected ways.
-
-    # TODO: Why does it start with 2?
-    assert qtile.c.get_test_data()['focus_change'] == 2
-
-    # Spawning a window must fire only 1 focus_change event
-    one = qtile.testWindow("one")
-    assert qtile.c.get_test_data()['focus_change'] == 3
-    two = qtile.testWindow("two")
-    assert qtile.c.get_test_data()['focus_change'] == 4
-    three = qtile.testWindow("three")
-    assert qtile.c.get_test_data()['focus_change'] == 5
-
-    # Switching window must fire only 1 focus_change event
-    assertFocused(qtile, "three")
-    qtile.c.group.focus_by_name("one")
-    assert qtile.c.get_test_data()['focus_change'] == 6
-    assertFocused(qtile, "one")
-
-    # Focusing the current window must fire another focus_change event
-    qtile.c.group.focus_by_name("one")
-    assert qtile.c.get_test_data()['focus_change'] == 7
-
-    # Toggling a window floating should not fire focus_change events
-    qtile.c.window.toggle_floating()
-    assert qtile.c.get_test_data()['focus_change'] == 7
-    qtile.c.window.toggle_floating()
-    assert qtile.c.get_test_data()['focus_change'] == 7
-
-    # Removing the focused window must fire only 1 focus_change event
-    assertFocused(qtile, "one")
-    assert qtile.c.group.info()['focusHistory'] == ["two", "three", "one"]
-    qtile.kill_window(one)
-    assert qtile.c.get_test_data()['focus_change'] == 8
-
-    # The position where 'one' was after it was floated and unfloated
-    # above depends on the layout, so we can't predict here what window gets
-    # selected after killing it; for this reason, focus 'three' explicitly to
-    # continue testing
-    qtile.c.group.focus_by_name("three")
-    assert qtile.c.group.info()['focusHistory'] == ["two", "three"]
-    assert qtile.c.get_test_data()['focus_change'] == 9
-
-    # Removing a non-focused window must not fire focus_change events
-    qtile.kill_window(two)
-    assert qtile.c.get_test_data()['focus_change'] == 9
-    assertFocused(qtile, "three")
-
-    # Removing the last window must still generate 1 focus_change event
-    qtile.kill_window(three)
-    assert qtile.c.layout.info()['clients'] == []
-    assert qtile.c.get_test_data()['focus_change'] == 10
-
-
-@each_layout_config
-def test_remove(qtile):
-    one = qtile.testWindow("one")
-    two = qtile.testWindow("two")
-    three = qtile.testWindow("three")
-    assertFocused(qtile, "three")
-    assert qtile.c.group.info()['focusHistory'] == ["one", "two", "three"]
-
-    # Removing a focused window must focus another (which one depends on the 
layout)
-    qtile.kill_window(three)
-    assert qtile.c.window.info()['name'] in qtile.c.layout.info()['clients']
-
-    # To continue testing, explicitly set focus on 'two'
-    qtile.c.group.focus_by_name("two")
-    four = qtile.testWindow("four")
-    assertFocused(qtile, "four")
-    assert qtile.c.group.info()['focusHistory'] == ["one", "two", "four"]
-
-    # Removing a non-focused window must not change the current focus
-    qtile.kill_window(two)
-    assertFocused(qtile, "four")
-    assert qtile.c.group.info()['focusHistory'] == ["one", "four"]
-
-    # Add more windows and shuffle the focus order
-    five = qtile.testWindow("five")
-    six = qtile.testWindow("six")
-    qtile.c.group.focus_by_name("one")
-    seven = qtile.testWindow("seven")
-    qtile.c.group.focus_by_name("six")
-    assertFocused(qtile, "six")
-    assert qtile.c.group.info()['focusHistory'] == ["four", "five", "one",
-                                                    "seven", "six"]
-
-    qtile.kill_window(five)
-    qtile.kill_window(one)
-    assertFocused(qtile, "six")
-    assert qtile.c.group.info()['focusHistory'] == ["four", "seven", "six"]
-
-    qtile.c.group.focus_by_name("seven")
-    qtile.kill_window(seven)
-    assert qtile.c.window.info()['name'] in qtile.c.layout.info()['clients']
-
-
-@each_layout_config
-def test_remove_floating(qtile):
-    pytest.importorskip("Tkinter")
-
-    one = qtile.testWindow("one")
-    two = qtile.testWindow("two")
-    float1 = qtile.testDialog("float1")
-    assertFocused(qtile, "float1")
-    assert set(qtile.c.layout.info()['clients']) == {"one", "two"}
-    assert qtile.c.group.info()['focusHistory'] == ["one", "two", "float1"]
-
-    # Removing a focused floating window must focus the one that was focused 
before
-    qtile.kill_window(float1)
-    assertFocused(qtile, "two")
-    assert qtile.c.group.info()['focusHistory'] == ["one", "two"]
-
-    float2 = qtile.testDialog("float2")
-    assertFocused(qtile, "float2")
-    assert qtile.c.group.info()['focusHistory'] == ["one", "two", "float2"]
-
-    # Removing a non-focused floating window must not change the current focus
-    qtile.c.group.focus_by_name("two")
-    qtile.kill_window(float2)
-    assertFocused(qtile, "two")
-    assert qtile.c.group.info()['focusHistory'] == ["one", "two"]
-
-    # Add more windows and shuffle the focus order
-    three = qtile.testWindow("three")
-    float3 = qtile.testDialog("float3")
-    qtile.c.group.focus_by_name("one")
-    float4 = qtile.testDialog("float4")
-    float5 = qtile.testDialog("float5")
-    qtile.c.group.focus_by_name("three")
-    qtile.c.group.focus_by_name("float3")
-    assert qtile.c.group.info()['focusHistory'] == ["two", "one", "float4",
-                                                    "float5", "three", 
"float3"]
-
-    qtile.kill_window(one)
-    assertFocused(qtile, "float3")
-    assert qtile.c.group.info()['focusHistory'] == ["two", "float4",
-                                                    "float5", "three", 
"float3"]
-
-    qtile.kill_window(float5)
-    assertFocused(qtile, "float3")
-    assert qtile.c.group.info()['focusHistory'] == ["two", "float4", "three", 
"float3"]
-
-    # The focus must be given to the previous window even if it's floating
-    qtile.c.group.focus_by_name("float4")
-    assert qtile.c.group.info()['focusHistory'] == ["two", "three", "float3", 
"float4"]
-    qtile.kill_window(float4)
-    assertFocused(qtile, "float3")
-    assert qtile.c.group.info()['focusHistory'] == ["two", "three", "float3"]
-
-    four = qtile.testWindow("four")
-    float6 = qtile.testDialog("float6")
-    five = qtile.testWindow("five")
-    qtile.c.group.focus_by_name("float3")
-    assert qtile.c.group.info()['focusHistory'] == ["two", "three", "four",
-                                                    "float6", "five", "float3"]
-
-    # Killing several unfocused windows before the current one, and then
-    # killing the current window, must focus the remaining most recently
-    # focused window
-    qtile.kill_window(five)
-    qtile.kill_window(four)
-    qtile.kill_window(float6)
-    assert qtile.c.group.info()['focusHistory'] == ["two", "three", "float3"]
-    qtile.kill_window(float3)
-    assertFocused(qtile, "three")
-    assert qtile.c.group.info()['focusHistory'] == ["two", "three"]
-
-
-@each_layout_config
-def test_desktop_notifications(qtile):
-    pytest.importorskip("Tkinter")
-
-    # Unlike normal floating windows such as dialogs, notifications don't steal
-    # focus when they spawn, so test them separately
-
-    # A notification fired in an empty group must not take focus
-    notif1 = qtile.testNotification("notif1")
-    assert qtile.c.group.info()['focus'] is None
-    qtile.kill_window(notif1)
-
-    # A window is spawned while a notification is displayed
-    notif2 = qtile.testNotification("notif2")
-    one = qtile.testWindow("one")
-    assert qtile.c.group.info()['focusHistory'] == ["one"]
-    qtile.kill_window(notif2)
-
-    # Another notification is fired, but the focus must not change
-    notif3 = qtile.testNotification("notif3")
-    assertFocused(qtile, 'one')
-    qtile.kill_window(notif3)
-
-    # Complicate the scenario with multiple windows and notifications
-
-    dialog1 = qtile.testDialog("dialog1")
-    two = qtile.testWindow("two")
-    notif4 = qtile.testNotification("notif4")
-    notif5 = qtile.testNotification("notif5")
-    assert qtile.c.group.info()['focusHistory'] == ["one", "dialog1", "two"]
-
-    dialog2 = qtile.testDialog("dialog2")
-    qtile.kill_window(notif5)
-    three = qtile.testWindow("three")
-    qtile.kill_window(one)
-    qtile.c.group.focus_by_name("two")
-    notif6 = qtile.testNotification("notif6")
-    notif7 = qtile.testNotification("notif7")
-    qtile.kill_window(notif4)
-    notif8 = qtile.testNotification("notif8")
-    assert qtile.c.group.info()['focusHistory'] == ["dialog1", "dialog2",
-                                                    "three", "two"]
-
-    dialog3 = qtile.testDialog("dialog3")
-    qtile.kill_window(dialog1)
-    qtile.kill_window(dialog2)
-    qtile.kill_window(notif6)
-    qtile.c.group.focus_by_name("three")
-    qtile.kill_window(notif7)
-    qtile.kill_window(notif8)
-    assert qtile.c.group.info()['focusHistory'] == ["two", "dialog3", "three"]
-
-
-@all_layouts_config
-def test_cycle_layouts(qtile):
-    qtile.testWindow("one")
-    qtile.testWindow("two")
-    qtile.testWindow("three")
-    qtile.testWindow("four")
-    qtile.c.group.focus_by_name("three")
-    assertFocused(qtile, "three")
-
-    # Cycling all the layouts must keep the current window focused
-    initial_layout_name = qtile.c.layout.info()['name']
-    while True:
-        qtile.c.next_layout()
-        if qtile.c.layout.info()['name'] == initial_layout_name:
-            break
-        # Use qtile.c.layout.info()['name'] in the assertion message, so we
-        # know which layout is buggy
-        assert qtile.c.window.info()['name'] == "three", 
qtile.c.layout.info()['name']
-
-    # Now try backwards
-    while True:
-        qtile.c.prev_layout()
-        if qtile.c.layout.info()['name'] == initial_layout_name:
-            break
-        # Use qtile.c.layout.info()['name'] in the assertion message, so we
-        # know which layout is buggy
-        assert qtile.c.window.info()['name'] == "three", 
qtile.c.layout.info()['name']
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/qtile-0.11.1+20180513.39ced15a/test/layouts/test_common.py 
new/qtile-0.12.0/test/layouts/test_common.py
--- old/qtile-0.11.1+20180513.39ced15a/test/layouts/test_common.py      
1970-01-01 01:00:00.000000000 +0100
+++ new/qtile-0.12.0/test/layouts/test_common.py        2018-07-21 
02:02:29.000000000 +0200
@@ -0,0 +1,431 @@
+# Copyright (c) 2017 Dario Giovannetti
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import pytest
+
+from libqtile import layout
+import libqtile.manager
+import libqtile.config
+import libqtile.hook
+from .layout_utils import assertFocused, assertFocusPathUnordered
+
+
+class AllLayoutsConfig(object):
+    """
+    Ensure that all layouts behave consistently in some common scenarios.
+    """
+    auto_fullscreen = True
+    main = None
+    groups = [
+        libqtile.config.Group("a"),
+        libqtile.config.Group("b"),
+        libqtile.config.Group("c"),
+        libqtile.config.Group("d"),
+    ]
+    floating_layout = libqtile.layout.floating.Floating()
+    keys = []
+    mouse = []
+    screens = []
+
+    @staticmethod
+    def iter_layouts():
+        # Retrieve the layouts dynamically (i.e. do not hard-code a list) to
+        # prevent forgetting to add new future layouts
+        for layout_name in dir(layout):
+            Layout = getattr(layout, layout_name)
+            try:
+                test = issubclass(Layout, layout.base.Layout)
+            except TypeError:
+                pass
+            else:
+                # Explicitly exclude the Slice layout, since it depends on
+                # other layouts (tested here) and has its own specific tests
+                if test and layout_name != 'Slice':
+                    yield layout_name, Layout
+
+    @classmethod
+    def generate(cls):
+        """
+        Generate a configuration for each layout currently in the repo.
+        Each configuration has only the tested layout (i.e. 1 item) in the
+        'layouts' variable.
+        """
+        return [type(layout_name, (cls, ), {'layouts': [Layout()]})
+                for layout_name, Layout in cls.iter_layouts()]
+
+
+class AllLayouts(AllLayoutsConfig):
+    """
+    Like AllLayoutsConfig, but all the layouts in the repo are installed
+    together in the 'layouts' variable.
+    """
+    layouts = [Layout() for layout_name, Layout
+               in AllLayoutsConfig.iter_layouts()]
+
+
+class AllLayoutsConfigEvents(AllLayoutsConfig):
+    """
+    Extends AllLayoutsConfig to test events.
+    """
+    def main(self, c):
+        # TODO: Test more events
+
+        c.test_data = {
+            'focus_change': 0,
+        }
+
+        def handle_focus_change():
+            c.test_data['focus_change'] += 1
+
+        libqtile.hook.subscribe.focus_change(handle_focus_change)
+
+
+each_layout_config = pytest.mark.parametrize("qtile", 
AllLayoutsConfig.generate(), indirect=True)
+all_layouts_config = pytest.mark.parametrize("qtile", [AllLayouts], 
indirect=True)
+each_layout_config_events = pytest.mark.parametrize("qtile", 
AllLayoutsConfigEvents.generate(), indirect=True)
+
+
+@each_layout_config
+def test_window_types(qtile):
+    pytest.importorskip("Tkinter")
+    qtile.testWindow("one")
+
+    # A dialog should take focus and be floating
+    qtile.testDialog("dialog")
+    qtile.c.window.info()['floating'] is True
+    assertFocused(qtile, "dialog")
+
+    # A notification shouldn't steal focus and should be floating
+    qtile.testNotification("notification")
+    assert qtile.c.group.info()['focus'] != 'notification'
+    qtile.c.group.info_by_name('notification')['floating'] is True
+
+
+@each_layout_config
+def test_focus_cycle(qtile):
+    pytest.importorskip("Tkinter")
+
+    qtile.testWindow("one")
+    qtile.testWindow("two")
+    qtile.testDialog("float1")
+    qtile.testDialog("float2")
+    qtile.testWindow("three")
+
+    # Test preconditions (the order of items in 'clients' is managed by each 
layout)
+    assert set(qtile.c.layout.info()['clients']) == {'one', 'two', 'three'}
+    assertFocused(qtile, "three")
+
+    # Assert that the layout cycles the focus on all windows
+    assertFocusPathUnordered(qtile, 'float1', 'float2', 'one', 'two', 'three')
+
+
+@each_layout_config
+def test_focus_back(qtile):
+    # No exception must be raised without windows
+    qtile.c.group.focus_back()
+
+    # Nothing must happen with only one window
+    one = qtile.testWindow("one")
+    qtile.c.group.focus_back()
+    assertFocused(qtile, "one")
+
+    # 2 windows
+    two = qtile.testWindow("two")
+    assertFocused(qtile, "two")
+    qtile.c.group.focus_back()
+    assertFocused(qtile, "one")
+    qtile.c.group.focus_back()
+    assertFocused(qtile, "two")
+
+    # Float a window
+    three = qtile.testWindow("three")
+    qtile.c.group.focus_back()
+    assertFocused(qtile, "two")
+    qtile.c.window.toggle_floating()
+    qtile.c.group.focus_back()
+    assertFocused(qtile, "three")
+
+    # If the previous window is killed, the further previous one must be 
focused
+    four = qtile.testWindow("four")
+    qtile.kill_window(two)
+    qtile.kill_window(three)
+    assertFocused(qtile, "four")
+    qtile.c.group.focus_back()
+    assertFocused(qtile, "one")
+
+
+# TODO: Test more events
+@each_layout_config_events
+def test_focus_change_event(qtile):
+    # Test that the correct number of focus_change events are fired e.g. when
+    # opening, closing or switching windows.
+    # If for example a layout explicitly fired a focus_change event even though
+    # group._Group.focus() or group._Group.remove() already fire one, the other
+    # installed layouts would wrongly react to it and cause misbehaviour.
+    # In short, this test prevents layouts from influencing each other in
+    # unexpected ways.
+
+    # TODO: Why does it start with 2?
+    assert qtile.c.get_test_data()['focus_change'] == 2
+
+    # Spawning a window must fire only 1 focus_change event
+    one = qtile.testWindow("one")
+    assert qtile.c.get_test_data()['focus_change'] == 3
+    two = qtile.testWindow("two")
+    assert qtile.c.get_test_data()['focus_change'] == 4
+    three = qtile.testWindow("three")
+    assert qtile.c.get_test_data()['focus_change'] == 5
+
+    # Switching window must fire only 1 focus_change event
+    assertFocused(qtile, "three")
+    qtile.c.group.focus_by_name("one")
+    assert qtile.c.get_test_data()['focus_change'] == 6
+    assertFocused(qtile, "one")
+
+    # Focusing the current window must fire another focus_change event
+    qtile.c.group.focus_by_name("one")
+    assert qtile.c.get_test_data()['focus_change'] == 7
+
+    # Toggling a window floating should not fire focus_change events
+    qtile.c.window.toggle_floating()
+    assert qtile.c.get_test_data()['focus_change'] == 7
+    qtile.c.window.toggle_floating()
+    assert qtile.c.get_test_data()['focus_change'] == 7
+
+    # Removing the focused window must fire only 1 focus_change event
+    assertFocused(qtile, "one")
+    assert qtile.c.group.info()['focusHistory'] == ["two", "three", "one"]
+    qtile.kill_window(one)
+    assert qtile.c.get_test_data()['focus_change'] == 8
+
+    # The position where 'one' was after it was floated and unfloated
+    # above depends on the layout, so we can't predict here what window gets
+    # selected after killing it; for this reason, focus 'three' explicitly to
+    # continue testing
+    qtile.c.group.focus_by_name("three")
+    assert qtile.c.group.info()['focusHistory'] == ["two", "three"]
+    assert qtile.c.get_test_data()['focus_change'] == 9
+
+    # Removing a non-focused window must not fire focus_change events
+    qtile.kill_window(two)
+    assert qtile.c.get_test_data()['focus_change'] == 9
+    assertFocused(qtile, "three")
+
+    # Removing the last window must still generate 1 focus_change event
+    qtile.kill_window(three)
+    assert qtile.c.layout.info()['clients'] == []
+    assert qtile.c.get_test_data()['focus_change'] == 10
+
+
+@each_layout_config
+def test_remove(qtile):
+    one = qtile.testWindow("one")
+    two = qtile.testWindow("two")
+    three = qtile.testWindow("three")
+    assertFocused(qtile, "three")
+    assert qtile.c.group.info()['focusHistory'] == ["one", "two", "three"]
+
+    # Removing a focused window must focus another (which one depends on the 
layout)
+    qtile.kill_window(three)
+    assert qtile.c.window.info()['name'] in qtile.c.layout.info()['clients']
+
+    # To continue testing, explicitly set focus on 'two'
+    qtile.c.group.focus_by_name("two")
+    four = qtile.testWindow("four")
+    assertFocused(qtile, "four")
+    assert qtile.c.group.info()['focusHistory'] == ["one", "two", "four"]
+
+    # Removing a non-focused window must not change the current focus
+    qtile.kill_window(two)
+    assertFocused(qtile, "four")
+    assert qtile.c.group.info()['focusHistory'] == ["one", "four"]
+
+    # Add more windows and shuffle the focus order
+    five = qtile.testWindow("five")
+    six = qtile.testWindow("six")
+    qtile.c.group.focus_by_name("one")
+    seven = qtile.testWindow("seven")
+    qtile.c.group.focus_by_name("six")
+    assertFocused(qtile, "six")
+    assert qtile.c.group.info()['focusHistory'] == ["four", "five", "one",
+                                                    "seven", "six"]
+
+    qtile.kill_window(five)
+    qtile.kill_window(one)
+    assertFocused(qtile, "six")
+    assert qtile.c.group.info()['focusHistory'] == ["four", "seven", "six"]
+
+    qtile.c.group.focus_by_name("seven")
+    qtile.kill_window(seven)
+    assert qtile.c.window.info()['name'] in qtile.c.layout.info()['clients']
+
+
+@each_layout_config
+def test_remove_floating(qtile):
+    pytest.importorskip("Tkinter")
+
+    one = qtile.testWindow("one")
+    two = qtile.testWindow("two")
+    float1 = qtile.testDialog("float1")
+    assertFocused(qtile, "float1")
+    assert set(qtile.c.layout.info()['clients']) == {"one", "two"}
+    assert qtile.c.group.info()['focusHistory'] == ["one", "two", "float1"]
+
+    # Removing a focused floating window must focus the one that was focused 
before
+    qtile.kill_window(float1)
+    assertFocused(qtile, "two")
+    assert qtile.c.group.info()['focusHistory'] == ["one", "two"]
+
+    float2 = qtile.testDialog("float2")
+    assertFocused(qtile, "float2")
+    assert qtile.c.group.info()['focusHistory'] == ["one", "two", "float2"]
+
+    # Removing a non-focused floating window must not change the current focus
+    qtile.c.group.focus_by_name("two")
+    qtile.kill_window(float2)
+    assertFocused(qtile, "two")
+    assert qtile.c.group.info()['focusHistory'] == ["one", "two"]
+
+    # Add more windows and shuffle the focus order
+    three = qtile.testWindow("three")
+    float3 = qtile.testDialog("float3")
+    qtile.c.group.focus_by_name("one")
+    float4 = qtile.testDialog("float4")
+    float5 = qtile.testDialog("float5")
+    qtile.c.group.focus_by_name("three")
+    qtile.c.group.focus_by_name("float3")
+    assert qtile.c.group.info()['focusHistory'] == ["two", "one", "float4",
+                                                    "float5", "three", 
"float3"]
+
+    qtile.kill_window(one)
+    assertFocused(qtile, "float3")
+    assert qtile.c.group.info()['focusHistory'] == ["two", "float4",
+                                                    "float5", "three", 
"float3"]
+
+    qtile.kill_window(float5)
+    assertFocused(qtile, "float3")
+    assert qtile.c.group.info()['focusHistory'] == ["two", "float4", "three", 
"float3"]
+
+    # The focus must be given to the previous window even if it's floating
+    qtile.c.group.focus_by_name("float4")
+    assert qtile.c.group.info()['focusHistory'] == ["two", "three", "float3", 
"float4"]
+    qtile.kill_window(float4)
+    assertFocused(qtile, "float3")
+    assert qtile.c.group.info()['focusHistory'] == ["two", "three", "float3"]
+
+    four = qtile.testWindow("four")
+    float6 = qtile.testDialog("float6")
+    five = qtile.testWindow("five")
+    qtile.c.group.focus_by_name("float3")
+    assert qtile.c.group.info()['focusHistory'] == ["two", "three", "four",
+                                                    "float6", "five", "float3"]
+
+    # Killing several unfocused windows before the current one, and then
+    # killing the current window, must focus the remaining most recently
+    # focused window
+    qtile.kill_window(five)
+    qtile.kill_window(four)
+    qtile.kill_window(float6)
+    assert qtile.c.group.info()['focusHistory'] == ["two", "three", "float3"]
+    qtile.kill_window(float3)
+    assertFocused(qtile, "three")
+    assert qtile.c.group.info()['focusHistory'] == ["two", "three"]
+
+
+@each_layout_config
+def test_desktop_notifications(qtile):
+    pytest.importorskip("Tkinter")
+
+    # Unlike normal floating windows such as dialogs, notifications don't steal
+    # focus when they spawn, so test them separately
+
+    # A notification fired in an empty group must not take focus
+    notif1 = qtile.testNotification("notif1")
+    assert qtile.c.group.info()['focus'] is None
+    qtile.kill_window(notif1)
+
+    # A window is spawned while a notification is displayed
+    notif2 = qtile.testNotification("notif2")
+    one = qtile.testWindow("one")
+    assert qtile.c.group.info()['focusHistory'] == ["one"]
+    qtile.kill_window(notif2)
+
+    # Another notification is fired, but the focus must not change
+    notif3 = qtile.testNotification("notif3")
+    assertFocused(qtile, 'one')
+    qtile.kill_window(notif3)
+
+    # Complicate the scenario with multiple windows and notifications
+
+    dialog1 = qtile.testDialog("dialog1")
+    two = qtile.testWindow("two")
+    notif4 = qtile.testNotification("notif4")
+    notif5 = qtile.testNotification("notif5")
+    assert qtile.c.group.info()['focusHistory'] == ["one", "dialog1", "two"]
+
+    dialog2 = qtile.testDialog("dialog2")
+    qtile.kill_window(notif5)
+    three = qtile.testWindow("three")
+    qtile.kill_window(one)
+    qtile.c.group.focus_by_name("two")
+    notif6 = qtile.testNotification("notif6")
+    notif7 = qtile.testNotification("notif7")
+    qtile.kill_window(notif4)
+    notif8 = qtile.testNotification("notif8")
+    assert qtile.c.group.info()['focusHistory'] == ["dialog1", "dialog2",
+                                                    "three", "two"]
+
+    dialog3 = qtile.testDialog("dialog3")
+    qtile.kill_window(dialog1)
+    qtile.kill_window(dialog2)
+    qtile.kill_window(notif6)
+    qtile.c.group.focus_by_name("three")
+    qtile.kill_window(notif7)
+    qtile.kill_window(notif8)
+    assert qtile.c.group.info()['focusHistory'] == ["two", "dialog3", "three"]
+
+
+@all_layouts_config
+def test_cycle_layouts(qtile):
+    qtile.testWindow("one")
+    qtile.testWindow("two")
+    qtile.testWindow("three")
+    qtile.testWindow("four")
+    qtile.c.group.focus_by_name("three")
+    assertFocused(qtile, "three")
+
+    # Cycling all the layouts must keep the current window focused
+    initial_layout_name = qtile.c.layout.info()['name']
+    while True:
+        qtile.c.next_layout()
+        if qtile.c.layout.info()['name'] == initial_layout_name:
+            break
+        # Use qtile.c.layout.info()['name'] in the assertion message, so we
+        # know which layout is buggy
+        assert qtile.c.window.info()['name'] == "three", 
qtile.c.layout.info()['name']
+
+    # Now try backwards
+    while True:
+        qtile.c.prev_layout()
+        if qtile.c.layout.info()['name'] == initial_layout_name:
+            break
+        # Use qtile.c.layout.info()['name'] in the assertion message, so we
+        # know which layout is buggy
+        assert qtile.c.window.info()['name'] == "three", 
qtile.c.layout.info()['name']


Reply via email to