Repository: qtile/qtile

Issue:
How are the tasks in TaskList sorted?
It seems that the task in the TaskList are sorted differently than what the `lazy.layout.next()` thinks. As  when I use the `lazy.layout.next()` the focus in the TaskList jumps around but seldom on the task I would expect...
How are the tasks in TaskList sorted?
It seems that the task in the TaskList are sorted differently than what the `lazy.layout.next()` thinks. As  when I use the `lazy.layout.next()` the focus in the TaskList jumps around but seldom on the task I would expect...


Discussion:
Yep, can confirm.
This is really an architecture problem: each layout keeps its own "list" or other state of what order windows are in. There's no standardized way for TaskList (or indeed, the qtile core) to ask a layout what order its windows are currently in.

Fixing this in a nice way would enable a lot of features, including one I've wanted for a long time. Unfortunately, you'd probably have to touch each layout and understand it to figure out how to linearize things into a reasonable way. Maybe it wouldn't be that hard, though, who knows :)
Options for this have been discussed in #1334 (probably among others).
Oh right, I forgot all about that discussion :)
I feel this is especially relevant to the max layout. I have just switched to Qtile, and it'd really would become the endgame for me in case the TaskList would be sorted normally just for the max layout.

It feels that max layout doesn't have a lot of kw params, so it must fairly minimal in its codebase. Do you think there is a way to only hack the Max layout so that the order in the TaskList would be sensible at least according the to Max layout only? I understand that it might take a lot of effort to fix this for all the existing layouts (like you mentioned in the linked PR), but the most relevant is the Max, as there is no other way to tell the order and how to switch to a needed window right away. I think for other layouts the order in the TaskList is not as critical. 

In case this is out of your reach within the nearest future, do you think writing my CustomMaxLayout would let me somehow achieve this? Coming from i3 (tabs) awesome (tasklist) this is the major blockade. :(
Storing windows in list instead of unordered set inside Group class seems to fix this.
Really? Nice! PR welcome. :)
Yep, can confirm.
This is really an architecture problem: each layout keeps its own "list" or other state of what order windows are in. There's no standardized way for TaskList (or indeed, the qtile core) to ask a layout what order its windows are currently in.

Fixing this in a nice way would enable a lot of features, including one I've wanted for a long time. Unfortunately, you'd probably have to touch each layout and understand it to figure out how to linearize things into a reasonable way. Maybe it wouldn't be that hard, though, who knows :)
Options for this have been discussed in #1334 (probably among others).
Oh right, I forgot all about that discussion :)
I feel this is especially relevant to the max layout. I have just switched to Qtile, and it'd really would become the endgame for me in case the TaskList would be sorted normally just for the max layout.

It feels that max layout doesn't have a lot of kw params, so it must fairly minimal in its codebase. Do you think there is a way to only hack the Max layout so that the order in the TaskList would be sensible at least according the to Max layout only? I understand that it might take a lot of effort to fix this for all the existing layouts (like you mentioned in the linked PR), but the most relevant is the Max, as there is no other way to tell the order and how to switch to a needed window right away. I think for other layouts the order in the TaskList is not as critical. 

In case this is out of your reach within the nearest future, do you think writing my CustomMaxLayout would let me somehow achieve this? Coming from i3 (tabs) awesome (tasklist) this is the major blockade. :(
Storing windows in list instead of unordered set inside Group class seems to fix this.
Really? Nice! PR welcome. :)

Resolving patch:
diff --git a/libqtile/group.py b/libqtile/group.py
--- a/libqtile/group.py
+++ b/libqtile/group.py
@@ -44,7 +44,7 @@ def __init__(self, name, layout=None, label=None):
         self.name = name
         self.label = name if label is None else label
         self.custom_layout = layout  # will be set on _configure
-        self.windows = set()
+        self.windows = []
         self.qtile = None
         self.layouts = []
         self.floating_layout = None
@@ -61,7 +61,7 @@ def _configure(self, layouts, floating_layout, qtile):
         self.screen = None
         self.current_layout = 0
         self.focus_history = []
-        self.windows = set()
+        self.windows = []
         self.qtile = qtile
         self.layouts = [i.clone(self) for i in layouts]
         self.floating_layout = floating_layout
@@ -235,7 +235,8 @@ def info(self):
 
     def add(self, win, focus=True, force=False):
         hook.fire("group_window_add", self, win)
-        self.windows.add(win)
+        if win not in self.windows:
+            self.windows.append(win)
         win.group = self
         if self.qtile.config.auto_fullscreen and win.wants_to_fullscreen:
             win._float_state = FloatStates.FULLSCREEN


Test patch:
diff --git a/test/test_group.py b/test/test_group.py
new file mode 100644
--- /dev/null
+++ b/test/test_group.py
@@ -0,0 +1,66 @@
+# Copyright (c) 2011 Florian Mounier
+# Copyright (c) 2012, 2014 Tycho Andersen
+# Copyright (c) 2013 Craig Barnes
+# Copyright (c) 2014 Sean Vig
+#
+# 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 random
+import pytest
+import libqtile.config
+from libqtile import layout
+from libqtile.confreader import Config
+
+class GroupConfig(Config):
+    auto_fullscreen = True
+    groups = [
+        libqtile.config.Group("a")
+    ]
+    layouts = [
+        layout.Max()
+    ]
+    floating_layout = libqtile.resources.default_config.floating_layout
+    keys = []
+    mouse = []
+    screens = []
+
+
+group_config = pytest.mark.parametrize("manager", [GroupConfig], indirect=True)
+
+
+@group_config
+def test_window_order(manager):
+    # windows to add
+    windows_name = ["one","two","three","four","five","six","seven","eight","nine","ten"]
+    windows = {}
+
+    # Add windows one by one
+    for win in windows_name:
+        windows[win] = manager.test_window(win)
+
+    # Winmust be sotred in the same order as they were created
+    assert windows_name == manager.c.group.info()["windows"]
+
+    # Randomly remove 5 windows and see if orders remains persistant
+    for i in range(5):
+        win_to_remove = random.choice(windows_name)
+        windows_name.remove(win_to_remove)
+        manager.kill_window(windows[win_to_remove])
+        del windows[win_to_remove]
+        assert windows_name == manager.c.group.info()["windows"]