summaryrefslogtreecommitdiffstats
path: root/powerline/segments/i3wm.py
blob: 57ab3770c367ac0ea59250cbc3daec8f2672e3ff (plain)
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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)

import re

from powerline.theme import requires_segment_info
from powerline.bindings.wm import get_i3_connection

WORKSPACE_REGEX = re.compile(r'^[0-9]+: ?')

def workspace_groups(w):
	group = []
	if w.focused:
		group.append('workspace:focused')
		group.append('w_focused')
	if w.urgent:
		group.append('workspace:urgent')
		group.append('w_urgent')
	if w.visible:
		group.append('workspace:visible')
		group.append('w_visible')
	group.append('workspace')
	return group


def format_name(name, strip=False):
	if strip:
		return WORKSPACE_REGEX.sub('', name, count=1)
	return name


def is_empty_workspace(workspace, containers):
	if workspace.focused or workspace.visible:
		return False
	wins = [win for win in containers[workspace.name].leaves()]
	return False if len(wins) > 0 else True

WS_ICONS = {"multiple": "M"}

def get_icon(workspace, separator, icons, show_multiple_icons, ws_containers):
	icons_tmp = WS_ICONS
	icons_tmp.update(icons)
	icons = icons_tmp

	wins = [win for win in ws_containers[workspace.name].leaves() \
		if win.parent.scratchpad_state == 'none']
	if len(wins) == 0:
		return ''

	result = ''
	cnt = 0
	for key in icons:
		if not icons[key] or len(icons[key]) < 1:
			continue
		if any(key in win.window_class for win in wins if win.window_class):
			result += (separator if cnt > 0 else '') + icons[key]
			cnt += 1
	if not show_multiple_icons and cnt > 1:
		if 'multiple' in icons:
			return icons['multiple']
		else:
			return ''
	return result

@requires_segment_info
def workspaces(pl, segment_info, only_show=None, output=None, strip=0, format='{name}',
	icons=WS_ICONS, sort_workspaces=False, show_output=False, priority_workspaces=[],
	hide_empty_workspaces=False):
	'''Return list of used workspaces

	:param list only_show:
		Specifies which workspaces to show. Valid entries are ``"visible"``,
		``"urgent"`` and ``"focused"``. If omitted or ``null`` all workspaces
		are shown.
	:param str output:
		May be set to the name of an X output. If specified, only workspaces
		on that output are shown. Overrides automatic output detection by
		the lemonbar renderer and bindings.
		Use "__all__" to show workspaces on all outputs.
	:param int strip:
		Specifies how many characters from the front of each workspace name
		should be stripped (e.g. to remove workspace numbers). Defaults to zero.
	:param str format:
		Specifies the format used to display workspaces; defaults to ``{name}``.
		Valid fields are: ``name`` (workspace name), ``number`` (workspace number
		if present), `stipped_name`` (workspace name stripped of leading number),
		``icon`` (if available, icon for application running in the workspace,
		uses the ``multiple`` icon instead of multiple different icons), ``multi_icon``
		(similar to ``icon``, but does not use ``multiple``, instead joins all icons
		with a single space)
	:param dict icons:
		A dictionary mapping a substring of window classes to strings to be used as an
		icon for that window class.
		Further, there is a ``multiple`` icon for workspaces containing more than one
		window.
	:param bool sort_workspaces:
		Sort the workspaces displayed by their name according to the natural ordering.
	:param bool show_output:
		Shows the name of the output if more than one output is connected.
	:param list priority_workspaces:
		A list of workspace names to be sorted before any other workspaces in the given
		order.
	:param bool hide_empty_workspaces:
		Hides all workspaces without any open window.
		Also hides non-focussed workspaces containing only an open scratchpad.


	Highlight groups used: ``workspace`` or ``w_visible``, ``workspace:visible``, ``workspace`` or ``w_focused``, ``workspace:focused``, ``workspace`` or ``w_urgent``, ``workspace:urgent``, ``workspace`` or ``output``.
	'''
	conn = get_i3_connection()

	if not output == "__all__":
		output = output or segment_info.get('output')
	else:
		output = None

	if output:
		output = [output]
	else:
		output = [o.name for o in conn.get_outputs() if o.active]


	def sort_ws(ws):
		if sort_workspaces:
			def natural_key(ws):
				str = ws.name
				return [int(s) if s.isdigit() else s for s in re.split(r'(\d+)', str)]
			ws = sorted(ws, key=natural_key)
		result = []
		for n in priority_workspaces:
			result += [w for w in ws if w.name == n]
		return result + [w for w in ws if not w.name in priority_workspaces]

	ws_containers = {w_con.name : w_con for w_con in conn.get_tree().workspaces()}

	if len(output) <= 1:
		res = []
		if show_output:
			res += [{
				'contents': output[0],
				'highlight_groups': ['output']
			}]
		res += [{
			'contents': format.format(name = w.name[min(len(w.name), strip):],
				stripped_name = format_name(w.name, strip=True),
				number = w.num,
				icon = get_icon(w, '', icons, False, ws_containers),
				multi_icon = get_icon(w, ' ', icons, True, ws_containers)),
			'highlight_groups': workspace_groups(w)
			} for w in sort_ws(conn.get_workspaces()) \
					if (not only_show or any(getattr(w, tp) for tp in only_show)) \
					if w.output == output[0] \
					if not (hide_empty_workspaces and is_empty_workspace(w, ws_containers))]
		return res
	else:
		res = []
		for n in output:
			if show_output:
				res += [{
					'contents': n,
					'highlight_groups': ['output']
				}]
			res += [{
				'contents': format.format(name = w.name[min(len(w.name), strip):],
					stripped_name = format_name(w.name, strip=True),
					number = w.num,
					icon = get_icon(w, '', icons, False, ws_containers),
					multi_icon = get_icon(w, ' ', icons, True, ws_containers)),
				'highlight_groups': workspace_groups(w)
				} for w in sort_ws(conn.get_workspaces()) \
						if (not only_show or any(getattr(w, tp) for tp in only_show)) \
						if w.output == n \
						if not (hide_empty_workspaces and is_empty_workspace(w, ws_containers))]
		return res

