1 #include "panelmanager.h"
2 
3 #include "settings.h"
4 #include "x11-types.h"
5 #include "xconnection.h"
6 
7 using std::make_pair;
8 using std::string;
9 using std::vector;
10 
11 class Panel {
12 public:
Panel(Window winid,PanelManager & pm)13     Panel(Window winid, PanelManager& pm) : winid_(winid), pm_(pm) {}
14     Window winid_;
15     PanelManager& pm_;
16     Rectangle size_;
17     vector<long> wmStrut_ = {};
18     using WmStrut = PanelManager::WmStrut;
19 
wmStrut(WmStrut idx) const20     int wmStrut(WmStrut idx) const {
21         size_t i = static_cast<size_t>(idx);
22         if (i < wmStrut_.size()) {
23             return static_cast<int>(wmStrut_[i]);
24         } else if (idx == WmStrut::left_end_y
25                || idx == WmStrut::right_end_y)
26         {
27             return pm_.rootWindowGeometry_.height;
28         } else if (idx == WmStrut::top_end_x
29                || idx == WmStrut::bottom_end_x)
30         {
31             return pm_.rootWindowGeometry_.width;
32         } else {
33             return 0;
34         }
35     }
36 
37     /** report the panels geometry based on WM_STRUT
38      * The returned rectangle is guaranteed to touch
39      * an edge of the root window rectangle.
40      */
wmStrutGeometry() const41     Rectangle wmStrutGeometry() const {
42         if (wmStrut(WmStrut::top) > 0) {
43             // align with top edge
44             return Rectangle::fromCorners(
45                         wmStrut(WmStrut::top_start_x),
46                         0,
47                         wmStrut(WmStrut::top_end_x),
48                         wmStrut(WmStrut::top));
49         }
50         if (wmStrut(WmStrut::bottom) > 0) {
51             // align with bottom edge
52             return Rectangle::fromCorners(
53                         wmStrut(WmStrut::bottom_start_x),
54                         pm_.rootWindowGeometry_.height - wmStrut(WmStrut::bottom),
55                         wmStrut(WmStrut::bottom_end_x),
56                         pm_.rootWindowGeometry_.height);
57         }
58         if (wmStrut(WmStrut::left) > 0) {
59             // align with left edge
60             return Rectangle::fromCorners(
61                         0,
62                         wmStrut(WmStrut::left_start_y),
63                         wmStrut(WmStrut::left),
64                         wmStrut(WmStrut::left_end_y));
65         }
66         if (wmStrut(WmStrut::right) > 0) {
67             // align with right edge
68             return Rectangle::fromCorners(
69                         pm_.rootWindowGeometry_.width - wmStrut(WmStrut::right),
70                         wmStrut(WmStrut::right_start_y),
71                         pm_.rootWindowGeometry_.width,
72                         wmStrut(WmStrut::right_end_y));
73         }
74         return {0, 0, 0, 0};
75     };
76 };
77 
PanelManager(XConnection & xcon)78 PanelManager::PanelManager(XConnection& xcon)
79     : xcon_(xcon)
80 {
81     atomWmStrut_ = xcon_.atom("_NET_WM_STRUT");
82     atomWmStrutPartial_ = xcon_.atom("_NET_WM_STRUT_PARTIAL");
83     rootWindowGeometry_ = xcon_.windowSize(xcon_.root());
84 }
85 
~PanelManager()86 PanelManager::~PanelManager()
87 {
88     for (auto it : panels_) {
89         delete it.second;
90     }
91 }
92 
registerPanel(Window win)93 void PanelManager::registerPanel(Window win)
94 {
95     Panel* p = new Panel(win, *this);
96     panels_.insert(make_pair(win, p));
97     updateReservedSpace(p, xcon_.windowSize(win));
98     panels_changed_.emit();
99 }
100 
unregisterPanel(Window win)101 void PanelManager::unregisterPanel(Window win)
102 {
103     auto it = panels_.find(win);
104     if (it == panels_.end()) {
105         return;
106     }
107     Panel* p = it->second;
108     panels_.erase(win);
109     delete p;
110     panels_changed_.emit();
111 }
112 
propertyChanged(Window win,Atom property)113 void PanelManager::propertyChanged(Window win, Atom property)
114 {
115     if (property != atomWmStrut_ && property != atomWmStrutPartial_) {
116         return;
117     }
118     auto it = panels_.find(win);
119     if (it != panels_.end()) {
120         Panel* p = it->second;
121         if (updateReservedSpace(p, xcon_.windowSize(win))) {
122             panels_changed_.emit();
123         }
124     }
125 }
126 
127 /**
128  * @brief the geometry of a window was changed, where window
129  * is possibly a panel window
130  * @param the window
131  * @param its new geometry
132  */
geometryChanged(Window win,Rectangle geometry)133 void PanelManager::geometryChanged(Window win, Rectangle geometry)
134 {
135     auto it = panels_.find(win);
136     if (it != panels_.end()) {
137         Panel* p = it->second;
138         if (updateReservedSpace(p, geometry)) {
139             panels_changed_.emit();
140         }
141     }
142 }
143 
injectDependencies(Settings * settings)144 void PanelManager::injectDependencies(Settings* settings)
145 {
146     settings_ = settings;
147     settings_->auto_detect_panels.changed().connect([this]() {
148         panels_changed_.emit();
149     });
150 }
151 
152 /**
153  * read the reserved space from the panel window and return if there are changes
154  * - size is the geometry of the panel
155  */
updateReservedSpace(Panel * p,Rectangle size)156 bool PanelManager::updateReservedSpace(Panel* p, Rectangle size)
157 {
158     auto optionalWmStrut = xcon_.getWindowPropertyCardinal(p->winid_, atomWmStrutPartial_);
159     if (!optionalWmStrut) {
160         optionalWmStrut= xcon_.getWindowPropertyCardinal(p->winid_, atomWmStrut_);
161     }
162     vector<long> wmStrut = optionalWmStrut.value_or(vector<long>());
163     if (p->wmStrut_ != wmStrut || p->size_ != size) {
164         p->wmStrut_ = wmStrut;
165         p->size_ = size;
166         return true;
167     }
168     return false;
169 }
170 
171 
172 //! given the dimension of a monitor, return the space reserved for panels
computeReservedSpace(Rectangle mon)173 PanelManager::ReservedSpace PanelManager::computeReservedSpace(Rectangle mon)
174 {
175     ReservedSpace rsTotal;
176     if (!settings_->auto_detect_panels()) {
177         return rsTotal;
178     }
179     for (auto it : panels_) {
180         Panel& p = *(it.second);
181         ReservedSpace rs;
182         Rectangle panelArea = p.wmStrutGeometry();
183         if (!panelArea) {
184             // if the panel does not define WmStrut,
185             // then take it's window geometry
186             panelArea = p.size_;
187         }
188         Rectangle intersection = mon.intersectionWith(panelArea);
189         if (!intersection) {
190             // monitor does not intersect with panel at all
191             continue;
192         }
193         // we only reserve space for the panel if the panel defines
194         // wmStrut_ or if the aspect ratio clearly indicates whether the
195         // panel is horizontal or vertical
196         bool verticalPanel = p.wmStrut(WmStrut::left) > 0 || p.wmStrut(WmStrut::right) > 0;
197         bool horizontalPanel = p.wmStrut(WmStrut::top) > 0 || p.wmStrut(WmStrut::bottom) > 0;
198         if (p.wmStrut_.empty()) {
199             // only fall back to aspect ratio if wmStrut is undefined
200             verticalPanel = intersection.height > intersection.width;
201             horizontalPanel = intersection.height < intersection.width;
202         }
203         if (verticalPanel) {
204             // don't affect the monitor, if the intersection spans
205             // the entire monitor width.
206             if (intersection.x == mon.x && intersection.width < mon.width) {
207                 rs.left_ = intersection.width;
208             }
209             if (intersection.br().x == mon.br().x && intersection.width < mon.width) {
210                 rs.right_ = intersection.width;
211             }
212         }
213         if (horizontalPanel) {
214             // don't affect the monitor, if the intersection spans
215             // the entire monitor height.
216             if (intersection.y == mon.y && intersection.height < mon.height) {
217                 rs.top_ = intersection.height;
218             }
219             if (intersection.br().y == mon.br().y && intersection.height < mon.height) {
220                 rs.bottom_ = intersection.height;
221             }
222         }
223         for (size_t i = 0; i < 4; i++) {
224             rsTotal[i] = std::max(rsTotal[i], rs[i]);
225         }
226     }
227     return rsTotal;
228 }
229 
rootWindowChanged(int width,int height)230 void PanelManager::rootWindowChanged(int width, int height)
231 {
232     rootWindowGeometry_.width = width;
233     rootWindowGeometry_.height = height;
234 }
235 
operator [](size_t idx)236 int& PanelManager::ReservedSpace::operator[](size_t idx)
237 {
238     vector<int*> v = {  &left_, &right_, &top_, &bottom_ };
239     return *(v[idx]);
240 }
241