Skip to content

Commit

Permalink
Use base Window class for documenting window commands
Browse files Browse the repository at this point in the history
This makes the docs point to `libqtile/backend.py::Window` to getting
information on window commands. Many of the changes synchronise the
command methods between the three `Window` classes, making sure the
wayland implementation has all of the commands and moving some methods
to the base class to be shared across them.
  • Loading branch information
m-col authored and elParaguayo committed Oct 16, 2021
1 parent c4b0ca7 commit f733714
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 80 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ Qtile x.x.x, released xxxx-xx-xx:
- For saving states of scratchpads during restart, we use wids instead of pids.
- Scratchpads can now be defined with an optional matcher to match with window properties.
- `Qtile.cmd_reload_config` is added for reloading the config without completely restarting.
- Window.cmd_togroup's argument `groupName` should be changed to
`group_name`. For the time being a log warning is in place and a
migration is added. In the future `groupName` will fail.

Qtile 0.18.1, released 2021-09-16:
* features
Expand Down
2 changes: 1 addition & 1 deletion docs/manual/ref/commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ those given here.
.. qtile_class:: libqtile.config.Screen
:noindex:

.. qtile_class:: libqtile.backend.x11.window.Window
.. qtile_class:: libqtile.backend.base.Window
96 changes: 84 additions & 12 deletions libqtile/backend/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
import cairocffi

from libqtile import drawer, pangocffi, utils
from libqtile.command.base import CommandObject
from libqtile.command.base import CommandError, CommandObject
from libqtile.log_utils import logger

if typing.TYPE_CHECKING:
from typing import Any, Dict, List, Optional, Tuple, Union
Expand Down Expand Up @@ -120,7 +121,8 @@ def keysym_from_name(self, name: str) -> int:
"""Get the keysym for a key from its name"""
raise NotImplementedError

def cmd_info(self):
def cmd_info(self) -> Dict:
"""Get basic information about the running backend."""
return {
"backend": self.name,
"display_name": self.display_name
Expand Down Expand Up @@ -215,7 +217,14 @@ def cmd_info(self) -> Dict:


class Window(_Window, metaclass=ABCMeta):
"""A regular Window belonging to a client."""
"""
A regular Window belonging to a client.
Abstract methods are required to be defined as part of a specific backend's
implementation. Non-abstract methods have default implementations here to be shared
across backends.
"""
qtile: Qtile

# If float_x or float_y are None, the window has never floated
float_x: Optional[int]
Expand Down Expand Up @@ -249,6 +258,16 @@ def wants_to_fullscreen(self) -> bool:
"""Does this window want to be fullscreen?"""
return False

@property
def opacity(self) -> float:
"""The opacity of this window from 0 (transparent) to 1 (opaque)."""
return self._opacity

@opacity.setter
def opacity(self, opacity: float) -> None:
"""Opacity setter."""
self._opacity = opacity

def match(self, match: config.Match) -> bool:
"""Compare this window against a Match instance."""
return match.compare(self)
Expand Down Expand Up @@ -289,6 +308,9 @@ def paint_borders(self, color: ColorsType, width: int) -> None:
def cmd_focus(self, warp: bool = True) -> None:
"""Focuses the window."""

def cmd_match(self, *args, **kwargs) -> bool:
return self.match(*args, **kwargs)

@abstractmethod
def cmd_get_position(self) -> Tuple[int, int]:
"""Get the (x, y) of the window"""
Expand All @@ -309,6 +331,13 @@ def cmd_resize_floating(self, dw: int, dh: int) -> None:
def cmd_set_position_floating(self, x: int, y: int) -> None:
"""Move window to x and y"""

@abstractmethod
def cmd_set_position(self, x: int, y: int) -> None:
"""
Move floating window to x and y; swap tiling window with the window under the
pointer.
"""

@abstractmethod
def cmd_set_size_floating(self, w: int, h: int) -> None:
"""Set window dimensions to w and h"""
Expand All @@ -332,7 +361,11 @@ def cmd_disable_floating(self) -> None:

@abstractmethod
def cmd_toggle_maximize(self) -> None:
"""Toggle the fullscreen state of the window."""
"""Toggle the maximize state of the window."""

@abstractmethod
def cmd_toggle_minimize(self) -> None:
"""Toggle the minimize state of the window."""

@abstractmethod
def cmd_toggle_fullscreen(self) -> None:
Expand All @@ -351,33 +384,72 @@ def cmd_bring_to_front(self) -> None:
"""Bring the window to the front"""

def cmd_togroup(
self, group_name: Optional[str] = None, *, switch_group: bool = False
self,
group_name: Optional[str] = None,
groupName: Optional[str] = None, # Deprecated
switch_group: bool = False
) -> None:
"""Move window to a specified group
Also switch to that group if switch_group is True.
Also switch to that group if `switch_group` is True.
`groupName` is deprecated and will be dropped soon. Please use `group_name`
instead.
"""
if groupName is not None:
logger.warning(
"Window.cmd_togroup's groupName is deprecated; use group_name"
)
group_name = groupName
self.togroup(group_name, switch_group=switch_group)

def cmd_opacity(self, opacity):
"""Set the window's opacity"""
def cmd_toscreen(self, index: Optional[int] = None) -> None:
"""Move window to a specified screen.
If index is not specified, we assume the current screen
Examples
========
Move window to current screen::
toscreen()
Move window to screen 0::
toscreen(0)
"""
if index is None:
screen = self.qtile.current_screen
else:
try:
screen = self.qtile.screens[index]
except IndexError:
raise CommandError('No such screen: %d' % index)
self.togroup(screen.group.name)

def cmd_opacity(self, opacity: float) -> None:
"""Set the window's opacity.
The value must be between 0 and 1 inclusive.
"""
if opacity < .1:
self.opacity = .1
elif opacity > 1:
self.opacity = 1
else:
self.opacity = opacity

def cmd_down_opacity(self):
"""Decrease the window's opacity"""
def cmd_down_opacity(self) -> None:
"""Decrease the window's opacity by 10%."""
if self.opacity > .2:
# don't go completely clear
self.opacity -= .1
else:
self.opacity = .1

def cmd_up_opacity(self):
"""Increase the window's opacity"""
def cmd_up_opacity(self) -> None:
"""Increase the window's opacity by 10%."""
if self.opacity < .9:
self.opacity += .1
else:
Expand Down
32 changes: 29 additions & 3 deletions libqtile/backend/wayland/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
XdgTopLevelSetFullscreenEvent,
)

