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