1 //===========================================
2 // Lumina-DE source code
3 // Copyright (c) 2017-2018, Ken Moore
4 // Available under the 3-clause BSD license
5 // See the LICENSE file for full details
6 //===========================================
7 #include "NativeWindowObject.h"
8 #include <QQmlEngine>
9 #include <QDebug>
10 #include <QBuffer>
11
12 // == QML Type Registration ==
RegisterType()13 void NativeWindowObject::RegisterType(){
14 static bool done = false;
15 if(done){ return; }
16 done=true;
17 qmlRegisterType<NativeWindowObject>("Lumina.Backend.NativeWindowObject",2,0, "NativeWindowObject");
18 }
19
20 // === PUBLIC ===
NativeWindowObject(WId id)21 NativeWindowObject::NativeWindowObject(WId id) : QObject(){
22 winid = id;
23 frameid = 0;
24 dmgID = dmg = 0;
25 geomTimer = new QTimer(this);
26 geomTimer->setSingleShot(true);
27 geomTimer->setInterval(50); //1/20 second
28 connect(geomTimer, SIGNAL(timeout()), this, SLOT(sendNewGeom()) );
29 qRegisterMetaType<WId>("WId");
30 }
31
~NativeWindowObject()32 NativeWindowObject::~NativeWindowObject(){
33 hash.clear();
34 }
35
addFrameWinID(WId fid)36 void NativeWindowObject::addFrameWinID(WId fid){
37 frameid = fid;
38 }
39
addDamageID(unsigned int dmg)40 void NativeWindowObject::addDamageID(unsigned int dmg){
41 dmgID = dmg;
42 }
43
isRelatedTo(WId tmp)44 bool NativeWindowObject::isRelatedTo(WId tmp){
45 return (relatedTo.contains(tmp) || winid == tmp || frameid == tmp);
46 }
47
id()48 WId NativeWindowObject::id(){
49 return winid;
50 }
51
frameId()52 WId NativeWindowObject::frameId(){
53 return frameid;
54 }
55
damageId()56 unsigned int NativeWindowObject::damageId(){
57 return dmgID;
58 }
59
property(NativeWindowObject::Property prop)60 QVariant NativeWindowObject::property(NativeWindowObject::Property prop){
61 if(hash.contains(prop)){ return hash.value(prop); }
62 else if(prop == NativeWindowObject::RelatedWindows){ return QVariant::fromValue(relatedTo); }
63 return QVariant(); //null variant
64 }
65
setProperty(NativeWindowObject::Property prop,QVariant val,bool force)66 void NativeWindowObject::setProperty(NativeWindowObject::Property prop, QVariant val, bool force){
67 if(prop == NativeWindowObject::RelatedWindows){ relatedTo = val.value< QList<WId> >(); }
68 else if(prop == NativeWindowObject::None || (!force && hash.value(prop)==val)){ return; }
69 else if(prop == NativeWindowObject::WinImage){
70 //special case - This should never be actually set in the property hash
71 // it is loaded dynamically by the QMLImageProvider instead (prevent flickering/caching image)
72 } else{ hash.insert(prop, val); }
73 emitSinglePropChanged(prop);
74 emit PropertiesChanged(QList<NativeWindowObject::Property>() << prop, QList<QVariant>() << val);
75 }
76
setProperties(QList<NativeWindowObject::Property> props,QList<QVariant> vals,bool force)77 void NativeWindowObject::setProperties(QList<NativeWindowObject::Property> props, QList<QVariant> vals, bool force){
78 for(int i=0; i<props.length(); i++){
79 if(i>=vals.length()){ props.removeAt(i); i--; continue; } //no corresponding value for this property
80 if(props[i] == NativeWindowObject::None || (!force && (hash.value(props[i]) == vals[i])) ){
81 props.removeAt(i); vals.removeAt(i); i--; continue; //Invalid property or identical value
82 }else if(props[i] == NativeWindowObject::WinImage){
83 //special case - This should never be actually set in the property hash
84 // it is loaded dynamically by the QMLImageProvider instead (prevent flickering/caching image)
85 }else{
86 hash.insert(props[i], vals[i]);
87 }
88 emitSinglePropChanged(props[i]);
89 }
90 emit PropertiesChanged(props, vals);
91 }
92
requestProperty(NativeWindowObject::Property prop,QVariant val,bool force)93 void NativeWindowObject::requestProperty(NativeWindowObject::Property prop, QVariant val, bool force){
94 if(prop == NativeWindowObject::None || prop == NativeWindowObject::RelatedWindows || (!force && hash.value(prop)==val) ){ return; }
95 emit RequestPropertiesChange(winid, QList<NativeWindowObject::Property>() << prop, QList<QVariant>() << val);
96 }
97
requestProperties(QList<NativeWindowObject::Property> props,QList<QVariant> vals,bool force)98 void NativeWindowObject::requestProperties(QList<NativeWindowObject::Property> props, QList<QVariant> vals, bool force){
99 //Verify/adjust inputs as needed
100 for(int i=0; i<props.length(); i++){
101 if(i>=vals.length()){ props.removeAt(i); i--; continue; } //no corresponding value for this property
102 if(props[i] == NativeWindowObject::None || props[i] == NativeWindowObject::RelatedWindows || (!force && hash.value(props[i])==vals[i]) ){ props.removeAt(i); vals.removeAt(i); i--; continue; } //Invalid property or identical value
103 /*if( (props[i] == NativeWindowObject::Visible || props[i] == NativeWindowObject::Active) && frameid !=0){
104 //These particular properties needs to change the frame - not the window itself
105 emit RequestPropertiesChange(frameid, QList<NativeWindowObject::Property>() << props[i], QList<QVariant>() << vals[i]);
106 props.removeAt(i); vals.removeAt(i); i--;
107 }*/
108 }
109 emit RequestPropertiesChange(winid, props, vals);
110 }
111
geometry()112 QRect NativeWindowObject::geometry(){
113 //Calculate the "full" geometry of the window + frame (if any)
114 //Check that the size is between the min/max limitations
115 QSize size = hash.value(NativeWindowObject::Size).toSize();
116 QSize min = hash.value(NativeWindowObject::MinSize).toSize();
117 QSize max = hash.value(NativeWindowObject::MaxSize).toSize();
118 if(min.isValid() && min.width() > size.width() ){ size.setWidth(min.width()); }
119 if(min.isValid() && min.height() > size.height()){ size.setHeight(min.height()); }
120 if(max.isValid() && max.width() < size.width() && max.width()>min.width()){ size.setWidth(max.width()); }
121 if(max.isValid() && max.height() < size.height() && max.height()>min.height()){ size.setHeight(max.height()); }
122 //Assemble the full geometry
123 QRect geom( hash.value(NativeWindowObject::GlobalPos).toPoint(), size );
124 //Now adjust the window geom by the frame margins
125 QList<int> frame = hash.value(NativeWindowObject::FrameExtents).value< QList<int> >(); //Left,Right,Top,Bottom
126 //qDebug() << "Calculate Geometry:" << geom << frame;
127 if(frame.length()==4){
128 geom = geom.adjusted( -frame[0], -frame[2], frame[1], frame[3] );
129 }
130 //qDebug() << " - Total:" << geom;
131 return geom;
132 }
133
setGeometryNow(QRect geom)134 void NativeWindowObject::setGeometryNow(QRect geom){
135 updateGeometry(geom.x(), geom.y(), geom.width(), geom.height(), true);
136 }
137
138 // QML ACCESS FUNCTIONS (shortcuts for particular properties in a format QML can use)
winImage()139 QString NativeWindowObject::winImage(){
140 //Need to alternate something on the end to ensure that QML knows to fetch the new image (non-cached only)
141 if(dmg==0){ dmg = 1; }
142 else{ dmg = 0; }
143 return "image://native_window/image:"+QString::number(winid)+":"+QString::number(dmg);
144 }
145
name()146 QString NativeWindowObject::name(){
147 return this->property(NativeWindowObject::Name).toString();
148 }
149
title()150 QString NativeWindowObject::title(){
151 return this->property(NativeWindowObject::Title).toString();
152 }
153
shortTitle()154 QString NativeWindowObject::shortTitle(){
155 QString tmp = this->property(NativeWindowObject::ShortTitle).toString();
156 if(tmp.isEmpty()){ tmp = title(); }
157 if(tmp.isEmpty()){ tmp = name(); }
158 return tmp;
159 }
160
icon()161 QString NativeWindowObject::icon(){
162 if(icodmg==0){ icodmg=1; }
163 else{ icodmg = 0; }
164 qDebug() << "Window Icon:" << icodmg << this->property(NativeWindowObject::Icon).value<QIcon>().availableSizes();
165 return "image://native_window/icon:"+QString::number(winid)+":"+QString::number(icodmg);
166 }
167
168 //QML Button states
showCloseButton()169 bool NativeWindowObject::showCloseButton(){
170 QList<NativeWindowObject::Type> types = this->property(NativeWindowObject::WinTypes).value<QList < NativeWindowObject::Type> >();
171 QList<NativeWindowObject::Type> badtypes;
172 badtypes << NativeWindowObject::T_DESKTOP << NativeWindowObject::T_TOOLBAR << NativeWindowObject::T_MENU \
173 << NativeWindowObject::T_SPLASH << NativeWindowObject::T_DROPDOWN_MENU << NativeWindowObject::T_POPUP_MENU \
174 << NativeWindowObject::T_NOTIFICATION << NativeWindowObject::T_COMBO << NativeWindowObject::T_DND;
175 for(int i=0; i<types.length(); i++){
176 if(badtypes.contains(types[i])){ return false; }
177 }
178 return true;
179 }
180
showMaxButton()181 bool NativeWindowObject::showMaxButton(){
182 QList<NativeWindowObject::Type> types = this->property(NativeWindowObject::WinTypes).value<QList < NativeWindowObject::Type> >();
183 QList<NativeWindowObject::Type> badtypes;
184 badtypes << NativeWindowObject::T_DESKTOP << NativeWindowObject::T_TOOLBAR << NativeWindowObject::T_MENU \
185 << NativeWindowObject::T_SPLASH << NativeWindowObject::T_DROPDOWN_MENU << NativeWindowObject::T_POPUP_MENU \
186 << NativeWindowObject::T_NOTIFICATION << NativeWindowObject::T_COMBO << NativeWindowObject::T_DND;
187 for(int i=0; i<types.length(); i++){
188 if(badtypes.contains(types[i])){ return false; }
189 }
190 return true;
191 }
192
showMinButton()193 bool NativeWindowObject::showMinButton(){
194 QList<NativeWindowObject::Type> types = this->property(NativeWindowObject::WinTypes).value<QList < NativeWindowObject::Type> >();
195 QList<NativeWindowObject::Type> badtypes;
196 badtypes << NativeWindowObject::T_DESKTOP << NativeWindowObject::T_TOOLBAR << NativeWindowObject::T_MENU \
197 << NativeWindowObject::T_SPLASH << NativeWindowObject::T_DROPDOWN_MENU << NativeWindowObject::T_POPUP_MENU \
198 << NativeWindowObject::T_NOTIFICATION << NativeWindowObject::T_COMBO << NativeWindowObject::T_DND << NativeWindowObject::T_DIALOG;
199 for(int i=0; i<types.length(); i++){
200 if(badtypes.contains(types[i])){ return false; }
201 }
202 return true;
203 }
204
showTitlebar()205 bool NativeWindowObject::showTitlebar(){
206 QList<NativeWindowObject::Type> types = this->property(NativeWindowObject::WinTypes).value<QList < NativeWindowObject::Type> >();
207 QList<NativeWindowObject::Type> badtypes;
208 badtypes << NativeWindowObject::T_DESKTOP << NativeWindowObject::T_TOOLBAR << NativeWindowObject::T_MENU \
209 << NativeWindowObject::T_SPLASH << NativeWindowObject::T_DROPDOWN_MENU << NativeWindowObject::T_POPUP_MENU \
210 << NativeWindowObject::T_NOTIFICATION << NativeWindowObject::T_COMBO << NativeWindowObject::T_DND;
211 for(int i=0; i<types.length(); i++){
212 if(badtypes.contains(types[i])){ return false; }
213 }
214 return true;
215 }
216
showGenericButton()217 bool NativeWindowObject::showGenericButton(){
218 QList<NativeWindowObject::Type> types = this->property(NativeWindowObject::WinTypes).value<QList < NativeWindowObject::Type> >();
219 QList<NativeWindowObject::Type> badtypes;
220 badtypes << NativeWindowObject::T_DESKTOP << NativeWindowObject::T_TOOLBAR << NativeWindowObject::T_MENU \
221 << NativeWindowObject::T_SPLASH << NativeWindowObject::T_DROPDOWN_MENU << NativeWindowObject::T_POPUP_MENU \
222 << NativeWindowObject::T_NOTIFICATION << NativeWindowObject::T_COMBO << NativeWindowObject::T_DND;
223 for(int i=0; i<types.length(); i++){
224 if(badtypes.contains(types[i])){ return false; }
225 }
226 return true;
227 }
228
showWindowFrame()229 bool NativeWindowObject::showWindowFrame(){
230 QList<NativeWindowObject::Type> types = this->property(NativeWindowObject::WinTypes).value<QList < NativeWindowObject::Type> >();
231 QList<NativeWindowObject::Type> badtypes;
232 badtypes << NativeWindowObject::T_DESKTOP << NativeWindowObject::T_TOOLBAR << NativeWindowObject::T_MENU \
233 << NativeWindowObject::T_SPLASH << NativeWindowObject::T_DROPDOWN_MENU << NativeWindowObject::T_POPUP_MENU \
234 << NativeWindowObject::T_NOTIFICATION << NativeWindowObject::T_COMBO << NativeWindowObject::T_DND;
235 for(int i=0; i<types.length(); i++){
236 if(badtypes.contains(types[i])){ return false; }
237 }
238 return true;
239 }
240
241 //QML Window States
isSticky()242 bool NativeWindowObject::isSticky(){
243 return (this->property(NativeWindowObject::Workspace).toInt()<0 || this->property(NativeWindowObject::States).value<QList<NativeWindowObject::State> >().contains(NativeWindowObject::S_STICKY) );
244 }
245
isVisible()246 bool NativeWindowObject::isVisible(){
247 return (this->property(NativeWindowObject::Visible).toBool() );
248 }
249
workspace()250 int NativeWindowObject::workspace(){
251 return this->property(NativeWindowObject::Workspace).toInt();
252 }
253
254 //QML Geometry reporting
frameGeometry()255 QRect NativeWindowObject::frameGeometry(){
256 return geometry();
257 }
258
imageGeometry()259 QRect NativeWindowObject::imageGeometry(){
260 QRect geom( this->property(NativeWindowObject::GlobalPos).toPoint(), this->property(NativeWindowObject::Size).toSize() );
261 return geom;
262 }
263
updateGeometry(int x,int y,int width,int height,bool now)264 void NativeWindowObject::updateGeometry(int x, int y, int width, int height, bool now){
265 // Full frame+window geometry - go ahead and pull it apart and only update the interior window geom
266 QList<int> fgeom = this->property(NativeWindowObject::FrameExtents).value<QList<int> >();
267 if(fgeom.isEmpty()){ fgeom << 0<<0<<0<<0; } //just in case (left/right/top/bottom)
268 QPoint pos(x+fgeom[0], y+fgeom[2]);
269 QSize sz(width-fgeom[0]-fgeom[1], height-fgeom[2]-fgeom[3]);
270 newgeom = QRect(pos, sz);
271 lastgeom = QRect(x,y,width,height); //save this for later
272 if(!now){
273 //qDebug() << "Update Geometry:" << fgeom << QRect(x,y,width,height) << pos << sz;
274 //requestProperties(QList<NativeWindowObject::Property>() << NativeWindowObject::GlobalPos << NativeWindowObject::Size, QList<QVariant>() << pos << sz);
275 if(!geomTimer->isActive()){ QTimer::singleShot(0,geomTimer, SLOT(start())); }
276 }else{
277 sendNewGeom();
278 }
279 }
280
281 // ==== PUBLIC SLOTS ===
toggleVisibility()282 void NativeWindowObject::toggleVisibility(){
283 setProperty(NativeWindowObject::Visible, !property(NativeWindowObject::Visible).toBool() );
284 }
285
toggleMaximize()286 void NativeWindowObject::toggleMaximize(){
287 //Find the screen containing this window (center of window)
288 QRect curgeom = frameGeometry();
289 QPoint ctr = curgeom.center();
290 QList<QScreen*> scrns = QApplication::screens();
291 QRect max;
292 for(int i=0; i<scrns.length(); i++){
293 if(scrns[i]->geometry().contains(ctr)){
294 max = scrns[i]->availableGeometry();
295 break;
296 }
297 }
298 //Now compare the current geometry to the screen geometry
299 qDebug() << "Maximize Toggle:" << curgeom << max;
300 if(curgeom!=max){
301 qDebug() << " - maximize";
302 setGeometryNow(max); //will set newgeom to max
303 lastgeom = curgeom; //save this for later
304 }else{
305 qDebug() << " - restore" << lastgeom;
306 //Already maximized, look at the old geometry and figure out how to restore it
307 if(lastgeom.isNull() || lastgeom == max){
308 qDebug() << " -- Reset lastgeom to half-screen size";
309 //no old info available - center the window at half maximum size
310 lastgeom = QRect(max.x()-max.width()/2, max.y()-max.height()/2, max.width()/2, max.height()/2);
311 }
312 setGeometryNow(lastgeom);
313 }
314 qDebug() << "After toggle:" << lastgeom;
315 //emit geomChanged();
316 }
317
requestClose()318 void NativeWindowObject::requestClose(){
319 emit RequestClose(winid);
320 }
321
requestKill()322 void NativeWindowObject::requestKill(){
323 emit RequestKill(winid);
324 }
325
requestPing()326 void NativeWindowObject::requestPing(){
327 emit RequestPing(winid);
328 }
329
requestActivate()330 void NativeWindowObject::requestActivate(){
331 requestProperty(NativeWindowObject::Active, true);
332 }
333
announceClosed()334 void NativeWindowObject::announceClosed(){
335 this->emit WindowClosed(winid);
336 }
337 // ==== PRIVATE ====
emitSinglePropChanged(NativeWindowObject::Property prop)338 void NativeWindowObject::emitSinglePropChanged(NativeWindowObject::Property prop){
339 //Simple switch to emit the QML-usable signals as properties are changed
340 switch(prop){
341 case NativeWindowObject::Name:
342 emit nameChanged(); break;
343 case NativeWindowObject::Title:
344 emit titleChanged();
345 if(this->property(NativeWindowObject::ShortTitle).toString().isEmpty()){ emit shortTitleChanged(); }
346 break;
347 case NativeWindowObject::ShortTitle:
348 emit shortTitleChanged(); break;
349 case NativeWindowObject::Icon:
350 emit iconChanged(); break;
351 case NativeWindowObject::Workspace:
352 case NativeWindowObject::States:
353 emit stickyChanged(); break;
354 case NativeWindowObject::WinImage:
355 emit winImageChanged(); break;
356 case NativeWindowObject::WinTypes:
357 emit winTypeChanged(); break;
358 case NativeWindowObject::Visible:
359 emit visibilityChanged(); break;
360 default:
361 break; //do nothing otherwise
362 }
363 }
364
sendNewGeom()365 void NativeWindowObject::sendNewGeom(){
366 QList<NativeWindowObject::Property> props; props << NativeWindowObject::GlobalPos << NativeWindowObject::Size;
367 QList<QVariant> vals; vals << newgeom.topLeft() << newgeom.size();
368 requestProperties(props, vals);
369 setProperties(props,vals);
370 emit VerifyNewGeometry(winid);
371 }
372