from libqtile import hook, utils
from libqtile import config, hook, utils
from libqtile.backend import base
from libqtile.backend.base import FloatStates
from libqtile.backend.wayland.drawer import Drawer
Expand Down Expand Up @@ -85,7 +85,7 @@ def __init__(self, core: Core, qtile: Qtile, surface: SurfaceType):
self.x = 0
self.y = 0
self.bordercolor: List[ffi.CData] = [_rgb((0, 0, 0, 1))]
self.opacity: float = 1.0
self._opacity: float = 1.0
self._outputs: List[Output] = []

# These become non-zero when being mapping for the first time
Expand Down Expand Up @@ -537,6 +537,9 @@ def info(self) -> Dict:
fullscreen=self._float_state == FloatStates.FULLSCREEN
)

def match(self, match: config.Match) -> bool:
return match.compare(self)

def _items(self, name: str) -> ItemT:
if name == "group":
return True, []
Expand Down Expand Up @@ -573,6 +576,29 @@ def cmd_resize_floating(self, dw: int, dh: int) -> None:
def cmd_set_position_floating(self, x: int, y: int) -> None:
self._tweak_float(x=x, y=y)

def cmd_set_position(self, x: int, y: int) -> None:
if self.floating:
self._tweak_float(x=x, y=y)
return

if self.group:
cx = self.core.cursor.x
cy = self.core.cursor.y
for window in self.group.windows:
if (
window is not self and
not window.floating and
window.x <= cx <= (window.x + window.width) and
window.y <= cy <= (window.y + window.height)
):
clients = self.group.layout.clients
index1 = clients.index(self)
index2 = clients.index(window)
clients[index1], clients[index2] = clients[index2], clients[index1]
self.group.layout.focused = index2
self.group.layout_all()
return

def cmd_set_size_floating(self, w: int, h: int) -> None:
self._tweak_float(w=w, h=h)

