Repository: qtile/qtile

Issue:
Qtile doesn't remember focus between layout switches
Here's a test that fails because of it. We should fix that :-)



Discussion:

Resolving patch:
diff --git a/libqtile/config.py b/libqtile/config.py
--- a/libqtile/config.py
+++ b/libqtile/config.py
@@ -443,7 +443,8 @@ def __init__(self, title=None, wm_class=None, role=None, wm_type=None,
     def compare(self, client):
         for _type, rule in self._rules:
             if _type == "net_wm_pid":
-                match_func = lambda value: rule == value
+                def match_func(value):
+                    return rule == value
             else:
                 match_func = getattr(rule, 'match', None) or \
                     getattr(rule, 'count')
diff --git a/libqtile/layout/stack.py b/libqtile/layout/stack.py
--- a/libqtile/layout/stack.py
+++ b/libqtile/layout/stack.py
@@ -22,8 +22,10 @@
 
 
 class _WinStack(object):
-    split = False
-    _current = 0
+    def __init__(self, autosplit=False):
+        self.split = autosplit
+        self._current = 0
+        self.lst = []
 
     @property
     def current(self):
@@ -42,9 +44,6 @@ def cw(self):
             return None
         return self.lst[self.current]
 
-    def __init__(self):
-        self.lst = []
-
     def toggleSplit(self):
         self.split = False if self.split else True
 
@@ -108,8 +107,8 @@ def __getitem__(self, i):
     def __contains__(self, client):
         return client in self.lst
 
-    def __repr__(self):
-        return "_WinStack(%s, %s)" % (
+    def __str__(self):
+        return "_WinStack: %s, %s" % (
             self.current, str([i.name for i in self])
         )
 
@@ -143,10 +142,7 @@ class Stack(Layout):
     def __init__(self, **config):
         Layout.__init__(self, **config)
         self.add_defaults(Stack.defaults)
-        self.stacks = [_WinStack() for i in range(self.num_stacks)]
-        for stack in self.stacks:
-            if self.autosplit:
-                stack.split = True
+        self.stacks = [_WinStack(autosplit=self.autosplit) for i in range(self.num_stacks)]
 
     @property
     def currentStack(self):
@@ -169,10 +165,7 @@ def clients(self):
     def clone(self, group):
         c = Layout.clone(self, group)
         # These are mutable
-        c.stacks = [_WinStack() for i in self.stacks]
-        for stack in c.stacks:
-            if self.autosplit:
-                stack.split = True
+        c.stacks = [_WinStack(autosplit=self.autosplit) for i in self.stacks]
         return c
 
     def _findNext(self, lst, offset):
@@ -383,7 +376,7 @@ def cmd_add(self):
         """
             Add another stack to the layout.
         """
-        newstack = _WinStack()
+        newstack = _WinStack(autosplit=self.autosplit)
         if self.autosplit:
             newstack.split = True
         self.stacks.append(newstack)
diff --git a/libqtile/widget/graph.py b/libqtile/widget/graph.py
--- a/libqtile/widget/graph.py
+++ b/libqtile/widget/graph.py
@@ -313,7 +313,8 @@ def update_graph(self):
     @staticmethod
     def get_main_iface():
         filename = "/proc/net/route"
-        make_route = lambda line: dict(zip(['iface', 'dest'], line.split()))
+        def make_route(line):
+            dict(zip(['iface', 'dest'], line.split()))
         routes = [make_route(line) for line in list(open(filename))[1:]]
         try:
             return next(


Test patch:
diff --git a/test/test_manager.py b/test/test_manager.py
--- a/test/test_manager.py
+++ b/test/test_manager.py
@@ -697,6 +697,23 @@ def test_resize_(self):
         raise AssertionError("Screen did not resize")
 
 
+@Xephyr(False, TestConfig())
+def test_focus_stays_on_layout_switch(xephyr):
+    xephyr.testWindow("one")
+    xephyr.testWindow("two")
+
+    # switch to a double stack layout
+    xephyr.c.nextlayout()
+
+    # focus on a different window than the default
+    xephyr.c.layout.next()
+
+    # toggle the layout
+    xephyr.c.nextlayout()
+    xephyr.c.prevlayout()
+
+    assert xephyr.c.window.info()['name'] == 'one'
+
 # Due to https://github.com/nose-devs/nose/issues/478, nose 1.1.2 ignores
 # attributes on yielded functions. Workaround is to attach the attribute
 # to the generator function. Can be removed once the issue is resolved.