Skip to content

Commit 5b8605d

Browse files
authored
Reserve space for panels without _NET_WM_STRUT (herbstluftwm#1080)
If a panel window has the type _NET_WM_WINDOW_TYPE_DOCK but does not define in _NET_WM_STRUT from which borders of the screen it takes space, then automatically detect it based on the window's geometry. This also adds tests for the new functionality and also for the old panel detection based on _NET_WM_STRUT
1 parent c672ac9 commit 5b8605d

File tree

7 files changed

+138
-11
lines changed

7 files changed

+138
-11
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ Current git version
1515
* New command 'mirror'
1616
* New command 'apply_tmp_rule'
1717
* The 'apply_rules' command now reports parse errors
18+
* Reserve space for panels that do not set _NET_WM_STRUT e.g. conky windows
19+
of type 'dock'.
1820
* Only build json object doc if WITH_DOCUMENTATION is activated
1921
* Bug fixes:
2022
- When hiding windows, correctly set their WM_STATE to IconicState (we set

src/panelmanager.cpp

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class Panel {
1414
Window winid_;
1515
PanelManager& pm_;
1616
Rectangle size_;
17-
vector<long> wmStrut_ = {0, 0, 0, 0};
17+
vector<long> wmStrut_ = {};
1818
int operator[](PanelManager::WmStrut idx) const {
1919
size_t i = static_cast<size_t>(idx);
2020
if (i < wmStrut_.size()) {
@@ -52,7 +52,7 @@ void PanelManager::registerPanel(Window win)
5252
{
5353
Panel* p = new Panel(win, *this);
5454
panels_.insert(make_pair(win, p));
55-
updateReservedSpace(p);
55+
updateReservedSpace(p, xcon_.windowSize(win));
5656
panels_changed_.emit();
5757
}
5858

@@ -76,7 +76,24 @@ void PanelManager::propertyChanged(Window win, Atom property)
7676
auto it = panels_.find(win);
7777
if (it != panels_.end()) {
7878
Panel* p = it->second;
79-
if (updateReservedSpace(p)) {
79+
if (updateReservedSpace(p, xcon_.windowSize(win))) {
80+
panels_changed_.emit();
81+
}
82+
}
83+
}
84+
85+
/**
86+
* @brief the geometry of a window was changed, where window
87+
* is possibly a panel window
88+
* @param the window
89+
* @param its new geometry
90+
*/
91+
void PanelManager::geometryChanged(Window win, Rectangle geometry)
92+
{
93+
auto it = panels_.find(win);
94+
if (it != panels_.end()) {
95+
Panel* p = it->second;
96+
if (updateReservedSpace(p, geometry)) {
8097
panels_changed_.emit();
8198
}
8299
}
@@ -87,10 +104,12 @@ void PanelManager::injectDependencies(Settings* settings)
87104
settings_ = settings;
88105
}
89106

90-
//! read the reserved space from the panel window and return if there are changes
91-
bool PanelManager::updateReservedSpace(Panel* p)
107+
/**
108+
* read the reserved space from the panel window and return if there are changes
109+
* - size is the geometry of the panel
110+
*/
111+
bool PanelManager::updateReservedSpace(Panel* p, Rectangle size)
92112
{
93-
Rectangle size = xcon_.windowSize(p->winid_);
94113
auto optionalWmStrut = xcon_.getWindowPropertyCardinal(p->winid_, atomWmStrut_);
95114
if (!optionalWmStrut) {
96115
optionalWmStrut= xcon_.getWindowPropertyCardinal(p->winid_, atomWmStrutPartial_);
@@ -120,10 +139,44 @@ PanelManager::ReservedSpace PanelManager::computeReservedSpace(Rectangle mon)
120139
// monitor does not intersect with panel at all
121140
continue;
122141
}
123-
rs.left_ = (p[WmStrut::left] > 0) ? intersection.width : 0;
124-
rs.right_ = (p[WmStrut::right] > 0) ? intersection.width : 0;
125-
rs.top_ = (p[WmStrut::top] > 0) ? intersection.height : 0;
126-
rs.bottom_ = (p[WmStrut::bottom] > 0) ? intersection.height : 0;
142+
// we only reserve space for the panel if the panel defines
143+
// wmStrut_ or if the aspect ratio clearly indicates whether the
144+
// panel is horizontal or vertical
145+
if (!p.wmStrut_.empty() || intersection.height > intersection.width) {
146+
// vertical panels
147+
if (intersection.x == mon.x) {
148+
rs.left_ = intersection.width;
149+
}
150+
if (intersection.br().x == mon.br().x) {
151+
rs.right_ = intersection.width;
152+
}
153+
}
154+
if (!p.wmStrut_.empty() || intersection.height < intersection.width) {
155+
// horizontal panels
156+
if (intersection.y == mon.y) {
157+
rs.top_ = intersection.height;
158+
}
159+
if (intersection.br().y == mon.br().y) {
160+
rs.bottom_ = intersection.height;
161+
}
162+
}
163+
if (!p.wmStrut_.empty()) {
164+
// if the panel explicitly defines wmStrut, then
165+
// we only consider the sides for the reserved space
166+
// that are explicitly mentioned in wmStrut
167+
if (p[WmStrut::left] == 0) {
168+
rs.left_ = 0;
169+
}
170+
if (p[WmStrut::right] == 0) {
171+
rs.right_ = 0;
172+
}
173+
if (p[WmStrut::top] == 0) {
174+
rs.top_ = 0;
175+
}
176+
if (p[WmStrut::bottom] == 0) {
177+
rs.bottom_ = 0;
178+
}
179+
}
127180
for (size_t i = 0; i < 4; i++) {
128181
rsTotal[i] = std::max(rsTotal[i], rs[i]);
129182
}

src/panelmanager.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,14 @@ class PanelManager {
3737
void registerPanel(Window win);
3838
void unregisterPanel(Window win);
3939
void propertyChanged(Window win, Atom property);
40+
void geometryChanged(Window win, Rectangle size);
4041
void injectDependencies(Settings* settings);
4142
ReservedSpace computeReservedSpace(Rectangle monitorDimension);
4243
Signal panels_changed_;
4344
void rootWindowChanged(int width, int height);
4445
private:
4546
friend Panel;
46-
bool updateReservedSpace(Panel* p);
47+
bool updateReservedSpace(Panel* p, Rectangle geometry);
4748

4849
std::unordered_map<Window, Panel*> panels_;
4950
Atom atomWmStrut_;

src/x11-types.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ struct Rectangle {
9898

9999
bool operator<(const Rectangle& other) const;
100100
bool operator==(const Rectangle& other) const;
101+
bool operator!=(const Rectangle& other) const { return !(this->operator==(other));}
101102

102103
operator bool() const;
103104

src/xmainloop.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,9 @@ void XMainLoop::configurenotify(XConfigureEvent* event) {
338338
std::ostringstream void_output;
339339
root_->monitors->detectMonitorsCommand(input, void_output);
340340
}
341+
} else {
342+
Rectangle geometry = { event->x, event->y, event->width, event->height };
343+
root_->panels->geometryChanged(event->window, geometry);
341344
}
342345
// HSDebug("name is: ConfigureNotify\n");
343346
}

tests/conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,7 @@ def create_client(self, urgent=False, pid=None,
641641
wm_class=None,
642642
window_type=None,
643643
transient_for=None,
644+
pre_map=lambda wh: None, # called before the window is mapped
644645
):
645646
w = self.root.create_window(
646647
geometry[0],
@@ -679,6 +680,7 @@ def create_client(self, urgent=False, pid=None,
679680
32,
680681
[pid])
681682

683+
pre_map(w)
682684
w.map()
683685
self.display.sync()
684686
if sync_hlwm:

tests/test_panels.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import pytest
2+
from Xlib import Xatom
3+
4+
5+
@pytest.mark.parametrize("which_pad, pad_size, geometry", [
6+
("pad_left", 10, (-1, 0, 11, 400)),
7+
("pad_right", 20, (800 - 20, 23, 20, 400)),
8+
("pad_up", 40, (-5, 0, 805, 40)),
9+
("pad_down", 15, (4, 600 - 15, 200, 40)),
10+
# cases where no pad is applied because the width/height ratio is ambiguous:
11+
("pad_left", 0, (0, 0, 40, 40)),
12+
("pad_down", 0, (800 - 40, 600 - 40, 40, 40)),
13+
])
14+
def test_panel_based_on_intersection(hlwm, x11, which_pad, pad_size, geometry):
15+
# monitor 0 is affected by the panel, but the other monitor is not
16+
hlwm.call('add othertag')
17+
hlwm.call('set_monitors 800x600+0+0 800x600+800+0')
18+
winhandle, _ = x11.create_client(geometry=geometry,
19+
window_type='_NET_WM_WINDOW_TYPE_DOCK')
20+
21+
assert int(hlwm.attr.monitors[0][which_pad]()) == pad_size
22+
for p in ["pad_left", "pad_right", "pad_up", "pad_down"]:
23+
assert hlwm.attr.monitors[1][p]() == '0', \
24+
"monitor 1 must never be affected"
25+
if p == which_pad:
26+
continue
27+
assert hlwm.attr.monitors[0][p]() == '0'
28+
29+
30+
@pytest.mark.parametrize("which_pad, pad_size, geometry, wm_strut", [
31+
("pad_left", 20, (-1, 0, 21, 4), [20, 0, 0, 0]),
32+
("pad_right", 30, (800 - 30, -2, 40, 11), [0, 20, 0, 0]),
33+
("pad_up", 40, (0, 0, 30, 40), [0, 0, 40, 0]),
34+
("pad_down", 15, (80, 600 - 15, 2, 15), [0, 0, 0, 15]),
35+
])
36+
@pytest.mark.parametrize("strut_before_map", [True, False])
37+
def test_panel_based_on_wmstrut(hlwm, x11, which_pad, pad_size, geometry, wm_strut, strut_before_map):
38+
# monitor 0 is affected by the panel, but the other monitor is not
39+
hlwm.call('add othertag')
40+
hlwm.call('set_monitors 800x600+0+0 800x600+800+0')
41+
42+
def set_wm_strut(winhandle):
43+
xproperty = x11.display.intern_atom('_NET_WM_STRUT')
44+
winhandle.change_property(xproperty, Xatom.CARDINAL, 32, wm_strut)
45+
46+
def noop(winhandle):
47+
pass
48+
49+
winhandle, _ = x11.create_client(geometry=geometry,
50+
window_type='_NET_WM_WINDOW_TYPE_DOCK',
51+
pre_map=set_wm_strut if strut_before_map else noop,
52+
)
53+
if not strut_before_map:
54+
set_wm_strut(winhandle)
55+
# sync hlwm and x11
56+
x11.display.sync()
57+
hlwm.call('true')
58+
59+
assert int(hlwm.attr.monitors[0][which_pad]()) == pad_size
60+
for p in ["pad_left", "pad_right", "pad_up", "pad_down"]:
61+
assert hlwm.attr.monitors[1][p]() == '0', \
62+
"monitor 1 must never be affected"
63+
if p == which_pad:
64+
continue
65+
assert hlwm.attr.monitors[0][p]() == '0'

0 commit comments

Comments
 (0)