1# Copyright (c) 2012, Tycho Andersen. All rights reserved.
2#
3# Permission is hereby granted, free of charge, to any person obtaining a copy
4# of this software and associated documentation files (the "Software"), to deal
5# in the Software without restriction, including without limitation the rights
6# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7# copies of the Software, and to permit persons to whom the Software is
8# furnished to do so, subject to the following conditions:
9#
10# The above copyright notice and this permission notice shall be included in
11# all copies or substantial portions of the Software.
12#
13# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19# SOFTWARE.
20
21
22from libqtile import hook
23from libqtile.scratchpad import ScratchPad
24
25
26class QtileState:
27    """Represents the state of the qtile object
28
29    Primarily used for restoring state across restarts; any additional state
30    which doesn't fit nicely into X atoms can go here.
31    """
32    def __init__(self, qtile):
33        # Note: window state is saved and restored via _NET_WM_STATE, so
34        # the only thing we need to restore here is the layout and screen
35        # configurations.
36        self.groups = []
37        self.screens = {}
38        self.current_screen = 0
39        self.scratchpads = {}
40        self.orphans = []
41
42        for group in qtile.groups:
43            if isinstance(group, ScratchPad):
44                self.scratchpads[group.name] = group.get_state()
45                for dd in group.dropdowns.values():
46                    dd.hide()
47            else:
48                self.groups.append((group.name, group.layout.name, group.label))
49
50        for index, screen in enumerate(qtile.screens):
51            self.screens[index] = screen.group.name
52            if screen == qtile.current_screen:
53                self.current_screen = index
54
55    def apply(self, qtile):
56        """
57        Rearrange the windows in the specified Qtile object according to this
58        QtileState.
59        """
60        for (group, layout, label) in self.groups:
61            try:
62                qtile.groups_map[group].layout = layout
63                qtile.groups_map[group].label = label
64            except KeyError:
65                qtile.add_group(group, layout, label=label)
66
67        for (screen, group) in self.screens.items():
68            try:
69                group = qtile.groups_map[group]
70                qtile.screens[screen].set_group(group)
71            except (KeyError, IndexError):
72                pass  # group or screen missing
73
74        for group in qtile.groups:
75            if isinstance(group, ScratchPad) and group.name in self.scratchpads:
76                orphans = group.restore_state(self.scratchpads.pop(group.name))
77                self.orphans.extend(orphans)
78        for sp_state in self.scratchpads.values():
79            for _, pid, _ in sp_state:
80                self.orphans.append(pid)
81        if self.orphans:
82            hook.subscribe.client_new(self.handle_orphan_dropdowns)
83
84        qtile.focus_screen(self.current_screen)
85
86    def handle_orphan_dropdowns(self, client):
87        """
88        Remove any windows from now non-existent scratchpad groups.
89        """
90        client_pid = client.get_pid()
91        if client_pid in self.orphans:
92            self.orphans.remove(client_pid)
93            client.group = None
94            if not self.orphans:
95                hook.unsubscribe.client_new(self.handle_orphan_dropdowns)
96