@requires_segment_info
def workspace(pl, segment_info, workspace=None, strip=False, format=None, icons=WS_ICONS):
	'''Return the specified workspace name

	:param str workspace:
		Specifies which workspace to show. If unspecified, may be set by the
		``list_workspaces`` lister if used, otherwise falls back to
		currently focused workspace.

	:param bool strip:
		Specifies whether workspace numbers (in the ``1: name`` format) should
		be stripped from workspace names before being displayed. Defaults to false.
		Deprecated: Use {name} or {stripped_name} of format instead.

	:param str format:
		Specifies the format used to display workspaces; defaults to ``{name}``.
		Valid fields are: ``name`` (workspace name), ``number`` (workspace number
		if present), `stipped_name`` (workspace name stripped of leading number),
		``icon`` (if available, icon for application running in the workspace,
		uses the ``multiple`` icon instead of multiple different icons), ``multi_icon``
		(similar to ``icon``, but does not use ``multiple``, instead joins all icons
		with a single space)

	:param dict icons:
		A dictionary mapping a substring of window classes to strings to be used as an
		icon for that window class.
		Further, there is a ``multiple`` icon for workspaces containing more than one
		window.

	Highlight groups used: ``workspace`` or ``w_visible``, ``workspace:visible``, ``workspace`` or ``w_focused``, ``workspace:focused``, ``workspace`` or ``w_urgent``, ``workspace:urgent``, ``workspace``.
	'''
	if format == None:
		format = '{stripped_name}' if strip else '{name}'

	conn = get_i3_connection()
	ws_containers = {w_con.name : w_con for w_con in conn.get_tree().workspaces()}

	if workspace:
		try:
			w = next((
				w for w in conn.get_workspaces()
				if w.name == workspace
			))
		except StopIteration:
			return None
	elif segment_info.get('workspace'):
		w = segment_info['workspace']
	else:
		try:
			w = next((
				w for w in conn.get_workspaces()
				if w.focused
			))
		except StopIteration:
			return None

	return [{
		'contents': format.format(name = w.name,
			stripped_name = format_name(w.name, strip=True),
			number = w.num,
			icon = get_icon(w, '', icons, False, ws_containers),
			multi_icon = get_icon(w, ' ', icons, True, ws_containers)),
		'highlight_groups': workspace_groups(w)
		}]


@requires_segment_info
def mode(pl, segment_info, names={'default': None}):
	'''Returns current i3 mode

	:param dict names:
		Specifies the string to show for various modes.
		Use ``null`` to hide a mode (``default`` is hidden by default).

	Highligh groups used: ``mode``
	'''
	mode = segment_info['mode']
	if mode in names:
		return names[mode]
	return mode


def scratchpad_groups(w):
	group = []
	if w.urgent:
		group.append('scratchpad:urgent')
	if w.nodes[0].focused:
		group.append('scratchpad:focused')
	if w.workspace().name != '__i3_scratch':
		group.append('scratchpad:visible')
	group.append('scratchpad')
	return group


SCRATCHPAD_ICONS = {
	'fresh': 'O',
	'changed': 'X',
}


def scratchpad(pl, icons=SCRATCHPAD_ICONS):
	'''Returns the windows currently on the scratchpad

	:param dict icons:
		Specifies the strings to show for the different scratchpad window states. Must
		contain the keys ``fresh`` and ``changed``.

	Highlight groups used: ``scratchpad`` or ``scratchpad:visible``, ``scratchpad`` or ``scratchpad:focused``, ``scratchpad`` or ``scratchpad:urgent``.
	'''

	return [
		{
			'contents': icons.get(w.scratchpad_state, icons['changed']),
			'highlight_groups': scratchpad_groups(w)
		}
		for w in get_i3_connection().get_tree().descendants()
		if w.scratchpad_state != 'none'
	]

def active_window(pl, cutoff=100):
	'''Returns the title of the currently active window.

	:param int cutoff:
		Maximum title length. If the title is longer, the window_class is used instead.

	Highlight groups used: ``active_window_title``.
	'''

	focused = get_i3_connection().get_tree().find_focused()
	cont = focused.name
	if len(cont) > cutoff:
		cont = focused.window_class

	return [{'contents': cont, 'highlight_groups': ['active_window_title']}] if focused.name != focused.workspace().name else []