Merge branch 'pr-1517' into develop

Closes #1517
This commit is contained in:
Foo 2016-02-04 04:44:25 +03:00
commit c0b56d3888
10 changed files with 468 additions and 29 deletions

View File

@ -16,8 +16,6 @@ their type and ``segments`` key with a list of segments (a bit more details in
More information in :ref:`Writing listers <dev-listers>` section.
Currently only Vim listers are available.
Vim listers
-----------
@ -29,3 +27,9 @@ Pdb listers
.. automodule:: powerline.listers.pdb
:members:
i3wm listers
----------
.. automodule:: powerline.listers.i3wm
:members:

View File

@ -517,6 +517,22 @@ Pdb
Equal to the length of :py:attr:`pdb.Pdb.stack` at the first invocation of
the prompt decremented by one.
i3wm
----
``mode``
Currently active i3 mode (as a string).
``output``
``xrandr`` output name currently drawing to. Currently only available
in lemonbar bindings.
``workspace``
dictionary containing the workspace name under the key ``"name"`` and
boolean values for the ``"visible"``, ``"urgent"`` and ``"focused"``
keys, indicating the state of the workspace. Currently only provided by
the :py:func:`powerline.listers.i3wm.workspace_lister` lister.
Segment class
=============

View File

@ -9,8 +9,8 @@ import subprocess
from threading import Lock, Timer
from powerline.lemonbar import LemonbarPowerline
from powerline.lib.shell import run_cmd
from powerline.commands.lemonbar import get_argparser
from powerline.bindings.wm import get_connected_xrandr_outputs
if __name__ == '__main__':
@ -20,13 +20,8 @@ if __name__ == '__main__':
powerline = LemonbarPowerline()
powerline.update_renderer()
bars = []
active_screens = [match.groupdict() for match in re.finditer(
'^(?P<name>[0-9A-Za-z-]+) connected (?P<width>\d+)x(?P<height>\d+)\+(?P<x>\d+)\+(?P<y>\d+)',
run_cmd(powerline.pl, ['xrandr', '-q']),
re.MULTILINE
)]
for screen in active_screens:
for screen in get_connected_xrandr_outputs(powerline.pl):
command = [args.bar_command, '-g', '{0}x{1}+{2}'.format(screen['width'], args.height, screen['x'])] + args.args[1:]
process = subprocess.Popen(command, stdin=subprocess.PIPE)
bars.append((screen['name'], process, int(screen['width']) / 5))

View File

@ -0,0 +1,38 @@
# 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.lib.shell import run_cmd
conn = None
def get_i3_connection():
'''Return a valid, cached i3 Connection instance
'''
global conn
if not conn:
try:
import i3ipc
except ImportError:
import i3 as conn
else:
conn = i3ipc.Connection()
return conn
XRANDR_OUTPUT_RE = re.compile(r'^(?P<name>[0-9A-Za-z-]+) connected (?P<width>\d+)x(?P<height>\d+)\+(?P<x>\d+)\+(?P<y>\d+)', re.MULTILINE)
def get_connected_xrandr_outputs(pl):
'''Iterate over xrandr outputs
Outputs are represented by a dictionary with ``name``, ``width``,
``height``, ``x`` and ``y`` keys.
'''
return (match.groupdict() for match in XRANDR_OUTPUT_RE.finditer(
run_cmd(pl, ['xrandr', '-q'])
))

View File

@ -78,3 +78,11 @@ def mergedicts_copy(d1, d2):
else:
ret[k] = d2[k]
return ret
def updated(d, *args, **kwargs):
'''Copy dictionary and update it with provided arguments
'''
d = d.copy()
d.update(*args, **kwargs)
return d

View File

@ -21,6 +21,12 @@ class WithPath(object):
def import_function(function_type, name, data, context, echoerr, module):
havemarks(name, module)
if module == 'powerline.segments.i3wm' and name == 'workspaces':
echoerr(context='Warning while checking segments (key {key})'.format(key=context.key),
context_mark=name.mark,
problem='segment {0} from {1} is deprecated'.format(name, module),
problem_mark=module.mark)
with WithPath(data['import_paths']):
try:
func = getattr(__import__(str(module), fromlist=[str(name)]), str(name))

68
powerline/listers/i3wm.py Normal file
View File

@ -0,0 +1,68 @@
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
from powerline.theme import requires_segment_info
from powerline.lib.dict import updated
from powerline.bindings.wm import get_i3_connection, get_connected_xrandr_outputs
@requires_segment_info
def output_lister(pl, segment_info):
'''List all outputs in segment_info format
'''
return (
(
updated(segment_info, output=output['name']),
{
'draw_inner_divider': None
}
)
for output in get_connected_xrandr_outputs(pl)
)
@requires_segment_info
def workspace_lister(pl, segment_info, only_show=None, output=None):
'''List all workspaces in segment_info format
Sets the segment info values of ``workspace`` and ``output`` to the name of
the i3 workspace and the ``xrandr`` output respectively and the keys
``"visible"``, ``"urgent"`` and ``"focused`` to a boolean indicating these
states.
:param list only_show:
Specifies which workspaces to list. Valid entries are ``"visible"``,
``"urgent"`` and ``"focused"``. If omitted or ``null`` all workspaces
are listed.
:param str output:
May be set to the name of an X output. If specified, only workspaces
on that output are listed. Overrides automatic output detection by
the lemonbar renderer and bindings. Set to ``false`` to force
all workspaces to be shown.
'''
if output == None:
output = output or segment_info.get('output')
return (
(
updated(
segment_info,
output=w['output'],
workspace={
'name': w['name'],
'visible': w['visible'],
'urgent': w['urgent'],
'focused': w['focused'],
},
),
{
'draw_inner_divider': None
}
)
for w in get_i3_connection().get_workspaces()
if (((not only_show or any(w[typ] for typ in only_show))
and (not output or w['output'] == output)))
)

View File

@ -1,10 +1,13 @@
# 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
conn = None
WORKSPACE_REGEX = re.compile(r'^[0-9]+: ?')
def calcgrp(w):
@ -19,6 +22,12 @@ def calcgrp(w):
return group
def format_name(name, strip=False):
if strip:
return WORKSPACE_REGEX.sub('', name, count=1)
return name
@requires_segment_info
def workspaces(pl, segment_info, only_show=None, output=None, strip=0):
'''Return list of used workspaces
@ -39,26 +48,59 @@ def workspaces(pl, segment_info, only_show=None, output=None, strip=0):
Highlight groups used: ``workspace`` or ``w_visible``, ``workspace`` or ``w_focused``, ``workspace`` or ``w_urgent``.
'''
global conn
if not conn:
try:
import i3ipc
except ImportError:
import i3 as conn
else:
conn = i3ipc.Connection()
output = output or segment_info.get('output')
return [{
'contents': w['name'][min(len(w['name']), strip):],
return [
{
'contents': w['name'][strip:],
'highlight_groups': calcgrp(w)
} for w in conn.get_workspaces()
if (not only_show or any(w[typ] for typ in only_show))
and (not output or w['output'] == output)
}
for w in get_i3_connection().get_workspaces()
if ((not only_show or any(w[typ] for typ in only_show))
and (not output or w['output'] == output))
]
@requires_segment_info
def workspace(pl, segment_info, workspace=None, strip=False):
'''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.
Highlight groups used: ``workspace`` or ``w_visible``, ``workspace`` or ``w_focused``, ``workspace`` or ``w_urgent``.
'''
if workspace:
try:
w = next((
w for w in get_i3_connection().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 get_i3_connection().get_workspaces()
if w['focused']
))
except StopIteration:
return None
return [{
'contents': format_name(w['name'], strip=strip),
'highlight_groups': calcgrp(w)
}]
@requires_segment_info
def mode(pl, segment_info, names={'default': None}):
'''Returns current i3 mode

227
tests/test_listers.py Normal file
View File

@ -0,0 +1,227 @@
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
import powerline.listers.i3wm as i3wm
from tests.lib import Args, replace_attr, Pl
from tests import TestCase
class TestI3WM(TestCase):
@staticmethod
def get_workspaces():
return iter([
{'name': '1: w1', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': False},
{'name': '2: w2', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': True},
{'name': '3: w3', 'output': 'HDMI1', 'focused': False, 'urgent': True, 'visible': True},
{'name': '4: w4', 'output': 'DVI01', 'focused': True, 'urgent': True, 'visible': True},
])
@staticmethod
def get_outputs(pl):
return iter([
{'name': 'LVDS1'},
{'name': 'HDMI1'},
{'name': 'DVI01'},
])
def test_output_lister(self):
pl = Pl()
with replace_attr(i3wm, 'get_connected_xrandr_outputs', self.get_outputs):
self.assertEqual(
list(i3wm.output_lister(pl=pl, segment_info={'a': 1})),
[
({'a': 1, 'output': 'LVDS1'}, {'draw_inner_divider': None}),
({'a': 1, 'output': 'HDMI1'}, {'draw_inner_divider': None}),
({'a': 1, 'output': 'DVI01'}, {'draw_inner_divider': None}),
]
)
def test_workspace_lister(self):
pl = Pl()
with replace_attr(i3wm, 'get_i3_connection', lambda: Args(get_workspaces=self.get_workspaces)):
self.assertEqual(
list(i3wm.workspace_lister(pl=pl, segment_info={'a': 1})),
[
({
'a': 1,
'output': 'LVDS1',
'workspace': {
'name': '1: w1',
'focused': False,
'urgent': False,
'visible': False
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'LVDS1',
'workspace': {
'name': '2: w2',
'focused': False,
'urgent': False,
'visible': True
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'HDMI1',
'workspace': {
'name': '3: w3',
'focused': False,
'urgent': True,
'visible': True
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'DVI01',
'workspace': {
'name': '4: w4',
'focused': True,
'urgent': True,
'visible': True
}
}, {'draw_inner_divider': None}),
]
)
self.assertEqual(
list(i3wm.workspace_lister(pl=pl, segment_info={'a': 1}, output='LVDS1')),
[
({
'a': 1,
'output': 'LVDS1',
'workspace': {
'name': '1: w1',
'focused': False,
'urgent': False,
'visible': False
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'LVDS1',
'workspace': {
'name': '2: w2',
'focused': False,
'urgent': False,
'visible': True
}
}, {'draw_inner_divider': None}),
]
)
self.assertEqual(
list(i3wm.workspace_lister(
pl=pl,
segment_info={'a': 1, 'output': 'LVDS1'}
)),
[
({
'a': 1,
'output': 'LVDS1',
'workspace': {
'name': '1: w1',
'focused': False,
'urgent': False,
'visible': False
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'LVDS1',
'workspace': {
'name': '2: w2',
'focused': False,
'urgent': False,
'visible': True
}
}, {'draw_inner_divider': None}),
]
)
self.assertEqual(
list(i3wm.workspace_lister(
pl=pl,
segment_info={'a': 1, 'output': 'LVDS1'},
output=False
)),
[
({
'a': 1,
'output': 'LVDS1',
'workspace': {
'name': '1: w1',
'focused': False,
'urgent': False,
'visible': False
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'LVDS1',
'workspace': {
'name': '2: w2',
'focused': False,
'urgent': False,
'visible': True
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'HDMI1',
'workspace': {
'name': '3: w3',
'focused': False,
'urgent': True,
'visible': True
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'DVI01',
'workspace': {
'name': '4: w4',
'focused': True,
'urgent': True,
'visible': True
}
}, {'draw_inner_divider': None}),
]
)
self.assertEqual(
list(i3wm.workspace_lister(
pl=pl,
segment_info={'a': 1},
only_show=['focused', 'urgent']
)),
[
({
'a': 1,
'output': 'HDMI1',
'workspace': {
'name': '3: w3',
'focused': False,
'urgent': True,
'visible': True
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'DVI01',
'workspace': {
'name': '4: w4',
'focused': True,
'urgent': True,
'visible': True
}
}, {'draw_inner_divider': None}),
]
)
if __name__ == '__main__':
from tests import main
main()

View File

@ -883,14 +883,26 @@ class TestWthr(TestCommon):
class TestI3WM(TestCase):
def test_workspaces(self):
pl = Pl()
with replace_attr(i3wm, 'conn', Args(get_workspaces=lambda: iter([
@staticmethod
def get_workspaces():
return iter([
{'name': '1: w1', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': False},
{'name': '2: w2', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': True},
{'name': '3: w3', 'output': 'HDMI1', 'focused': False, 'urgent': True, 'visible': True},
{'name': '4: w4', 'output': 'DVI01', 'focused': True, 'urgent': True, 'visible': True},
]))):
])
@staticmethod
def get_outputs(pl):
return iter([
{'name': 'LVDS1'},
{'name': 'HDMI1'},
{'name': 'DVI01'},
])
def test_workspaces(self):
pl = Pl()
with replace_attr(i3wm, 'get_i3_connection', lambda: Args(get_workspaces=self.get_workspaces)):
segment_info = {}
self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info), [
@ -936,6 +948,29 @@ class TestI3WM(TestCase):
{'contents': 'w2', 'highlight_groups': ['w_visible', 'workspace']},
])
def test_workspace(self):
pl = Pl()
with replace_attr(i3wm, 'get_i3_connection', lambda: Args(get_workspaces=self.get_workspaces)):
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']},
])
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']},
])
segment_info['workspace'] = next(self.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']},
])
self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, strip=True), [
{'contents': 'w1', 'highlight_groups': ['workspace']},
])
def test_mode(self):
pl = Pl()
self.assertEqual(i3wm.mode(pl=pl, segment_info={'mode': 'default'}), None)