# Copyright (c) 2008, Aldo Cortesi. 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 . import confreader
from . import drawer
from . import configurable
from . import window
from libqtile.command_object import CommandObject
[docs]class Gap(CommandObject):
"""A gap placed along one of the edges of the screen
If a gap has been defined, Qtile will avoid covering it with windows. The
most probable reason for configuring a gap is to make space for a
third-party bar or other static window.
Parameters
==========
size :
The "thickness" of the gap, i.e. the height of a horizontal gap, or the
width of a vertical gap.
"""
def __init__(self, size):
"""
"""
# 'size' corresponds to the height of a horizontal gap, or the width
# of a vertical gap
self.size = size
self.initial_size = size
# 'length' corresponds to the width of a horizontal gap, or the height
# of a vertical gap
self.length = None
self.qtile = None
self.screen = None
self.x = None
self.y = None
self.width = None
self.height = None
self.horizontal = None
def _configure(self, qtile, screen):
self.qtile = qtile
self.screen = screen
# If both horizontal and vertical gaps are present, screen corners are
# given to the horizontal ones
if screen.top is self:
self.x = screen.x
self.y = screen.y
self.length = screen.width
self.width = self.length
self.height = self.size
self.horizontal = True
elif screen.bottom is self:
self.x = screen.x
self.y = screen.dy + screen.dheight
self.length = screen.width
self.width = self.length
self.height = self.size
self.horizontal = True
elif screen.left is self:
self.x = screen.x
self.y = screen.dy
self.length = screen.dheight
self.width = self.size
self.height = self.length
self.horizontal = False
else: # right
self.x = screen.dx + screen.dwidth
self.y = screen.dy
self.length = screen.dheight
self.width = self.size
self.height = self.length
self.horizontal = False
def draw(self):
pass
def finalize(self):
pass
def geometry(self):
return (self.x, self.y, self.width, self.height)
def _items(self, name):
if name == "screen":
return (True, None)
def _select(self, name, sel):
if name == "screen":
return self.screen
@property
def position(self):
for i in ["top", "bottom", "left", "right"]:
if getattr(self.screen, i) is self:
return i
def info(self):
return dict(position=self.position)
def cmd_info(self):
"""
Info for this object.
"""
return self.info()
class Obj:
def __init__(self, name):
self.name = name
def __str__(self):
return self.name
def __repr__(self):
return self.name
STRETCH = Obj("STRETCH")
CALCULATED = Obj("CALCULATED")
STATIC = Obj("STATIC")
[docs]class Bar(Gap, configurable.Configurable):
"""A bar, which can contain widgets
Parameters
==========
widgets :
A list of widget objects.
size :
The "thickness" of the bar, i.e. the height of a horizontal bar, or the
width of a vertical bar.
"""
defaults = [
("background", "#000000", "Background colour."),
("opacity", 1, "Bar window opacity."),
("margin", 0, "Space around bar as int or list of ints [N E S W]."),
]
def __init__(self, widgets, size, **config):
Gap.__init__(self, size)
configurable.Configurable.__init__(self, **config)
self.add_defaults(Bar.defaults)
self.widgets = widgets
self.saved_focus = None
self.queued_draws = 0
def _configure(self, qtile, screen):
Gap._configure(self, qtile, screen)
if self.margin:
if isinstance(self.margin, int):
self.margin = [self.margin] * 4
if self.horizontal:
self.x += self.margin[3]
self.width -= self.margin[1] + self.margin[3]
self.length = self.width
self.size += self.margin[0] + self.margin[2]
if self.screen.top is self:
self.y += self.margin[0]
else:
self.y -= self.margin[2]
else:
self.y += self.margin[0]
self.height -= self.margin[0] + self.margin[2]
self.length = self.height
self.size += self.margin[1] + self.margin[3]
if self.screen.left is self:
self.x += self.margin[3]
else:
self.x -= self.margin[1]
stretches = 0
for w in self.widgets:
# Executing _test_orientation_compatibility later, for example in
# the _configure() method of each widget, would still pass
# test/test_bar.py but a segfault would be raised when nosetests is
# about to exit
w._test_orientation_compatibility(self.horizontal)
if w.length_type == STRETCH:
stretches += 1
if stretches > 1:
raise confreader.ConfigError("Only one STRETCH widget allowed!")
self.window = window.Internal.create(
self.qtile,
self.x, self.y, self.width, self.height,
self.opacity
)
self.drawer = drawer.Drawer(
self.qtile,
self.window.window.wid,
self.width,
self.height
)
self.drawer.clear(self.background)
self.window.handle_Expose = self.handle_Expose
self.window.handle_ButtonPress = self.handle_ButtonPress
self.window.handle_ButtonRelease = self.handle_ButtonRelease
qtile.windows_map[self.window.window.wid] = self.window
self.window.unhide()
for i in self.widgets:
qtile.register_widget(i)
i._configure(qtile, self)
self._resize(self.length, self.widgets)
def finalize(self):
self.drawer.finalize()
def _resize(self, length, widgets):
stretches = [i for i in widgets if i.length_type == STRETCH]
if stretches:
stretchspace = length - sum(
[i.length for i in widgets if i.length_type != STRETCH]
)
stretchspace = max(stretchspace, 0)
astretch = stretchspace // len(stretches)
for i in stretches:
i.length = astretch
if astretch:
i.length += stretchspace % astretch
offset = 0
if self.horizontal:
for i in widgets:
i.offsetx = offset
i.offsety = 0
offset += i.length
else:
for i in widgets:
i.offsetx = 0
i.offsety = offset
offset += i.length
def handle_Expose(self, e): # noqa: N802
self.draw()
def get_widget_in_position(self, e):
if self.horizontal:
for i in self.widgets:
if e.event_x < i.offsetx + i.length:
return i
else:
for i in self.widgets:
if e.event_y < i.offsety + i.length:
return i
def handle_ButtonPress(self, e): # noqa: N802
widget = self.get_widget_in_position(e)
if widget:
widget.button_press(
e.event_x - widget.offsetx,
e.event_y - widget.offsety,
e.detail
)
def handle_ButtonRelease(self, e): # noqa: N802
widget = self.get_widget_in_position(e)
if widget:
widget.button_release(
e.event_x - widget.offsetx,
e.event_y - widget.offsety,
e.detail
)
def widget_grab_keyboard(self, widget):
"""
A widget can call this method to grab the keyboard focus
and receive keyboard messages. When done,
widget_ungrab_keyboard() must be called.
"""
self.window.handle_KeyPress = widget.handle_KeyPress
self.saved_focus = self.qtile.current_window
self.window.window.set_input_focus()
def widget_ungrab_keyboard(self):
"""
Removes the widget's keyboard handler.
"""
del self.window.handle_KeyPress
if self.saved_focus is not None:
self.saved_focus.window.set_input_focus()
def draw(self):
if self.queued_draws == 0:
self.qtile.call_soon(self._actual_draw)
self.queued_draws += 1
def _actual_draw(self):
self.queued_draws = 0
self._resize(self.length, self.widgets)
for i in self.widgets:
i.draw()
if self.widgets:
end = i.offset + i.length
if end < self.length:
if self.horizontal:
self.drawer.draw(offsetx=end, width=self.length - end)
else:
self.drawer.draw(offsety=end, height=self.length - end)
def info(self):
return dict(
size=self.size,
length=self.length,
width=self.width,
height=self.height,
position=self.position,
widgets=[i.info() for i in self.widgets],
window=self.window.window.wid
)
def is_show(self):
return self.size != 0
def show(self, is_show=True):
if is_show != self.is_show():
if is_show:
self.size = self.initial_size
self.window.unhide()
else:
self.size = 0
self.window.hide()