i3 segments rework (#2159)

rework i3wm segments
This commit is contained in:
Philip Wellnitz 2021-03-15 03:45:17 +09:00 committed by GitHub
parent a1309cc116
commit 7310d53ade
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 305 additions and 84 deletions

View File

@ -51,6 +51,7 @@
"cwd:current_folder": "information:regular",
"cwd:divider": { "fg": "gray7", "bg": "gray4", "attrs": [] },
"virtualenv": { "fg": "white", "bg": "darkcyan", "attrs": [] },
"attached_clients": { "fg": "gray8", "bg": "gray0", "attrs": [] }
"attached_clients": { "fg": "gray8", "bg": "gray0", "attrs": [] },
"workspace": "information:regular"
}
}

View File

@ -1,10 +1,6 @@
{
"segments": {
"right": [
{
"function": "powerline.segments.common.wthr.weather",
"priority": 50
},
{
"function": "powerline.segments.common.time.date"
},
@ -15,14 +11,6 @@
"format": "%H:%M",
"istime": true
}
},
{
"function": "powerline.segments.common.mail.email_imap_alert",
"priority": 10,
"args": {
"username": "",
"password": ""
}
}
]
}

View File

@ -6,17 +6,18 @@ 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
@ -28,41 +29,152 @@ def format_name(name, strip=False):
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):
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`` or ``w_focused``, ``workspace`` or ``w_urgent``.
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
return [
{
'contents': w.name[strip:],
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 get_i3_connection().get_workspaces()
if ((not only_show or any(getattr(w, typ) for typ in only_show))
and (not output or w.output == output))
]
} 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):
def workspace(pl, segment_info, workspace=None, strip=False, format=None, icons=WS_ICONS):
'''Return the specified workspace name
:param str workspace:
@ -73,13 +185,35 @@ def workspace(pl, segment_info, workspace=None, strip=False):
: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.
Highlight groups used: ``workspace`` or ``w_visible``, ``workspace`` or ``w_focused``, ``workspace`` or ``w_urgent``.
: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 get_i3_connection().get_workspaces()
w for w in conn.get_workspaces()
if w.name == workspace
))
except StopIteration:
@ -89,14 +223,18 @@ def workspace(pl, segment_info, workspace=None, strip=False):
else:
try:
w = next((
w for w in get_i3_connection().get_workspaces()
w for w in conn.get_workspaces()
if w.focused
))
except StopIteration:
return None
return [{
'contents': format_name(w.name, strip=strip),
'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)
}]
@ -153,3 +291,19 @@ def scratchpad(pl, icons=SCRATCHPAD_ICONS):
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 []

View File

@ -33,6 +33,7 @@ class PowerlineDummyTest(object):
class PowerlineTestSuite(object):
def __init__(self, name):
self.name = name
self.suite = ''
def __enter__(self):
self.saved_current_suite = os.environ['POWERLINE_CURRENT_SUITE']

View File

@ -1001,81 +1001,158 @@ class TestWthr(TestCommon):
class TestI3WM(TestCase):
@staticmethod
def get_workspaces():
def test_workspaces(self):
class Conn(object):
def get_tree(self):
return self
def descendents(self):
nodes_unfocused = [Args(focused = False)]
nodes_focused = [Args(focused = True)]
workspace_scratch = lambda: Args(name='__i3_scratch')
workspace_noscratch = lambda: Args(name='2: w2')
return [
Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_unfocused),
Args(scratchpad_state='changed', urgent=True, workspace=workspace_noscratch, nodes=nodes_focused),
Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_unfocused),
Args(scratchpad_state=None, urgent=False, workspace=workspace_noscratch, nodes=nodes_unfocused),
Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_focused),
Args(scratchpad_state=None, urgent=True, workspace=workspace_noscratch, nodes=nodes_unfocused),
]
def workspaces(self):
return iter([
Args(name='1: w1', output='LVDS1', focused = False, urgent = False, visible = False),
Args(name='2: w2', output='LVDS1', focused = False, urgent = False, visible = True),
Args(name='3: w3', output='HDMI1', focused = False, urgent = True, visible = True),
Args(name='4: w4', output='DVI01', focused = True, urgent = True, visible = True),
Args(name='1: w1', output='LVDS1', focused=False, urgent=False, visible=False, num=1, leaves=lambda: []),
Args(name='2: w2', output='LVDS1', focused=False, urgent=False, visible=True, num=2, leaves=lambda: []),
Args(name='3: w3', output='HDMI1', focused=False, urgent=True, visible=True, num=3, leaves=lambda: []),
Args(name='4: w4', output='DVI01', focused=True, urgent=True, visible=True, num=None, leaves=lambda: [])
])
def get_workspaces(self):
return iter([
Args(name='1: w1', output='LVDS1', focused=False, urgent=False, visible=False, num=1, leaves=lambda: []),
Args(name='2: w2', output='LVDS1', focused=False, urgent=False, visible=True, num=2, leaves=lambda: []),
Args(name='3: w3', output='HDMI1', focused=False, urgent=True, visible=True, num=3, leaves=lambda: []),
Args(name='4: w4', output='DVI01', focused=True, urgent=True, visible=True, num=None, leaves=lambda: [])
])
def get_outputs(self):
return iter([
Args(name='LVDS1', active=True),
Args(name='HDMI1', active=True),
Args(name='DVI01', active=True),
Args(name='HDMI2', active=False),
])
def test_workspaces(self):
pl = Pl()
with replace_attr(i3wm, 'get_i3_connection', lambda: Args(get_workspaces=self.get_workspaces)):
with replace_attr(i3wm, 'get_i3_connection', lambda: Conn()):
segment_info = {}
self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info), [
{'contents': '1: w1', 'highlight_groups': ['workspace']},
{'contents': '2: w2', 'highlight_groups': ['w_visible', 'workspace']},
{'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
{'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
{'contents': '2: w2', 'highlight_groups': ['workspace:visible', 'w_visible', 'workspace']},
{'contents': '3: w3', 'highlight_groups': ['workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']},
{'contents': '4: w4', 'highlight_groups': ['workspace:focused', 'w_focused', 'workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']},
])
self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=None), [
{'contents': '1: w1', 'highlight_groups': ['workspace']},
{'contents': '2: w2', 'highlight_groups': ['w_visible', 'workspace']},
{'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
{'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
{'contents': '2: w2', 'highlight_groups': ['workspace:visible', 'w_visible', 'workspace']},
{'contents': '3: w3', 'highlight_groups': ['workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']},
{'contents': '4: w4', 'highlight_groups': ['workspace:focused', 'w_focused', 'workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']},
])
self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['focused', 'urgent']), [
{'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
{'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
{'contents': '3: w3', 'highlight_groups': ['workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']},
{'contents': '4: w4', 'highlight_groups': ['workspace:focused', 'w_focused', 'workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']},
])
self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible']), [
{'contents': '2: w2', 'highlight_groups': ['w_visible', 'workspace']},
{'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
{'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
{'contents': '2: w2', 'highlight_groups': ['workspace:visible', 'w_visible', 'workspace']},
{'contents': '3: w3', 'highlight_groups': ['workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']},
{'contents': '4: w4', 'highlight_groups': ['workspace:focused', 'w_focused', 'workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']},
])
self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], strip=3), [
{'contents': 'w2', 'highlight_groups': ['w_visible', 'workspace']},
{'contents': 'w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
{'contents': 'w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
{'contents': 'w2', 'highlight_groups': ['workspace:visible', 'w_visible', 'workspace']},
{'contents': 'w3', 'highlight_groups': ['workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']},
{'contents': 'w4', 'highlight_groups': ['workspace:focused', 'w_focused', 'workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']},
])
self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['focused', 'urgent'], output='DVI01'), [
{'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
{'contents': '4: w4', 'highlight_groups': ['workspace:focused', 'w_focused', 'workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']},
])
self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], output='HDMI1'), [
{'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
{'contents': '3: w3', 'highlight_groups': ['workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']},
])
self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], strip=3, output='LVDS1'), [
{'contents': 'w2', 'highlight_groups': ['w_visible', 'workspace']},
{'contents': 'w2', 'highlight_groups': ['workspace:visible', 'w_visible', 'workspace']},
])
segment_info['output'] = 'LVDS1'
self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], output='HDMI1'), [
{'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
{'contents': '3: w3', 'highlight_groups': ['workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']},
])
self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], strip=3), [
{'contents': 'w2', 'highlight_groups': ['w_visible', 'workspace']},
{'contents': 'w2', 'highlight_groups': ['workspace:visible', 'w_visible', 'workspace']},
])
def test_workspace(self):
class Conn(object):
def get_tree(self):
return self
def descendents(self):
nodes_unfocused = [Args(focused = False)]
nodes_focused = [Args(focused = True)]
workspace_scratch = lambda: Args(name='__i3_scratch')
workspace_noscratch = lambda: Args(name='2: w2')
return [
Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_unfocused),
Args(scratchpad_state='changed', urgent=True, workspace=workspace_noscratch, nodes=nodes_focused),
Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_unfocused),
Args(scratchpad_state=None, urgent=False, workspace=workspace_noscratch, nodes=nodes_unfocused),
Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_focused),
Args(scratchpad_state=None, urgent=True, workspace=workspace_noscratch, nodes=nodes_unfocused),
]
def workspaces(self):
return iter([
Args(name='1: w1', output='LVDS1', focused=False, urgent=False, visible=False, num=1, leaves=lambda: []),
Args(name='2: w2', output='LVDS1', focused=False, urgent=False, visible=True, num=2, leaves=lambda: []),
Args(name='3: w3', output='HDMI1', focused=False, urgent=True, visible=True, num=3, leaves=lambda: []),
Args(name='4: w4', output='DVI01', focused=True, urgent=True, visible=True, num=None, leaves=lambda: [])
])
def get_workspaces(self):
return iter([
Args(name='1: w1', output='LVDS1', focused=False, urgent=False, visible=False, num=1, leaves=lambda: []),
Args(name='2: w2', output='LVDS1', focused=False, urgent=False, visible=True, num=2, leaves=lambda: []),
Args(name='3: w3', output='HDMI1', focused=False, urgent=True, visible=True, num=3, leaves=lambda: []),
Args(name='4: w4', output='DVI01', focused=True, urgent=True, visible=True, num=None, leaves=lambda: [])
])
def get_outputs(self):
return iter([
Args(name='LVDS1', active=True),
Args(name='HDMI1', active=True),
Args(name='DVI01', active=True),
Args(name='HDMI2', active=False),
])
pl = Pl()
with replace_attr(i3wm, 'get_i3_connection', lambda: Args(get_workspaces=self.get_workspaces)):
with replace_attr(i3wm, 'get_i3_connection', lambda: Conn()):
segment_info = {}
self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='1: w1'), [
{'contents': '1: w1', 'highlight_groups': ['workspace']},
])
self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='3: w3', strip=True), [
{'contents': 'w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
{'contents': 'w3', 'highlight_groups': ['workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']},
])
self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='9: w9'), None)
self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info), [
{'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
{'contents': '4: w4', 'highlight_groups': ['workspace:focused', 'w_focused', 'workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']},
])
segment_info['workspace'] = next(self.get_workspaces())
segment_info['workspace'] = next(Conn().get_workspaces())
self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='4: w4'), [
{'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
{'contents': '4: w4', 'highlight_groups': ['workspace:focused', 'w_focused', 'workspace:urgent', 'w_urgent', 'workspace:visible', 'w_visible', 'workspace']},
])
self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, strip=True), [
{'contents': 'w1', 'highlight_groups': ['workspace']},