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']
