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 - petr@cervinka.net
+
+- 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 - 0.11.0-1
+* Wed Jul 18 2018 John Dulaney - 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 - 0.11.1-2
+- Add unpackaged files %#{_bindir}/dqcmd %#{_bindir}/qcmd
+
+* Wed Feb 28 2018 John Dulaney - 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']