Expand Down Expand Up @@ -675,7 +701,7 @@ def __init__(
self._wid: int = self.core.new_wid()
self.x: int = x
self.y: int = y
self.opacity: float = 1.0
self._opacity: float = 1.0
self._width: int = width
self._height: int = height
self._outputs: List[Output] = []
Expand Down
11 changes: 5 additions & 6 deletions libqtile/backend/x11/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -705,22 +705,21 @@ def handle_UnmapNotify(self, event) -> None: # noqa: N802
win = self.qtile.windows_map.get(event.window)

if win and getattr(win, "group", None):
assert isinstance(win, window._Window)
try:
win.hide()
win.state = window.WithdrawnState
win.state = window.WithdrawnState # type: ignore
except xcffib.xproto.WindowError:
# This means that the window has probably been destroyed,
# but we haven't yet seen the DestroyNotify (it is likely
# next in the queue). So, we just let these errors pass
# since the window is dead.
pass
# Clear these atoms as per spec
win.window.conn.conn.core.DeleteProperty(
win.wid, win.window.conn.atoms["_NET_WM_STATE"]
win.window.conn.conn.core.DeleteProperty( # type: ignore
win.wid, win.window.conn.atoms["_NET_WM_STATE"] # type: ignore
)
win.window.conn.conn.core.DeleteProperty(
win.wid, win.window.conn.atoms["_NET_WM_DESKTOP"]
win.window.conn.conn.core.DeleteProperty( # type: ignore
win.wid, win.window.conn.atoms["_NET_WM_DESKTOP"] # type: ignore
)
self.qtile.unmanage(event.window)
if self.qtile.current_window is None:
Expand Down
61 changes: 3 additions & 58 deletions libqtile/backend/x11/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,7 @@ def state(self, val):

@property
def opacity(self):
assert hasattr(self, "window")
opacity = self.window.get_property(
"_NET_WM_WINDOW_OPACITY", unpack=int
)
Expand All @@ -713,12 +714,11 @@ def opacity(self):
return as_float

@opacity.setter
def opacity(self, opacity):
def opacity(self, opacity: float) -> None:
if 0.0 <= opacity <= 1.0:
real_opacity = int(opacity * 0xffffffff)
assert hasattr(self, "window")
self.window.set_property('_NET_WM_WINDOW_OPACITY', real_opacity)
else:
return

def kill(self):
if "WM_DELETE_WINDOW" in self.window.get_wm_protocols():
Expand Down Expand Up @@ -1511,17 +1511,6 @@ def togroup(self, group_name=None, *, switch_group=False):
if switch_group:
group.cmd_toscreen(toggle=False)

def toscreen(self, index=None):
"""Move window to a specified screen, or the current screen."""
if index is None:
screen = self.qtile.current_screen
else:
try:
screen = self.qtile.screens[index]
except IndexError:
raise CommandError('No such screen: %d' % index)
self.togroup(screen.group.name)

def match(self, match):
"""Match window against given attributes.
Expand Down Expand Up @@ -1746,47 +1735,6 @@ def cmd_kill(self):
"""
self.kill()

def cmd_togroup(self, groupName=None, *, switch_group=False): # noqa: 803
"""Move window to a specified group.
If groupName is not specified, we assume the current group.
If switch_group is True, also switch to that group.
Examples
========
Move window to current group::
togroup()
Move window to group "a"::
togroup("a")
Move window to group "a", and switch to group "a"::
togroup("a", switch_group=True)
"""
self.togroup(groupName, switch_group=switch_group)

def cmd_toscreen(self, index=None):
"""Move window to a specified screen.
If index is not specified, we assume the current screen
Examples
========
Move window to current screen::
toscreen()
Move window to screen 0::
toscreen(0)
"""
self.toscreen(index)

def cmd_move_floating(self, dx, dy):
"""Move window by dx and dy"""
self.tweak_float(dx=dx, dy=dy)
Expand Down Expand Up @@ -1844,9 +1792,6 @@ def cmd_bring_to_front(self):
else:
self._reconfigure_floating() # atomatically above

def cmd_match(self, *args, **kwargs):
return self.match(*args, **kwargs)

def _is_in_window(self, x, y, window):
return (window.edges[0] <= x <= window.edges[2] and
window.edges[1] <= y <= window.edges[3])
Expand Down
Loading

0 comments on commit f733714

Please sign in to comment.