1 /**************************************************************************
2 *   Copyright (C) 2005-2020 by Oleksandr Shneyder                         *
3 *                              <o.shneyder@phoca-gmbh.de>                 *
4 *                                                                         *
5 *   This program is free software; you can redistribute it and/or modify  *
6 *   it under the terms of the GNU General Public License as published by  *
7 *   the Free Software Foundation; either version 2 of the License, or     *
8 *   (at your option) any later version.                                   *
9 *   This program is distributed in the hope that it will be useful,       *
10 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
11 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
12 *   GNU General Public License for more details.                          *
13 *                                                                         *
14 *   You should have received a copy of the GNU General Public License     *
15 *   along with this program.  If not, see <https://www.gnu.org/licenses/>. *
16 ***************************************************************************/
17 
18 #include "sessionexplorer.h"
19 #include "sessionbutton.h"
20 #include "folderbutton.h"
21 #include "editconnectiondialog.h"
22 #include "onmainwindow.h"
23 #include <QMessageBox>
24 #include <QCheckBox>
25 #include "x2gosettings.h"
26 #include <QGridLayout>
27 #include <QScrollArea>
28 #include <QFile>
29 #include <QDesktopServices>
30 #include "x2goutils.h"
31 #include "imgframe.h"
32 #include <QToolButton>
33 #include "x2gologdebug.h"
34 #include <QBuffer>
35 #include <QDir>
36 
SessionExplorer(ONMainWindow * p)37 SessionExplorer::SessionExplorer(ONMainWindow* p):QObject(p)
38 {
39     parent=p;
40     lastSession=0;
41     backButton=new QToolButton(parent->getCentralFrame());
42     backButton->setIcon(QIcon( parent->iconsPath ( "/32x32/tbhide.png" )));
43     backButton->setAutoRaise(true);
44     pathLabel=new QLabel(" ",parent->getCentralFrame());
45     backButton->setFixedWidth(36);
46     navigationLayout=new QHBoxLayout();
47     navigationLayout->addWidget(backButton);
48     navigationLayout->addWidget(pathLabel);
49     backButton->setToolTip(tr("Back"));
50     QPalette pal=backButton->palette();
51     pal.setBrush ( QPalette::Window, QColor ( 110,112,127,255 ) );
52     pal.setBrush ( QPalette::Base, QColor ( 110,112,127,255 ) );
53     pal.setBrush ( QPalette::Button, QColor ( 110,112,127,255 ) );
54 
55     backButton->setPalette(pal);
56     backButton->setAutoFillBackground(true);
57     pal=pathLabel->palette();
58     pal.setBrush ( QPalette::Window, QColor ( 110,112,127,255 ) );
59     pal.setBrush ( QPalette::WindowText, QColor ( 200,200,200,255 ) );
60     pathLabel->setPalette(pal);
61     pathLabel->setAutoFillBackground(true);
62     setNavigationVisible(false);
63     connect(backButton,SIGNAL(clicked(bool)), this, SLOT(slotLevelUp()));
64 }
65 
~SessionExplorer()66 SessionExplorer::~SessionExplorer()
67 {
68 }
69 
resize()70 void SessionExplorer::resize()
71 {
72     pathLabel->setMaximumWidth(parent->getUsersArea()->width()-backButton->width());
73     QFontMetrics metrics(pathLabel->font());
74     QString displayText = metrics.elidedText(" "+currentPath, Qt::ElideLeft, pathLabel->width()-6);
75     pathLabel->setText(displayText);
76 }
77 
cleanSessions()78 void SessionExplorer::cleanSessions()
79 {
80     for ( int i=0; i<sessions.size(); ++i )
81         sessions[i]->close();
82     sessions.clear();
83     for ( int i=0; i<folders.size(); ++i )
84         folders[i]->close();
85     folders.clear();
86 }
87 
exportsEdit(SessionButton * bt)88 void SessionExplorer::exportsEdit ( SessionButton* bt )
89 {
90     EditConnectionDialog dlg (false, bt->id(),parent,4 );
91     if ( dlg.exec() ==QDialog::Accepted )
92     {
93         bt->redraw();
94         bool vis=bt->isVisible();
95         placeButtons();
96         parent->getUsersArea()->ensureVisible ( bt->x(),bt->y(),50,220 );
97         bt->setVisible ( vis );
98     }
99 }
100 
getFoldersFromConfig()101 void SessionExplorer::getFoldersFromConfig()
102 {
103     X2goSettings *st;
104 
105     if (parent->getBrokerMode())
106         st=new X2goSettings(parent->getConfig()->iniFile,QSettings::IniFormat);
107     else
108         st= new X2goSettings( "sessions" );
109 
110     QStringList folders=st->setting()->childKeys();
111     QString folder;
112     foreach(folder,folders)
113     {
114         if(folder.indexOf("icon_")==0)
115         {
116             folder=folder.mid(strlen("icon_"));
117             folder.replace("::","/");
118             if(findFolder(folder)==-1)
119                 createFolder(folder);
120         }
121     }
122 }
123 
124 
slotEdit(SessionButton * bt)125 void SessionExplorer::slotEdit ( SessionButton* bt )
126 {
127     EditConnectionDialog dlg (false, bt->id(),parent );
128     if ( dlg.exec() ==QDialog::Accepted )
129     {
130         bt->redraw();
131         placeButtons();
132         parent->getUsersArea()->ensureVisible ( bt->x(),bt->y(),50,220 );
133     }
134 }
135 
slotCreateDesktopIcon(SessionButton * bt)136 void SessionExplorer::slotCreateDesktopIcon ( SessionButton* bt )
137 {
138     QMessageBox messageBox(QMessageBox::Question,
139                            tr ( "Create session icon on desktop." ),
140                            tr ( "Desktop icons can be configured "
141                                 "not to show X2Go Client (hidden mode.) "
142                                 "If you like to use this feature you'll "
143                                 "need to configure login via a GPG key "
144                                 "or GPG Smart Card.\n\n"
145                                 "Use X2Go Client's hidden mode?" ),
146                            QMessageBox::Yes|QMessageBox::No,
147                            parent);
148 
149     //adding a chekbox to know if user want to enable trayicon in hide sessions
150     QCheckBox cbShowTrayIcon(tr("Show session tray icon when running"));
151     messageBox.layout()->addWidget(&cbShowTrayIcon);
152     QGridLayout* gridLayout = (QGridLayout*) messageBox.layout();
153     gridLayout->addWidget(&cbShowTrayIcon, gridLayout->rowCount(), 0, 1, gridLayout->columnCount());
154     cbShowTrayIcon.blockSignals(true);
155 
156     //getting the result
157     bool crHidden = (messageBox.exec() == QMessageBox::Yes);
158     bool bShowTrayicon = (cbShowTrayIcon.checkState() == Qt::Checked);
159 
160 
161     X2goSettings st ( "sessions" );
162 
163     QString name=st.setting()->value ( bt->id() +"/name",
164                                        ( QVariant ) tr ( "New Session" ) ).toString() ;
165 
166     // PyHoca-GUI uses the slash as separator for cascaded menus, so let's handle these on the file system
167     name.replace("/","::");
168 
169 
170     QString sessIcon = wrap_legacy_resource_URIs (st.setting ()->value (bt->id() + "/icon",
171                                                                         (QVariant) ":/img/icons/128x128/x2gosession.png"
172                                                                        ).toString ());
173     sessIcon = expandHome(sessIcon);
174     if ( sessIcon.startsWith ( ":/img/icons",Qt::CaseInsensitive ) ||
175             !sessIcon.endsWith ( ".png",Qt::CaseInsensitive ) )
176     {
177         sessIcon="/usr/share/x2goclient/icons/x2gosession.png";
178     }
179 
180     QString cmd="x2goclient";
181     QStringList args;
182     if ( crHidden )
183         args << "--hide";
184 
185     if (bShowTrayicon)
186         args << "--tray-icon";
187 
188     args << QString ("--sessionid=" + bt->id ());
189 
190 #ifndef Q_OS_WIN
191     QFile file (
192 #if QT_VERSION < 0x050000
193         QDesktopServices::storageLocation (
194             QDesktopServices::DesktopLocation ) +"/"+name+".desktop" );
195 #else
196         QStandardPaths::writableLocation(
197             QStandardPaths::DesktopLocation) +"/"+name+".desktop" );
198 #endif
199     if ( !file.open ( QIODevice::WriteOnly | QIODevice::Text ) )
200         return;
201 
202     QTextStream out ( &file );
203     out << "[Desktop Entry]\n"<<
204         "Exec="<<cmd<<" "<<args.join (" ")<<"\n"<<
205         "Icon="<<sessIcon<<"\n"<<
206         "Name="<<name<<"\n"<<
207         "StartupNotify=true\n"<<
208         "Terminal=false\n"<<
209         "Type=Application\n"<<
210         "X-KDE-SubstituteUID=false\n";
211     file.setPermissions(QFile::ReadOwner|QFile::WriteOwner|QFile::ExeOwner);
212     file.close();
213 #else
214     // Windows can't create links containing "::"
215     QString linkname=name;
216     linkname.replace("::","_");
217     QString scrname=QDir::tempPath() +"\\mklnk.vbs";
218     QFile file ( scrname );
219     if ( !file.open ( QIODevice::WriteOnly | QIODevice::Text ) )
220         return;
221 
222     QSettings xst ( "HKEY_LOCAL_MACHINE\\SOFTWARE\\x2goclient",
223                     QSettings::NativeFormat );
224     QString workDir=xst.value ( "Default" ).toString();
225     QString progname=workDir+"\\x2goclient.exe";
226     QTextStream out ( &file );
227     out << "Set Shell = CreateObject(\"WScript.Shell\")\n"<<
228         "DesktopPath = Shell.SpecialFolders(\"Desktop\")\n"<<
229         "Set link = Shell.CreateShortcut(DesktopPath & \"\\"<<linkname<<
230         ".lnk\")\n"<<
231         "link.Arguments = \""<<args.join (" ")<<"\"\n"<<
232         "link.Description = \""<<tr ( "X2Go Link to session " ) <<
233         "--"<<name<<"--"<<"\"\n"<<
234         "link.TargetPath = \""<<progname<<"\"\n"<<
235         "link.iconLocation = \""<<progname<<"\"\n"<<
236         "link.WindowStyle = 1\n"<<
237         "link.WorkingDirectory = \""<<workDir<<"\"\n"<<
238         "link.Save\n";
239     file.close();
240     system ( scrname.toLatin1() );
241     QFile::remove ( scrname );
242 #endif
243 }
244 
245 
createBut(const QString & id)246 SessionButton* SessionExplorer::createBut ( const QString& id )
247 {
248     SessionButton* l;
249     l=new SessionButton ( parent,parent->getUsersFrame(),id );
250     sessions.append ( l );
251     connect ( l,SIGNAL ( signal_edit ( SessionButton* ) ),
252               this,SLOT ( slotEdit ( SessionButton* ) ) );
253 
254     connect ( l,SIGNAL ( signal_remove ( SessionButton* ) ),
255               this,SLOT ( slotDeleteButton ( SessionButton* ) ) );
256 
257     connect ( l,SIGNAL ( sessionSelected ( SessionButton* ) ),parent,
258               SLOT ( slotSelectedFromList ( SessionButton* ) ) );
259 
260     if(l->getPath()!="")
261     {
262         if(findFolder(l->getPath())==-1)
263         {
264             createFolder(l->getPath());
265         }
266     }
267 
268     return l;
269 }
270 
271 
placeButtons()272 void SessionExplorer::placeButtons()
273 {
274     getFoldersFromConfig();
275 
276     setNavigationVisible(currentPath.length()>0);
277     resize();
278     int currentVerticalPosition=0;
279     qSort ( sessions.begin(),sessions.end(),SessionButton::lessThen );
280     qSort ( folders.begin(), folders.end(), FolderButton::lessThen );
281 
282     for ( int i=0; i<folders.size(); ++i )
283     {
284         if(folders[i]->getPath() != currentPath)
285         {
286             folders[i]->hide();
287             continue;
288         }
289 
290         if ( parent->getMiniMode() )
291         {
292             folders[i]->move ( ( parent->getUsersArea()->width()-260 ) /2,
293                                currentVerticalPosition+5 );
294             currentVerticalPosition+=170;
295         }
296         else
297         {
298             folders[i]->move ( ( parent->getUsersArea()->width()-360 ) /2,
299                                currentVerticalPosition+5 );
300             currentVerticalPosition+=230;
301         }
302 
303         folders[i]->show();
304         folders[i]->setChildrenList(getFolderChildren(folders[i]));
305     }
306 
307     for ( int i=0; i<sessions.size(); ++i )
308     {
309         if(sessions[i]->getPath() != currentPath)
310         {
311             sessions[i]->hide();
312             continue;
313         }
314 
315         int horizontalPosition=(parent->getMiniMode())?(parent->getUsersArea()->width()-260 ) /2:(parent->getUsersArea()->width()-360 ) /2;
316 
317         sessions[i]->move ( horizontalPosition,
318                             currentVerticalPosition+5 );
319 
320         if(parent->getBrokerMode())
321         {
322             currentVerticalPosition+=150;
323         }
324         else
325         {
326             if ( parent->getMiniMode() )
327             {
328                 currentVerticalPosition+=170;
329             }
330             else
331             {
332                 currentVerticalPosition+=230;
333             }
334         }
335         sessions[i]->show();
336     }
337 
338     if ( currentVerticalPosition )
339     {
340         parent->getUsersFrame()->setFixedHeight (
341             currentVerticalPosition);
342     }
343 }
344 
getFolderChildren(FolderButton * folder)345 QStringList SessionExplorer::getFolderChildren(FolderButton* folder)
346 {
347     QStringList children;
348     QString normPath=(folder->getPath()+"/"+folder->getName()).split("/",QString::SkipEmptyParts).join("/");
349 
350     for(int i=0; i<folders.count(); ++i)
351     {
352         if(folders[i]->getPath()==normPath)
353             children<<folders[i]->getName();
354     }
355     for(int i=0; i<sessions.count(); ++i)
356     {
357         if(sessions[i]->getPath()==normPath)
358             children<<sessions[i]->name();
359     }
360     return children;
361 }
362 
363 
slotDeleteButton(SessionButton * bt)364 void SessionExplorer::slotDeleteButton ( SessionButton * bt )
365 {
366     if ( QMessageBox::warning (
367                 parent,bt->name(),
368                 tr ( "Are you sure you want to delete this session?" ),
369                 QMessageBox::Yes,QMessageBox::No ) !=QMessageBox::Yes )
370         return;
371 
372     X2goSettings st ( "sessions" );
373 
374     st.setting()->beginGroup ( bt->id() );
375     st.setting()->remove ( "" );
376     st.setting()->sync();
377     sessions.removeAll ( bt );
378     bt->close();
379     placeButtons();
380     parent->getUsersArea()->ensureVisible ( 0,0,50,220 );
381 }
382 
setNavigationVisible(bool value)383 void SessionExplorer::setNavigationVisible(bool value)
384 {
385     backButton->setVisible(value);
386     pathLabel->setVisible(value);
387 }
388 
createFolder(QString path)389 void SessionExplorer::createFolder(QString path)
390 {
391     QStringList tails=path.split("/");
392     QStringList currentPath;
393     for(int i=0; i<tails.count()-1; ++i)
394     {
395         currentPath<<tails[i];
396         if(findFolder(currentPath.join("/"))==-1)
397         {
398             createFolder(currentPath.join("/"));
399         }
400     }
401     FolderButton* fb=new FolderButton(parent,parent->getUsersFrame(),currentPath.join("/"), tails.last());
402     connect(fb, SIGNAL(folderSelected(FolderButton*)), this, SLOT(slotFolderSelected(FolderButton*)));
403     folders<<fb;
404 }
405 
findFolder(QString path)406 int SessionExplorer::findFolder(QString path)
407 {
408     for(int i=0; i<folders.count(); ++i)
409     {
410         QString normPath=(folders[i]->getPath()+"/"+folders[i]->getName()).split("/",QString::SkipEmptyParts).join("/");
411         if(normPath==path)
412             return i;
413     }
414     return -1;
415 }
416 
slotFolderSelected(FolderButton * bt)417 void SessionExplorer::slotFolderSelected(FolderButton* bt)
418 {
419     currentPath=(bt->getPath()+"/"+bt->getName()).split("/",QString::SkipEmptyParts).join("/");
420     placeButtons();
421 }
422 
slotLevelUp()423 void SessionExplorer::slotLevelUp()
424 {
425     QStringList levels=currentPath.split("/",QString::SkipEmptyParts);
426     if(levels.count())
427     {
428         levels.pop_back();
429         currentPath=levels.join("/");
430     }
431     placeButtons();
432 }
433 
setFolderIcon(QString path,QString icon)434 void SessionExplorer::setFolderIcon(QString path, QString icon)
435 {
436     QPixmap pix(icon);
437     if(!pix.isNull())
438     {
439         pix=pix.scaled(64,64,Qt::KeepAspectRatio, Qt::SmoothTransformation);
440         path=path.split("/",QString::SkipEmptyParts).join("::");
441 
442         X2goSettings *st;
443         if (parent->getBrokerMode())
444             st=new X2goSettings(parent->getConfig()->iniFile,QSettings::IniFormat);
445         else
446             st= new X2goSettings( "sessions" );
447         QByteArray bytes;
448         QBuffer buffer(&bytes);
449         buffer.open(QIODevice::WriteOnly);
450         pix.save(&buffer,"PNG");
451         x2goDebug<<"Save: "<<path;
452         st->setting()->setValue("icon_"+path, QString(bytes.toBase64()));
453         st->setting()->sync();
454         FolderButton* b;
455         foreach(b, folders)
456         {
457             if((b->getPath()+"/"+b->getName()).split("/",QString::SkipEmptyParts).join("::")==path)
458             {
459                 b->loadIcon();
460                 break;
461             }
462         }
463     }
464 }
465 
createNewFolder(QString path)466 void SessionExplorer::createNewFolder(QString path)
467 {
468     X2goSettings *st;
469     if (parent->getBrokerMode())
470         st=new X2goSettings(parent->getConfig()->iniFile,QSettings::IniFormat);
471     else
472         st= new X2goSettings( "sessions" );
473 
474 
475     if(findFolder(path)==-1)
476     {
477         QString name=path;
478         name.replace("/","::");
479         st->setting()->setValue(name, QByteArray());
480         st->setting()->sync();
481         createFolder(path);
482         placeButtons();
483     }
484 }
485 
renameFolder(QString oldPath,QString currentPath)486 void SessionExplorer::renameFolder(QString oldPath, QString currentPath)
487 {
488     FolderButton* b;
489     oldPath=oldPath.split("/",QString::SkipEmptyParts).join("/");
490     currentPath=currentPath.split("/",QString::SkipEmptyParts).join("/");
491 
492     QStringList parts=oldPath.split("/",QString::SkipEmptyParts);
493     QString oldName=parts.last();
494     parts.pop_back();
495     QString pathOfFolder=parts.join("/");
496 
497 
498     foreach(b, folders)
499     {
500         if(b->getPath()==pathOfFolder && b->getName()==oldName)
501         {
502             b->setName(currentPath.split("/",QString::SkipEmptyParts).last());
503         }
504         if((b->getPath()+"/").indexOf(oldPath+"/")==0)
505         {
506             QString newPath=currentPath+b->getPath().mid(oldPath.length());
507             b->setPath(newPath);
508         }
509     }
510 
511     SessionButton* s;
512     foreach(s, sessions)
513     {
514         if((s->getPath()+"/").indexOf(oldPath+"/")==0)
515         {
516             QString newPath=currentPath+s->getPath().mid(oldPath.length());
517             s->setPath(newPath);
518         }
519     }
520     if((this->currentPath+"/").indexOf(oldPath+"/")==0)
521     {
522         this->currentPath=currentPath+this->currentPath.mid(oldPath.length());
523     }
524 
525     X2goSettings *st;
526     if (parent->getBrokerMode())
527         st=new X2goSettings(parent->getConfig()->iniFile,QSettings::IniFormat);
528     else
529         st= new X2goSettings( "sessions" );
530 
531     QStringList folders=st->setting()->childKeys();
532     QString folder;
533     foreach(folder,folders)
534     {
535         QString fname=folder;
536         folder.replace("::","/");
537         if((folder+"/").indexOf(oldPath+"/")==0)
538         {
539             QVariant value=st->setting()->value(fname);
540             folder=currentPath+folder.mid(oldPath.length());
541             folder.replace("/","::");
542             st->setting()->setValue(folder,value);
543             st->setting()->remove(fname);
544         }
545     }
546 
547     QStringList sessions=st->setting()->childGroups();
548     QString session;
549     foreach(session, sessions)
550     {
551         QString sname=st->setting()->value(session+"/name").toString();
552         if((sname+"/").indexOf(oldPath+"/")==0)
553         {
554             sname=currentPath+sname.mid(oldPath.length());
555             st->setting()->setValue(session+"/name",sname);
556         }
557     }
558     st->setting()->sync();
559     placeButtons();
560 }
561 
isFolderEmpty(QString path)562 bool SessionExplorer::isFolderEmpty(QString path)
563 {
564     FolderButton* b;
565     path=path.split("/",QString::SkipEmptyParts).join("/");
566 
567     foreach(b, folders)
568     {
569         if(b->getPath()==path)
570         {
571             return false;
572         }
573     }
574 
575     SessionButton* s;
576     foreach(s, sessions)
577     {
578         if(s->getPath()==path)
579         {
580             return false;
581         }
582     }
583     return true;
584 }
585 
deleteFolder(QString path)586 void SessionExplorer::deleteFolder(QString path)
587 {
588     path=path.split("/",QString::SkipEmptyParts).join("::");
589     X2goSettings *st;
590     if (parent->getBrokerMode())
591         st=new X2goSettings(parent->getConfig()->iniFile,QSettings::IniFormat);
592     else
593         st= new X2goSettings( "sessions" );
594 
595     st->setting()->remove(path);
596 
597     path.replace("::","/");
598 
599 
600     for(int i=0; i< folders.count(); ++i)
601     {
602         FolderButton* b=folders[i];
603         if((b->getPath()+"/"+b->getName()).split("/",QString::SkipEmptyParts).join("/")==path)
604         {
605             b->close();
606             folders.removeAt(i);
607             break;
608         }
609     }
610     if(currentPath==path)
611     {
612         currentPath="";
613     }
614     placeButtons();
615 }
616 
setEnable(bool enable)617 void SessionExplorer::setEnable(bool enable)
618 {
619     backButton->setEnabled(enable);
620 }
621