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