-
-
Notifications
You must be signed in to change notification settings - Fork 692
/
lazy.py
199 lines (157 loc) · 6.71 KB
/
lazy.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# Copyright (c) 2019, Sean Vig. All rights reserved.
#
# 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.
from __future__ import annotations
from typing import TYPE_CHECKING
from libqtile.command.client import InteractiveCommandClient
from libqtile.command.graph import CommandGraphCall, CommandGraphNode
from libqtile.command.interface import CommandInterface
from libqtile.log_utils import logger
if TYPE_CHECKING:
from typing import Callable, Iterable
from libqtile.command.graph import SelectorType
from libqtile.config import _Match
class LazyCall:
def __init__(self, call: CommandGraphCall, args: tuple, kwargs: dict) -> None:
"""The lazily evaluated command graph call
Parameters
----------
call: CommandGraphCall
The call that is made
args: tuple
The args passed to the call when it is evaluated.
kwargs: dict
The kwargs passed to the call when it is evaluated.
"""
self._call = call
self._args = args
self._kwargs = kwargs
self._focused: _Match | None = None
self._if_no_focused: bool = False
self._layouts: set[str] = set()
self._when_floating = True
self._condition: bool | None = None
self._func: Callable[[], bool] = lambda: True
def __call__(self, *args, **kwargs):
"""Convenience method to allow users to pass arguments to
functions decorated with `@lazy.function`.
@lazy.function
def my_function(qtile, pos_arg, keyword_arg=False):
pass
...
Key(... my_function("Positional argument", keyword_arg=True))
"""
# We need to return a new object so the arguments are not shared between
# a single instance of the LazyCall object.
return LazyCall(self._call, (*self._args, *args), {**self._kwargs, **kwargs})
@property
def selectors(self) -> list[SelectorType]:
"""The selectors for the given call"""
return self._call.selectors
@property
def name(self) -> str:
"""The name of the given call"""
return self._call.name
@property
def args(self) -> tuple:
"""The args to the given call"""
return self._args
@property
def kwargs(self) -> dict:
"""The kwargs to the given call"""
return self._kwargs
def when(
self,
focused: _Match | None = None,
if_no_focused: bool = False,
layout: Iterable[str] | str | None = None,
when_floating: bool = True,
func: Callable | None = None,
condition: bool | None = None,
) -> "LazyCall":
"""Enable call only for matching criteria.
Keyword parameters
----------
focused: Match or None
Match criteria to enable call for the current window.
if_no_focused: bool
Whether or not the `focused` attribute should also
match when there is no focused window.
This is useful when the `focused` attribute is e.g. set
to a regex that should also match when there is
no focused window.
By default this is set to `False` so that the focused
attribute only matches when there is actually a focused window.
layout: str, Iterable[str], or None
Restrict call to one or more layouts.
If None, enable the call for all layouts.
when_floating: bool
Enable call when the current window is floating.
func: callable
Enable call when the result of the callable evaluates to True
condition: a boolean value to determine whether the lazy object should
be run. Unlike 'func', the condition is evaluated once when the config
file is first loaded.
"""
self._focused = focused
self._if_no_focused = if_no_focused
self._condition = condition
if func is not None:
self._func = func
if layout is not None:
self._layouts = {layout} if isinstance(layout, str) else set(layout)
self._when_floating = when_floating
return self
def check(self, q) -> bool:
cur_win_floating = q.current_window and q.current_window.floating
if self._condition is False:
return False
if self._focused:
if q.current_window and not self._focused.compare(q.current_window):
return False
if not q.current_window and not self._if_no_focused:
return False
if cur_win_floating and not self._when_floating:
return False
if self._layouts and q.current_layout.name not in self._layouts:
return False
if self._func is not None:
try:
result = self._func()
except Exception:
logger.exception("Error when running function in lazy call. Ignoring.")
result = True
if not result:
return False
return True
class LazyCommandInterface(CommandInterface):
"""A lazy loading command object
Allows all commands and items to be resolved at run time, and returns
lazily evaluated commands.
"""
def execute(self, call: CommandGraphCall, args: tuple, kwargs: dict) -> LazyCall:
"""Lazily evaluate the given call"""
return LazyCall(call, args, kwargs)
def has_command(self, node: CommandGraphNode, command: str) -> bool:
"""Lazily resolve the given command"""
return True
def has_item(self, node: CommandGraphNode, object_type: str, item: str | int) -> bool:
"""Lazily resolve the given item"""
return True
lazy = InteractiveCommandClient(LazyCommandInterface())