1 /*
2 SPDX-FileCopyrightText: 2008-2014 Eike Hein <hein@kde.org>
3 SPDX-FileCopyrightText: 2009 Juan Carlos Torres <carlosdgtorres@gmail.com>
4 SPDX-FileCopyrightText: 2020 Ryan McCoskrie <work@ryanmccoskrie.me>
5
6 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
7 */
8
9 #include "mainwindow.h"
10 #include "config/appearancesettings.h"
11 #include "config/windowsettings.h"
12 #include "firstrundialog.h"
13 #include "sessionstack.h"
14 #include "settings.h"
15 #include "skin.h"
16 #include "tabbar.h"
17 #include "terminal.h"
18 #include "titlebar.h"
19 #include "ui_behaviorsettings.h"
20
21 #include <KAboutData>
22 #include <KActionCollection>
23 #include <KConfigDialog>
24 #include <KGlobalAccel>
25 #include <KHelpMenu>
26 #include <KLocalizedString>
27 #include <KMessageBox>
28 #include <KNotification>
29 #include <KNotifyConfigWidget>
30 #include <KShortcutsDialog>
31 #include <KStandardAction>
32 #include <KStatusNotifierItem>
33 #include <KToggleFullScreenAction>
34 #include <KWindowEffects>
35 #include <KWindowSystem>
36
37 #include <QApplication>
38 #include <QDBusConnection>
39 #include <QDBusInterface>
40 #include <QDBusPendingReply>
41 #include <QDesktopWidget>
42 #include <QMenu>
43 #include <QPainter>
44 #include <QPlatformSurfaceEvent>
45 #include <QScreen>
46 #include <QWhatsThis>
47 #include <QWindow>
48
49 #if HAVE_X11
50 #include <QX11Info>
51
52 #include <X11/Xlib.h>
53 #include <fixx11h.h>
54 #endif
55
56 #if HAVE_KWAYLAND
57 #include <KWayland/Client/connection_thread.h>
58 #include <KWayland/Client/plasmashell.h>
59 #include <KWayland/Client/registry.h>
60 #include <KWayland/Client/surface.h>
61 #endif
62
MainWindow(QWidget * parent)63 MainWindow::MainWindow(QWidget *parent)
64 : KMainWindow(parent, Qt::CustomizeWindowHint | Qt::FramelessWindowHint | Qt::Tool)
65 {
66 QDBusConnection::sessionBus().registerObject(QStringLiteral("/yakuake/window"), this, QDBusConnection::ExportScriptableSlots);
67
68 setAttribute(Qt::WA_TranslucentBackground, true);
69 setAttribute(Qt::WA_DeleteOnClose, false);
70 setAttribute(Qt::WA_QuitOnClose, true);
71
72 m_skin = new Skin();
73 m_menu = new QMenu(this);
74 m_helpMenu = new KHelpMenu(this, KAboutData::applicationData());
75 m_sessionStack = new SessionStack(this);
76 m_titleBar = new TitleBar(this);
77 m_tabBar = new TabBar(this);
78 m_notifierItem = nullptr;
79
80 m_firstRunDialog = nullptr;
81 m_isFullscreen = false;
82
83 #if HAVE_X11
84 m_kwinAssistPropSet = false;
85 m_isX11 = KWindowSystem::isPlatformX11();
86 #else
87 m_isX11 = false;
88 #endif
89 m_isWayland = KWindowSystem::isPlatformWayland();
90 #if HAVE_KWAYLAND
91 m_plasmaShell = nullptr;
92 m_plasmaShellSurface = nullptr;
93 initWayland();
94 #endif
95
96 m_toggleLock = false;
97
98 setupActions();
99 setupMenu();
100
101 connect(m_tabBar, SIGNAL(newTabRequested()), m_sessionStack, SLOT(addSession()));
102 connect(m_tabBar, SIGNAL(lastTabClosed()), m_tabBar, SIGNAL(newTabRequested()));
103 connect(m_tabBar, SIGNAL(lastTabClosed()), this, SLOT(handleLastTabClosed()));
104 connect(m_tabBar, SIGNAL(tabSelected(int)), m_sessionStack, SLOT(raiseSession(int)));
105 connect(m_tabBar, SIGNAL(tabClosed(int)), m_sessionStack, SLOT(removeSession(int)));
106 connect(m_tabBar, &TabBar::tabTitleEdited, m_sessionStack, [&](int, QString) {
107 m_sessionStack->raiseSession(m_sessionStack->activeSessionId());
108 });
109 connect(m_tabBar, SIGNAL(requestTerminalHighlight(int)), m_sessionStack, SLOT(handleTerminalHighlightRequest(int)));
110 connect(m_tabBar, SIGNAL(requestRemoveTerminalHighlight()), m_sessionStack, SIGNAL(removeTerminalHighlight()));
111 connect(m_tabBar, SIGNAL(tabContextMenuClosed()), m_sessionStack, SIGNAL(removeTerminalHighlight()));
112
113 connect(m_sessionStack, SIGNAL(sessionAdded(int, QString)), m_tabBar, SLOT(addTab(int, QString)));
114 connect(m_sessionStack, SIGNAL(sessionRaised(int)), m_tabBar, SLOT(selectTab(int)));
115 connect(m_sessionStack, SIGNAL(sessionRemoved(int)), m_tabBar, SLOT(removeTab(int)));
116 connect(m_sessionStack, SIGNAL(activeTitleChanged(QString)), m_titleBar, SLOT(setTitle(QString)));
117 connect(m_sessionStack, SIGNAL(activeTitleChanged(QString)), this, SLOT(setWindowTitle(QString)));
118
119 connect(&m_mousePoller, SIGNAL(timeout()), this, SLOT(pollMouse()));
120
121 connect(KWindowSystem::self(), SIGNAL(workAreaChanged()), this, SLOT(applyWindowGeometry()));
122 connect(QApplication::desktop(), SIGNAL(screenCountChanged(int)), this, SLOT(updateScreenMenu()));
123
124 applySettings();
125
126 m_sessionStack->addSession();
127
128 if (Settings::firstRun()) {
129 QMetaObject::invokeMethod(this, "toggleWindowState", Qt::QueuedConnection);
130 QMetaObject::invokeMethod(this, "showFirstRunDialog", Qt::QueuedConnection);
131 } else {
132 if (Settings::pollMouse())
133 toggleMousePoll(true);
134 }
135
136 if (Settings::openAfterStart())
137 QMetaObject::invokeMethod(this, "toggleWindowState", Qt::QueuedConnection);
138 }
139
~MainWindow()140 MainWindow::~MainWindow()
141 {
142 Settings::self()->save();
143
144 delete m_skin;
145 }
146
147 #if HAVE_KWAYLAND
initWayland()148 void MainWindow::initWayland()
149 {
150 if (!m_isWayland) {
151 return;
152 }
153
154 using namespace KWayland::Client;
155 auto connection = ConnectionThread::fromApplication(this);
156 if (!connection) {
157 return;
158 }
159 Registry *registry = new Registry(this);
160 registry->create(connection);
161 QObject::connect(registry, &Registry::interfacesAnnounced, this, [registry, this] {
162 const auto interface = registry->interface(Registry::Interface::PlasmaShell);
163 if (interface.name != 0) {
164 m_plasmaShell = registry->createPlasmaShell(interface.name, interface.version, this);
165 }
166 });
167
168 registry->setup();
169 connection->roundtrip();
170 }
171
initWaylandSurface()172 void MainWindow::initWaylandSurface()
173 {
174 if (m_plasmaShellSurface) {
175 m_plasmaShellSurface->setPosition(pos());
176 return;
177 }
178 if (!m_plasmaShell) {
179 return;
180 }
181 if (auto surface = KWayland::Client::Surface::fromWindow(windowHandle())) {
182 m_plasmaShellSurface = m_plasmaShell->createSurface(surface, this);
183 m_plasmaShellSurface->setPosition(pos());
184 }
185 }
186
187 #endif
188
queryClose()189 bool MainWindow::queryClose()
190 {
191 bool confirmQuit = Settings::confirmQuit();
192 bool hasUnclosableSessions = m_sessionStack->hasUnclosableSessions();
193
194 QString closeQuestion = xi18nc("@info", "Are you sure you want to quit?");
195 QString warningMessage;
196
197 if ((confirmQuit && m_sessionStack->count() > 1) || hasUnclosableSessions) {
198 if (confirmQuit && m_sessionStack->count() > 1) {
199 if (hasUnclosableSessions)
200 warningMessage = xi18nc("@info",
201 "<warning>There are multiple open sessions, <emphasis>some of which you have locked to prevent closing them "
202 "accidentally.</emphasis> These will be killed if you continue.</warning>");
203 else
204 warningMessage = xi18nc("@info", "<warning>There are multiple open sessions. These will be killed if you continue.</warning>");
205 } else if (hasUnclosableSessions) {
206 warningMessage = xi18nc("@info",
207 "<warning>There are one or more open sessions that you have locked to prevent closing them accidentally. These will be "
208 "killed if you continue.</warning>");
209 }
210
211 int result = KMessageBox::warningContinueCancel(this,
212 warningMessage + QStringLiteral("<br /><br />") + closeQuestion,
213 xi18nc("@title:window", "Really Quit?"),
214 KStandardGuiItem::quit(),
215 KStandardGuiItem::cancel());
216
217 return result != KMessageBox::Cancel;
218 }
219
220 return true;
221 }
222
setupActions()223 void MainWindow::setupActions()
224 {
225 m_actionCollection = new KActionCollection(this);
226
227 KToggleFullScreenAction *fullScreenAction = new KToggleFullScreenAction(this);
228 fullScreenAction->setWindow(this);
229 actionCollection()->setDefaultShortcut(fullScreenAction, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_F11));
230 m_actionCollection->addAction(QStringLiteral("view-full-screen"), fullScreenAction);
231 connect(fullScreenAction, SIGNAL(toggled(bool)), this, SLOT(setFullScreen(bool)));
232
233 QAction *action = KStandardAction::quit(this, SLOT(close()), actionCollection());
234 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Q));
235 action = KStandardAction::aboutApp(m_helpMenu, SLOT(aboutApplication()), actionCollection());
236 action = KStandardAction::reportBug(m_helpMenu, SLOT(reportBug()), actionCollection());
237 action = KStandardAction::aboutKDE(m_helpMenu, SLOT(aboutKDE()), actionCollection());
238 action = KStandardAction::keyBindings(this, SLOT(configureKeys()), actionCollection());
239 action = KStandardAction::configureNotifications(this, SLOT(configureNotifications()), actionCollection());
240 action = KStandardAction::preferences(this, SLOT(configureApp()), actionCollection());
241
242 action = KStandardAction::whatsThis(this, SLOT(whatsThis()), actionCollection());
243
244 action = actionCollection()->addAction(QStringLiteral("toggle-window-state"));
245 action->setText(xi18nc("@action", "Open/Retract Yakuake"));
246 action->setIcon(QIcon::fromTheme(QStringLiteral("yakuake")));
247 #ifndef Q_OS_WIN /* PORT */
248 KGlobalAccel::self()->setGlobalShortcut(action, QList<QKeySequence>() << QKeySequence(Qt::Key_F12));
249 #else
250 KGlobalAccel::self()->setGlobalShortcut(action, QList<QKeySequence>() << QKeySequence(Qt::Key_F11));
251 #endif
252 connect(action, SIGNAL(triggered()), this, SLOT(toggleWindowState()));
253 connect(action, SIGNAL(changed()), this, SLOT(updateTrayTooltip()));
254 connect(KGlobalAccel::self(), SIGNAL(globalShortcutChanged(QAction *, const QKeySequence &)), this, SLOT(updateTrayTooltip()));
255 updateTrayTooltip();
256
257 action = actionCollection()->addAction(QStringLiteral("keep-open"));
258 action->setText(xi18nc("@action", "Keep window open when it loses focus"));
259 action->setCheckable(true);
260 connect(action, SIGNAL(toggled(bool)), this, SLOT(setKeepOpen(bool)));
261
262 action = actionCollection()->addAction(QStringLiteral("manage-profiles"));
263 action->setText(xi18nc("@action", "Manage Profiles..."));
264 action->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
265 connect(action, SIGNAL(triggered()), m_sessionStack, SIGNAL(manageProfiles()));
266
267 action = actionCollection()->addAction(QStringLiteral("edit-profile"));
268 action->setText(xi18nc("@action", "Edit Current Profile..."));
269 action->setIcon(QIcon::fromTheme(QStringLiteral("document-properties")));
270 connect(action, SIGNAL(triggered()), this, SLOT(handleContextDependentAction()));
271 m_contextDependentActions << action;
272
273 action = actionCollection()->addAction(QStringLiteral("increase-window-width"));
274 action->setText(xi18nc("@action", "Increase Window Width"));
275 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::SHIFT + Qt::Key_Right));
276 connect(action, SIGNAL(triggered()), this, SLOT(increaseWindowWidth()));
277
278 action = actionCollection()->addAction(QStringLiteral("decrease-window-width"));
279 action->setText(xi18nc("@action", "Decrease Window Width"));
280 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::SHIFT + Qt::Key_Left));
281 connect(action, SIGNAL(triggered()), this, SLOT(decreaseWindowWidth()));
282
283 action = actionCollection()->addAction(QStringLiteral("increase-window-height"));
284 action->setText(xi18nc("@action", "Increase Window Height"));
285 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::SHIFT + Qt::Key_Down));
286 connect(action, SIGNAL(triggered()), this, SLOT(increaseWindowHeight()));
287
288 action = actionCollection()->addAction(QStringLiteral("decrease-window-height"));
289 action->setText(xi18nc("@action", "Decrease Window Height"));
290 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::SHIFT + Qt::Key_Up));
291 connect(action, SIGNAL(triggered()), this, SLOT(decreaseWindowHeight()));
292
293 action = actionCollection()->addAction(QStringLiteral("new-session"));
294 action->setText(xi18nc("@action", "New Session"));
295 action->setIcon(QIcon::fromTheme(QStringLiteral("tab-new")));
296 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_T));
297 connect(action, SIGNAL(triggered()), m_sessionStack, SLOT(addSession()));
298
299 action = actionCollection()->addAction(QStringLiteral("new-session-two-horizontal"));
300 action->setText(xi18nc("@action", "Two Terminals, Horizontally"));
301 action->setIcon(QIcon::fromTheme(QStringLiteral("tab-new")));
302 connect(action, SIGNAL(triggered()), m_sessionStack, SLOT(addSessionTwoHorizontal()));
303
304 action = actionCollection()->addAction(QStringLiteral("new-session-two-vertical"));
305 action->setText(xi18nc("@action", "Two Terminals, Vertically"));
306 action->setIcon(QIcon::fromTheme(QStringLiteral("tab-new")));
307 connect(action, SIGNAL(triggered()), m_sessionStack, SLOT(addSessionTwoVertical()));
308
309 action = actionCollection()->addAction(QStringLiteral("new-session-quad"));
310 action->setText(xi18nc("@action", "Four Terminals, Grid"));
311 action->setIcon(QIcon::fromTheme(QStringLiteral("tab-new")));
312 connect(action, SIGNAL(triggered()), m_sessionStack, SLOT(addSessionQuad()));
313
314 action = actionCollection()->addAction(QStringLiteral("close-session"));
315 action->setText(xi18nc("@action", "Close Session"));
316 action->setIcon(QIcon::fromTheme(QStringLiteral("tab-close")));
317 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_W));
318 connect(action, SIGNAL(triggered()), this, SLOT(handleContextDependentAction()));
319 m_contextDependentActions << action;
320
321 action = actionCollection()->addAction(QStringLiteral("previous-session"));
322 action->setText(xi18nc("@action", "Previous Session"));
323 action->setIcon(QIcon::fromTheme(QStringLiteral("go-previous")));
324 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::SHIFT + Qt::Key_Left));
325 connect(action, SIGNAL(triggered()), m_tabBar, SLOT(selectPreviousTab()));
326
327 action = actionCollection()->addAction(QStringLiteral("next-session"));
328 action->setText(xi18nc("@action", "Next Session"));
329 action->setIcon(QIcon::fromTheme(QStringLiteral("go-next")));
330 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::SHIFT + Qt::Key_Right));
331 connect(action, SIGNAL(triggered()), m_tabBar, SLOT(selectNextTab()));
332
333 action = actionCollection()->addAction(QStringLiteral("move-session-left"));
334 action->setText(xi18nc("@action", "Move Session Left"));
335 action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-left")));
336 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Left));
337 connect(action, SIGNAL(triggered()), this, SLOT(handleContextDependentAction()));
338 m_contextDependentActions << action;
339
340 action = actionCollection()->addAction(QStringLiteral("move-session-right"));
341 action->setText(xi18nc("@action", "Move Session Right"));
342 action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right")));
343 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Right));
344 connect(action, SIGNAL(triggered()), this, SLOT(handleContextDependentAction()));
345 m_contextDependentActions << action;
346
347 action = actionCollection()->addAction(QStringLiteral("grow-terminal-right"));
348 action->setText(xi18nc("@action", "Grow Terminal to the Right"));
349 action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right")));
350 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Right));
351 connect(action, SIGNAL(triggered()), this, SLOT(handleContextDependentAction()));
352 m_contextDependentActions << action;
353
354 action = actionCollection()->addAction(QStringLiteral("grow-terminal-left"));
355 action->setText(xi18nc("@action", "Grow Terminal to the Left"));
356 action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-left")));
357 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Left));
358 connect(action, SIGNAL(triggered()), this, SLOT(handleContextDependentAction()));
359 m_contextDependentActions << action;
360
361 action = actionCollection()->addAction(QStringLiteral("grow-terminal-top"));
362 action->setText(xi18nc("@action", "Grow Terminal to the Top"));
363 action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up")));
364 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Up));
365 connect(action, SIGNAL(triggered()), this, SLOT(handleContextDependentAction()));
366 m_contextDependentActions << action;
367
368 action = actionCollection()->addAction(QStringLiteral("grow-terminal-bottom"));
369 action->setText(xi18nc("@action", "Grow Terminal to the Bottom"));
370 action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down")));
371 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Down));
372 connect(action, SIGNAL(triggered()), this, SLOT(handleContextDependentAction()));
373 m_contextDependentActions << action;
374
375 action = actionCollection()->addAction(QStringLiteral("rename-session"));
376 action->setText(xi18nc("@action", "Rename Session..."));
377 action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename")));
378 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_S));
379 connect(action, SIGNAL(triggered()), this, SLOT(handleContextDependentAction()));
380 m_contextDependentActions << action;
381
382 action = actionCollection()->addAction(QStringLiteral("previous-terminal"));
383 action->setText(xi18nc("@action", "Previous Terminal"));
384 action->setIcon(QIcon::fromTheme(QStringLiteral("go-previous")));
385 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_Tab));
386 connect(action, SIGNAL(triggered()), m_sessionStack, SIGNAL(previousTerminal()));
387
388 action = actionCollection()->addAction(QStringLiteral("next-terminal"));
389 action->setText(xi18nc("@action", "Next Terminal"));
390 action->setIcon(QIcon::fromTheme(QStringLiteral("go-next")));
391 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_Tab));
392 connect(action, SIGNAL(triggered()), m_sessionStack, SIGNAL(nextTerminal()));
393
394 action = actionCollection()->addAction(QStringLiteral("close-active-terminal"));
395 action->setText(xi18nc("@action", "Close Active Terminal"));
396 action->setIcon(QIcon::fromTheme(QStringLiteral("view-close")));
397 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_R));
398 connect(action, SIGNAL(triggered()), this, SLOT(handleContextDependentAction()));
399 m_contextDependentActions << action;
400
401 action = actionCollection()->addAction(QStringLiteral("split-left-right"));
402 action->setText(xi18nc("@action", "Split Left/Right"));
403 action->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right")));
404 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_ParenLeft));
405 connect(action, SIGNAL(triggered()), this, SLOT(handleContextDependentAction()));
406 m_contextDependentActions << action;
407
408 action = actionCollection()->addAction(QStringLiteral("split-top-bottom"));
409 action->setText(xi18nc("@action", "Split Top/Bottom"));
410 action->setIcon(QIcon::fromTheme(QStringLiteral("view-split-top-bottom")));
411 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_ParenRight));
412 connect(action, SIGNAL(triggered()), this, SLOT(handleContextDependentAction()));
413 m_contextDependentActions << action;
414
415 action = actionCollection()->addAction(QStringLiteral("toggle-session-prevent-closing"));
416 action->setText(xi18nc("@action", "Prevent Closing"));
417 action->setCheckable(true);
418 connect(action, SIGNAL(triggered(bool)), this, SLOT(handleContextDependentToggleAction(bool)));
419 m_contextDependentActions << action;
420
421 action = actionCollection()->addAction(QStringLiteral("toggle-session-keyboard-input"));
422 action->setText(xi18nc("@action", "Disable Keyboard Input"));
423 action->setCheckable(true);
424 connect(action, SIGNAL(triggered(bool)), this, SLOT(handleContextDependentToggleAction(bool)));
425 m_contextDependentActions << action;
426
427 action = actionCollection()->addAction(QStringLiteral("toggle-session-monitor-activity"));
428 action->setText(xi18nc("@action", "Monitor for Activity"));
429 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_A));
430 action->setCheckable(true);
431 connect(action, SIGNAL(triggered(bool)), this, SLOT(handleContextDependentToggleAction(bool)));
432 m_contextDependentActions << action;
433
434 action = actionCollection()->addAction(QStringLiteral("toggle-session-monitor-silence"));
435 action->setText(xi18nc("@action", "Monitor for Silence"));
436 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_I));
437 action->setCheckable(true);
438 connect(action, SIGNAL(triggered(bool)), this, SLOT(handleContextDependentToggleAction(bool)));
439 m_contextDependentActions << action;
440
441 action = actionCollection()->addAction(QStringLiteral("toggle-titlebar"));
442 action->setText(xi18nc("@action", "Toggle Titlebar"));
443 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_M));
444 action->setCheckable(true);
445 connect(action, SIGNAL(triggered()), this, SLOT(handleToggleTitlebar()));
446
447 for (uint i = 1; i <= 10; ++i) {
448 action = actionCollection()->addAction(QStringLiteral("switch-to-session-%1").arg(i));
449 action->setText(xi18nc("@action", "Switch to Session %1", i));
450 action->setData(i - 1);
451 connect(action, SIGNAL(triggered()), this, SLOT(handleSwitchToAction()));
452
453 if (i < 10) {
454 // add default shortcut bindings for the first 9 sessions
455 actionCollection()->setDefaultShortcut(action, QStringLiteral("Alt+%1").arg(i));
456 } else {
457 // add default shortcut bindings for the 10th session
458 actionCollection()->setDefaultShortcut(action, Qt::ALT + Qt::Key_0);
459 }
460 }
461
462 m_actionCollection->associateWidget(this);
463 m_actionCollection->readSettings();
464 }
465
handleContextDependentAction(QAction * action,int sessionId)466 void MainWindow::handleContextDependentAction(QAction *action, int sessionId)
467 {
468 if (sessionId == -1)
469 sessionId = m_sessionStack->activeSessionId();
470 if (sessionId == -1)
471 return;
472
473 if (!action)
474 action = qobject_cast<QAction *>(QObject::sender());
475
476 if (action == actionCollection()->action(QStringLiteral("edit-profile")))
477 m_sessionStack->editProfile(sessionId);
478
479 if (action == actionCollection()->action(QStringLiteral("close-session")))
480 m_sessionStack->removeSession(sessionId);
481
482 if (action == actionCollection()->action(QStringLiteral("move-session-left")))
483 m_tabBar->moveTabLeft(sessionId);
484
485 if (action == actionCollection()->action(QStringLiteral("move-session-right")))
486 m_tabBar->moveTabRight(sessionId);
487
488 if (action == actionCollection()->action(QStringLiteral("rename-session")))
489 m_tabBar->interactiveRename(sessionId);
490
491 if (action == actionCollection()->action(QStringLiteral("close-active-terminal")))
492 m_sessionStack->closeActiveTerminal(sessionId);
493
494 if (action == actionCollection()->action(QStringLiteral("split-left-right")))
495 m_sessionStack->splitSessionLeftRight(sessionId);
496
497 if (action == actionCollection()->action(QStringLiteral("split-top-bottom")))
498 m_sessionStack->splitSessionTopBottom(sessionId);
499
500 if (action == actionCollection()->action(QStringLiteral("grow-terminal-right")))
501 m_sessionStack->tryGrowTerminalRight(m_sessionStack->activeTerminalId());
502
503 if (action == actionCollection()->action(QStringLiteral("grow-terminal-left")))
504 m_sessionStack->tryGrowTerminalLeft(m_sessionStack->activeTerminalId());
505
506 if (action == actionCollection()->action(QStringLiteral("grow-terminal-top")))
507 m_sessionStack->tryGrowTerminalTop(m_sessionStack->activeTerminalId());
508
509 if (action == actionCollection()->action(QStringLiteral("grow-terminal-bottom")))
510 m_sessionStack->tryGrowTerminalBottom(m_sessionStack->activeTerminalId());
511 }
512
handleContextDependentToggleAction(bool checked,QAction * action,int sessionId)513 void MainWindow::handleContextDependentToggleAction(bool checked, QAction *action, int sessionId)
514 {
515 if (sessionId == -1)
516 sessionId = m_sessionStack->activeSessionId();
517 if (sessionId == -1)
518 return;
519
520 if (!action)
521 action = qobject_cast<QAction *>(QObject::sender());
522
523 if (action == actionCollection()->action(QStringLiteral("toggle-session-prevent-closing"))) {
524 m_sessionStack->setSessionClosable(sessionId, !checked);
525
526 // Repaint the tab bar when the Prevent Closing action is toggled
527 // so the lock icon is added to or removed from the tab label.
528 m_tabBar->repaint();
529 }
530
531 if (action == actionCollection()->action(QStringLiteral("toggle-session-keyboard-input")))
532 m_sessionStack->setSessionKeyboardInputEnabled(sessionId, !checked);
533
534 if (action == actionCollection()->action(QStringLiteral("toggle-session-monitor-activity")))
535 m_sessionStack->setSessionMonitorActivityEnabled(sessionId, checked);
536
537 if (action == actionCollection()->action(QStringLiteral("toggle-session-monitor-silence")))
538 m_sessionStack->setSessionMonitorSilenceEnabled(sessionId, checked);
539 }
540
setContextDependentActionsQuiet(bool quiet)541 void MainWindow::setContextDependentActionsQuiet(bool quiet)
542 {
543 QListIterator<QAction *> i(m_contextDependentActions);
544
545 while (i.hasNext())
546 i.next()->blockSignals(quiet);
547 }
548
handleToggleTerminalKeyboardInput(bool checked)549 void MainWindow::handleToggleTerminalKeyboardInput(bool checked)
550 {
551 QAction *action = qobject_cast<QAction *>(QObject::sender());
552
553 if (!action || action->data().isNull())
554 return;
555
556 bool ok = false;
557 int terminalId = action->data().toInt(&ok);
558 if (!ok)
559 return;
560
561 m_sessionStack->setTerminalKeyboardInputEnabled(terminalId, !checked);
562 }
563
handleToggleTerminalMonitorActivity(bool checked)564 void MainWindow::handleToggleTerminalMonitorActivity(bool checked)
565 {
566 QAction *action = qobject_cast<QAction *>(QObject::sender());
567
568 if (!action || action->data().isNull())
569 return;
570
571 bool ok = false;
572 int terminalId = action->data().toInt(&ok);
573 if (!ok)
574 return;
575
576 m_sessionStack->setTerminalMonitorActivityEnabled(terminalId, checked);
577 }
578
handleToggleTerminalMonitorSilence(bool checked)579 void MainWindow::handleToggleTerminalMonitorSilence(bool checked)
580 {
581 QAction *action = qobject_cast<QAction *>(QObject::sender());
582
583 if (!action || action->data().isNull())
584 return;
585
586 bool ok = false;
587 int terminalId = action->data().toInt(&ok);
588 if (!ok)
589 return;
590
591 m_sessionStack->setTerminalMonitorSilenceEnabled(terminalId, checked);
592 }
593
handleTerminalActivity(Terminal * terminal)594 void MainWindow::handleTerminalActivity(Terminal *terminal)
595 {
596 Session *session = qobject_cast<Session *>(sender());
597
598 if (session) {
599 disconnect(terminal, SIGNAL(activityDetected(Terminal *)), session, SIGNAL(activityDetected(Terminal *)));
600
601 QString message(xi18nc("@info", "Activity detected in monitored terminal in session \"%1\".", m_tabBar->tabTitle(session->id())));
602
603 KNotification::event(QLatin1String("activity"), message, QPixmap(), terminal->partWidget(), KNotification::CloseWhenWidgetActivated);
604 }
605 }
606
handleTerminalSilence(Terminal * terminal)607 void MainWindow::handleTerminalSilence(Terminal *terminal)
608 {
609 Session *session = qobject_cast<Session *>(sender());
610
611 if (session) {
612 QString message(xi18nc("@info", "Silence detected in monitored terminal in session \"%1\".", m_tabBar->tabTitle(session->id())));
613
614 KNotification::event(QLatin1String("silence"), message, QPixmap(), terminal->partWidget(), KNotification::CloseWhenWidgetActivated);
615 }
616 }
617
handleLastTabClosed()618 void MainWindow::handleLastTabClosed()
619 {
620 if (isVisible() && !Settings::keepOpenAfterLastSessionCloses())
621 toggleWindowState();
622 }
623
handleSwitchToAction()624 void MainWindow::handleSwitchToAction()
625 {
626 QAction *action = qobject_cast<QAction *>(QObject::sender());
627
628 if (action && !action->data().isNull())
629 m_sessionStack->raiseSession(m_tabBar->sessionAtTab(action->data().toInt()));
630 }
631
handleToggleTitlebar()632 void MainWindow::handleToggleTitlebar()
633 {
634 auto toggleFunc = [this]() {
635 bool showTitleBar = !Settings::showTitleBar();
636 m_titleBar->setVisible(showTitleBar);
637 Settings::setShowTitleBar(showTitleBar);
638 Settings::self()->save();
639 applyWindowGeometry();
640 };
641
642 if (Settings::showTitleBar()) { // If the title bar is hidden don't ask if toggling is ok
643
644 const char *message =
645 "You are about to hide the title bar. This will keep you "
646 "from accessing the settings menu via the mouse. To show "
647 "the title bar again press the keyboard shortcut (default "
648 "Ctrl+Shift+m) or access the settings menu via keyborad "
649 "shortcut (defult: Ctrl+Shift+,).";
650
651 const int result = KMessageBox::warningContinueCancel(this,
652 xi18nc("@info", message),
653 xi18nc("@title:window", "Hiding Title Bar"),
654 KStandardGuiItem::cont(),
655 KStandardGuiItem::cancel(),
656 QStringLiteral("hinding_title_bar"));
657
658 if (result == KMessageBox::ButtonCode::Continue) {
659 toggleFunc();
660 }
661 } else {
662 toggleFunc();
663 }
664 }
665
setupMenu()666 void MainWindow::setupMenu()
667 {
668 m_menu->insertSection(nullptr, xi18nc("@title:menu", "Help"));
669 m_menu->addAction(actionCollection()->action(QLatin1String(KStandardAction::name(KStandardAction::WhatsThis))));
670 m_menu->addAction(actionCollection()->action(QLatin1String(KStandardAction::name(KStandardAction::ReportBug))));
671 m_menu->addAction(actionCollection()->action(QLatin1String(KStandardAction::name(KStandardAction::AboutApp))));
672 m_menu->addAction(actionCollection()->action(QLatin1String(KStandardAction::name(KStandardAction::AboutKDE))));
673
674 m_menu->insertSection(nullptr, xi18nc("@title:menu", "Quick Options"));
675 m_menu->addAction(actionCollection()->action(QStringLiteral("view-full-screen")));
676 m_menu->addAction(actionCollection()->action(QStringLiteral("keep-open")));
677
678 m_screenMenu = new QMenu(this);
679 connect(m_screenMenu, SIGNAL(triggered(QAction *)), this, SLOT(setScreen(QAction *)));
680 m_screenMenu->setTitle(xi18nc("@title:menu", "Screen"));
681 m_menu->addMenu(m_screenMenu);
682
683 m_windowWidthMenu = new QMenu(this);
684 connect(m_windowWidthMenu, SIGNAL(triggered(QAction *)), this, SLOT(setWindowWidth(QAction *)));
685 m_windowWidthMenu->setTitle(xi18nc("@title:menu", "Width"));
686 m_menu->addMenu(m_windowWidthMenu);
687
688 m_windowHeightMenu = new QMenu(this);
689 connect(m_windowHeightMenu, SIGNAL(triggered(QAction *)), this, SLOT(setWindowHeight(QAction *)));
690 m_windowHeightMenu->setTitle(xi18nc("@title:menu", "Height"));
691 m_menu->addMenu(m_windowHeightMenu);
692
693 m_menu->insertSection(nullptr, xi18nc("@title:menu", "Settings"));
694 m_menu->addAction(actionCollection()->action(QStringLiteral("manage-profiles")));
695 m_menu->addAction(actionCollection()->action(QLatin1String(KStandardAction::name(KStandardAction::KeyBindings))));
696 m_menu->addAction(actionCollection()->action(QLatin1String(KStandardAction::name(KStandardAction::ConfigureNotifications))));
697 m_menu->addAction(actionCollection()->action(QLatin1String(KStandardAction::name(KStandardAction::Preferences))));
698
699 m_menu->addSeparator();
700 m_menu->addAction(actionCollection()->action(QLatin1String(KStandardAction::name(KStandardAction::Quit))));
701 }
702
updateScreenMenu()703 void MainWindow::updateScreenMenu()
704 {
705 QAction *action;
706
707 m_screenMenu->clear();
708
709 action = m_screenMenu->addAction(xi18nc("@item:inmenu", "At mouse location"));
710 action->setCheckable(true);
711 action->setData(0);
712 action->setChecked(Settings::screen() == 0);
713
714 for (int i = 1; i <= QGuiApplication::screens().count(); i++) {
715 action = m_screenMenu->addAction(xi18nc("@item:inmenu", "Screen %1", i));
716 action->setCheckable(true);
717 action->setData(i);
718 action->setChecked(i == Settings::screen());
719 }
720
721 action = m_screenMenu->menuAction();
722 action->setVisible(QGuiApplication::screens().count() > 1);
723 }
724
updateWindowSizeMenus()725 void MainWindow::updateWindowSizeMenus()
726 {
727 updateWindowWidthMenu();
728 updateWindowHeightMenu();
729 }
730
updateWindowWidthMenu()731 void MainWindow::updateWindowWidthMenu()
732 {
733 QAction *action = nullptr;
734
735 if (m_windowWidthMenu->isEmpty()) {
736 for (int i = 10; i <= 100; i += 10) {
737 action = m_windowWidthMenu->addAction(QString::number(i) + QStringLiteral("%"));
738 action->setCheckable(true);
739 action->setData(i);
740 action->setChecked(i == Settings::width());
741 }
742 } else {
743 QListIterator<QAction *> i(m_windowWidthMenu->actions());
744
745 while (i.hasNext()) {
746 action = i.next();
747
748 action->setChecked(action->data().toInt() == Settings::width());
749 }
750 }
751 }
752
updateWindowHeightMenu()753 void MainWindow::updateWindowHeightMenu()
754 {
755 QAction *action = nullptr;
756
757 if (m_windowHeightMenu->isEmpty()) {
758 for (int i = 10; i <= 100; i += 10) {
759 action = m_windowHeightMenu->addAction(QString::number(i) + QStringLiteral("%"));
760 action->setCheckable(true);
761 action->setData(i);
762 action->setChecked(i == Settings::height());
763 }
764 } else {
765 QListIterator<QAction *> i(m_windowHeightMenu->actions());
766
767 while (i.hasNext()) {
768 action = i.next();
769
770 action->setChecked(action->data().toInt() == Settings::height());
771 }
772 }
773 }
774
configureKeys()775 void MainWindow::configureKeys()
776 {
777 KShortcutsDialog dialog(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, this);
778 dialog.addCollection(actionCollection());
779
780 const auto collections = m_sessionStack->getPartActionCollections();
781
782 if (collections.size() >= 1) {
783 dialog.addCollection(collections.at(0), QStringLiteral("Konsolepart"));
784 }
785
786 if (!dialog.configure()) {
787 return;
788 }
789
790 if (collections.size() >= 1) {
791 // We need to update all the other collections
792 // rootCollection is the collection which got updatet by the dialog
793 const auto rootCollection = collections.at(0);
794
795 // For all the other collections
796 for (auto i = 1; i < collections.size(); ++i) {
797 // Update all the action they share with rootCollection
798 const auto rootActions = rootCollection->actions();
799 for (const auto *action : rootActions) {
800 if (auto *destaction = collections.at(i)->action(action->objectName())) {
801 destaction->setShortcuts(action->shortcuts());
802 }
803 }
804 }
805 }
806 }
807
configureNotifications()808 void MainWindow::configureNotifications()
809 {
810 KNotifyConfigWidget::configure(this);
811 }
812
configureApp()813 void MainWindow::configureApp()
814 {
815 if (KConfigDialog::showDialog(QStringLiteral("settings")))
816 return;
817
818 KConfigDialog *settingsDialog = new KConfigDialog(this, QStringLiteral("settings"), Settings::self());
819 settingsDialog->setMinimumHeight(560);
820 settingsDialog->setFaceType(KPageDialog::List);
821 connect(settingsDialog, &KConfigDialog::settingsChanged, this, &MainWindow::applySettings);
822
823 WindowSettings *windowSettings = new WindowSettings(settingsDialog);
824 settingsDialog->addPage(windowSettings, xi18nc("@title Preferences page name", "Window"), QStringLiteral("preferences-system-windows-move"));
825 connect(windowSettings, SIGNAL(updateWindowGeometry(int, int, int)), this, SLOT(setWindowGeometry(int, int, int)));
826
827 QWidget *behaviorSettings = new QWidget(settingsDialog);
828 Ui::BehaviorSettings behaviorSettingsUi;
829 behaviorSettingsUi.setupUi(behaviorSettings);
830 settingsDialog->addPage(behaviorSettings, xi18nc("@title Preferences page name", "Behavior"), QStringLiteral("preferences-system-windows-actions"));
831
832 AppearanceSettings *appearanceSettings = new AppearanceSettings(settingsDialog);
833 settingsDialog->addPage(appearanceSettings, xi18nc("@title Preferences page name", "Appearance"), QStringLiteral("preferences-desktop-theme"));
834 connect(settingsDialog, &QDialog::rejected, appearanceSettings, &AppearanceSettings::resetSelection);
835
836 settingsDialog->button(QDialogButtonBox::Help)->hide();
837 settingsDialog->button(QDialogButtonBox::Cancel)->setFocus();
838
839 connect(settingsDialog, &QDialog::finished, [=]() {
840 m_toggleLock = true;
841 KWindowSystem::activateWindow(winId());
842 KWindowSystem::forceActiveWindow(winId());
843 });
844
845 settingsDialog->show();
846 }
847
applySettings()848 void MainWindow::applySettings()
849 {
850 if (Settings::dynamicTabTitles()) {
851 connect(m_sessionStack, SIGNAL(titleChanged(int, QString)), m_tabBar, SLOT(setTabTitleAutomated(int, QString)));
852
853 m_sessionStack->emitTitles();
854 } else {
855 disconnect(m_sessionStack, SIGNAL(titleChanged(int, QString)), m_tabBar, SLOT(setTabTitleAutomated(int, QString)));
856 }
857
858 m_animationTimer.setInterval(Settings::frames() ? 10 : 0);
859
860 m_tabBar->setVisible(Settings::showTabBar());
861 m_titleBar->setVisible(Settings::showTitleBar());
862
863 if (!Settings::showSystrayIcon() && m_notifierItem) {
864 delete m_notifierItem;
865 m_notifierItem = nullptr;
866
867 // Removing the notifier item deletes the menu
868 // add a new one
869 m_menu = new QMenu(this);
870 setupMenu();
871 m_titleBar->updateMenu();
872 } else if (Settings::showSystrayIcon() && !m_notifierItem) {
873 m_notifierItem = new KStatusNotifierItem(this);
874 m_notifierItem->setStandardActionsEnabled(false);
875 m_notifierItem->setIconByName(QStringLiteral("yakuake"));
876 m_notifierItem->setStatus(KStatusNotifierItem::Active);
877 m_notifierItem->setContextMenu(m_menu);
878
879 // Prevent the default implementation of showing
880 // and instead run toggleWindowState
881 m_notifierItem->setAssociatedWidget(nullptr);
882 connect(m_notifierItem, &KStatusNotifierItem::activateRequested, this, &MainWindow::toggleWindowState);
883 updateTrayTooltip();
884 }
885
886 repaint(); // used to repaint skin borders if Settings::hideSkinBorders has been changed
887
888 setKeepOpen(Settings::keepOpen());
889
890 updateScreenMenu();
891 updateWindowSizeMenus();
892
893 updateUseTranslucency();
894
895 applySkin();
896 applyWindowGeometry();
897 applyWindowProperties();
898 }
899
applySkin()900 void MainWindow::applySkin()
901 {
902 bool gotSkin = m_skin->load(Settings::skin(), Settings::skinInstalledWithKns());
903
904 if (!gotSkin) {
905 Settings::setSkin(QStringLiteral("default"));
906 gotSkin = m_skin->load(Settings::skin());
907 }
908
909 if (!gotSkin) {
910 KMessageBox::error(parentWidget(),
911 xi18nc("@info",
912 "<application>Yakuake</application> was unable to load a skin. It is likely that it was installed incorrectly.<nl/><nl/>"
913 "The application will now quit."),
914 xi18nc("@title:window", "Cannot Load Skin"));
915
916 QMetaObject::invokeMethod(qApp, "quit", Qt::QueuedConnection);
917 }
918
919 m_titleBar->applySkin();
920 m_tabBar->applySkin();
921 }
922
applyWindowProperties()923 void MainWindow::applyWindowProperties()
924 {
925 if (Settings::keepOpen() && !Settings::keepAbove()) {
926 KWindowSystem::clearState(winId(), NET::KeepAbove);
927 KWindowSystem::setState(winId(), NET::Sticky | NET::SkipTaskbar | NET::SkipPager);
928 } else
929 KWindowSystem::setState(winId(), NET::KeepAbove | NET::Sticky | NET::SkipTaskbar | NET::SkipPager);
930
931 KWindowSystem::setOnAllDesktops(winId(), Settings::showOnAllDesktops());
932 KWindowEffects::enableBlurBehind(windowHandle(), Settings::blur());
933 }
934
applyWindowGeometry()935 void MainWindow::applyWindowGeometry()
936 {
937 int width, height;
938
939 QAction *action = actionCollection()->action(QStringLiteral("view-full-screen"));
940
941 if (action->isChecked()) {
942 width = 100;
943 height = 100;
944 } else {
945 width = Settings::width();
946 height = Settings::height();
947 }
948
949 setWindowGeometry(width, height, Settings::position());
950 }
951
setWindowGeometry(int newWidth,int newHeight,int newPosition)952 void MainWindow::setWindowGeometry(int newWidth, int newHeight, int newPosition)
953 {
954 QRect workArea = getDesktopGeometry();
955
956 int maxHeight = workArea.height() * newHeight / 100;
957
958 int targetWidth = workArea.width() * newWidth / 100;
959
960 setGeometry(workArea.x() + workArea.width() * newPosition * (100 - newWidth) / 10000, workArea.y(), targetWidth, maxHeight);
961 #if HAVE_KWAYLAND
962 initWaylandSurface();
963 #endif
964
965 maxHeight -= m_titleBar->height();
966 m_titleBar->setGeometry(0, maxHeight, targetWidth, m_titleBar->height());
967 if (!isVisible())
968 m_titleBar->updateMask();
969
970 if (Settings::frames() > 0)
971 m_animationStepSize = maxHeight / Settings::frames();
972 else
973 m_animationStepSize = maxHeight;
974
975 auto borderWidth = Settings::hideSkinBorders() ? 0 : m_skin->borderWidth();
976
977 if (Settings::showTabBar()) {
978 if (m_skin->tabBarCompact()) {
979 m_tabBar->setGeometry(m_skin->tabBarLeft(), maxHeight, width() - m_skin->tabBarLeft() - m_skin->tabBarRight(), m_tabBar->height());
980 } else {
981 maxHeight -= m_tabBar->height();
982 m_tabBar->setGeometry(borderWidth, maxHeight - borderWidth, width() - 2 * borderWidth, m_tabBar->height());
983 }
984 }
985
986 m_sessionStack->setGeometry(borderWidth, 0, width() - 2 * borderWidth, maxHeight - borderWidth);
987
988 updateMask();
989 }
990
setScreen(QAction * action)991 void MainWindow::setScreen(QAction *action)
992 {
993 Settings::setScreen(action->data().toInt());
994 Settings::self()->save();
995
996 applyWindowGeometry();
997
998 updateScreenMenu();
999 }
1000
setWindowWidth(int width)1001 void MainWindow::setWindowWidth(int width)
1002 {
1003 Settings::setWidth(width);
1004 Settings::self()->save();
1005
1006 applyWindowGeometry();
1007
1008 updateWindowWidthMenu();
1009 }
1010
setWindowHeight(int height)1011 void MainWindow::setWindowHeight(int height)
1012 {
1013 Settings::setHeight(height);
1014 Settings::self()->save();
1015
1016 applyWindowGeometry();
1017
1018 updateWindowHeightMenu();
1019 }
1020
setWindowWidth(QAction * action)1021 void MainWindow::setWindowWidth(QAction *action)
1022 {
1023 setWindowWidth(action->data().toInt());
1024 }
1025
setWindowHeight(QAction * action)1026 void MainWindow::setWindowHeight(QAction *action)
1027 {
1028 setWindowHeight(action->data().toInt());
1029 }
1030
increaseWindowWidth()1031 void MainWindow::increaseWindowWidth()
1032 {
1033 if (Settings::width() <= 90)
1034 setWindowWidth(Settings::width() + 10);
1035 }
1036
decreaseWindowWidth()1037 void MainWindow::decreaseWindowWidth()
1038 {
1039 if (Settings::width() >= 20)
1040 setWindowWidth(Settings::width() - 10);
1041 }
1042
increaseWindowHeight()1043 void MainWindow::increaseWindowHeight()
1044 {
1045 if (Settings::height() <= 90)
1046 setWindowHeight(Settings::height() + 10);
1047 }
1048
decreaseWindowHeight()1049 void MainWindow::decreaseWindowHeight()
1050 {
1051 if (Settings::height() >= 20)
1052 setWindowHeight(Settings::height() - 10);
1053 }
1054
updateMask()1055 void MainWindow::updateMask()
1056 {
1057 QRegion region = m_titleBar->mask();
1058
1059 region.translate(0, m_titleBar->y());
1060
1061 region += QRegion(0, 0, width(), m_titleBar->y());
1062
1063 setMask(region);
1064 }
1065
paintEvent(QPaintEvent * event)1066 void MainWindow::paintEvent(QPaintEvent *event)
1067 {
1068 QPainter painter(this);
1069
1070 if (useTranslucency()) {
1071 painter.setOpacity(qreal(Settings::backgroundColorOpacity()) / 100);
1072 painter.fillRect(rect(), Settings::backgroundColor());
1073 painter.setOpacity(1.0);
1074 } else
1075 painter.fillRect(rect(), Settings::backgroundColor());
1076
1077 if (!Settings::hideSkinBorders()) {
1078 const QRect leftBorder(0, 0, m_skin->borderWidth(), height() - m_titleBar->height());
1079 painter.fillRect(leftBorder, m_skin->borderColor());
1080
1081 const QRect rightBorder(width() - m_skin->borderWidth(), 0, m_skin->borderWidth(), height() - m_titleBar->height());
1082 painter.fillRect(rightBorder, m_skin->borderColor());
1083
1084 const QRect bottomBorder(0, height() - m_skin->borderWidth() - m_titleBar->height(), width(), m_skin->borderWidth());
1085 painter.fillRect(bottomBorder, m_skin->borderColor());
1086 }
1087
1088 KMainWindow::paintEvent(event);
1089 }
1090
moveEvent(QMoveEvent * event)1091 void MainWindow::moveEvent(QMoveEvent *event)
1092 {
1093 if (Settings::screen() && QApplication::desktop()->screenNumber(this) != getScreen()) {
1094 Settings::setScreen(QApplication::desktop()->screenNumber(this) + 1);
1095
1096 updateScreenMenu();
1097
1098 applyWindowGeometry();
1099 }
1100
1101 KMainWindow::moveEvent(event);
1102 }
1103
closeEvent(QCloseEvent * event)1104 void MainWindow::closeEvent(QCloseEvent *event)
1105 {
1106 KMainWindow::closeEvent(event);
1107 if (event->isAccepted()) {
1108 QApplication::quit();
1109 }
1110 }
1111
wmActiveWindowChanged()1112 void MainWindow::wmActiveWindowChanged()
1113 {
1114 if (m_toggleLock) {
1115 m_toggleLock = false;
1116 return;
1117 }
1118
1119 KWindowInfo info(KWindowSystem::activeWindow(), {}, NET::WM2TransientFor);
1120
1121 if (info.valid() && info.transientFor() == winId()) {
1122 return;
1123 }
1124
1125 if (!Settings::keepOpen() && isVisible() && !isActiveWindow()) {
1126 toggleWindowState();
1127 }
1128 }
1129
changeEvent(QEvent * event)1130 void MainWindow::changeEvent(QEvent *event)
1131 {
1132 if (event->type() == QEvent::WindowStateChange && !m_isFullscreen) {
1133 if (windowState().testFlag(Qt::WindowMaximized)) {
1134 // Don't alter settings to new size so unmaximizing restores previous geometry.
1135 setWindowGeometry(100, 100, Settings::position());
1136 setWindowState(Qt::WindowMaximized);
1137 } else {
1138 setWindowGeometry(Settings::width(), Settings::height(), Settings::position());
1139 }
1140 }
1141
1142 KMainWindow::changeEvent(event);
1143 }
1144
focusNextPrevChild(bool)1145 bool MainWindow::focusNextPrevChild(bool)
1146 {
1147 return false;
1148 }
1149
toggleWindowState()1150 void MainWindow::toggleWindowState()
1151 {
1152 if (m_isWayland) {
1153 auto message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.plasmashell"),
1154 QStringLiteral("/StrutManager"),
1155 QStringLiteral("org.kde.PlasmaShell.StrutManager"),
1156 QStringLiteral("availableScreenRect"));
1157 message.setArguments({QGuiApplication::screens().at(getScreen())->name()});
1158 QDBusPendingCall call = QDBusConnection::sessionBus().asyncCall(message);
1159 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
1160
1161 QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [=]() {
1162 QDBusPendingReply<QRect> reply = *watcher;
1163 m_availableScreenRect = reply.isValid() ? reply.value() : QRect();
1164 setWindowGeometry(Settings::width(), Settings::height(), Settings::position());
1165 watcher->deleteLater();
1166 });
1167
1168 _toggleWindowState();
1169 } else {
1170 _toggleWindowState();
1171 }
1172 }
1173
_toggleWindowState()1174 void MainWindow::_toggleWindowState()
1175 {
1176 bool visible = isVisible();
1177
1178 if (visible && KWindowSystem::activeWindow() != winId() && Settings::keepOpen()) {
1179 // Window is open but doesn't have focus; it's set to stay open
1180 // regardless of focus loss.
1181
1182 if (Settings::toggleToFocus()) {
1183 // The open/retract action is set to focus the window when it's
1184 // open but lacks focus. The following will cause it to receive
1185 // focus, and in an environment with multiple virtual desktops
1186 // will also cause the window manager to switch to the virtual
1187 // desktop the window resides on.
1188
1189 KWindowSystem::activateWindow(winId());
1190 KWindowSystem::forceActiveWindow(winId());
1191
1192 return;
1193 } else if (!Settings::showOnAllDesktops() && KWindowInfo(winId(), NET::WMDesktop).desktop() != KWindowSystem::currentDesktop()) {
1194 // The open/restrict action isn't set to focus the window, but
1195 // the window is currently on another virtual desktop (the option
1196 // to show it on all of them is disabled), so closing it doesn't
1197 // make sense and we're opting to show it instead to avoid
1198 // requiring the user to invoke the action twice to get to see
1199 // Yakuake. Just forcing focus would cause the window manager to
1200 // switch to the virtual desktop the window currently resides on,
1201 // so move the window to the current desktop before doing so.
1202
1203 KWindowSystem::setOnDesktop(winId(), KWindowSystem::currentDesktop());
1204
1205 KWindowSystem::activateWindow(winId());
1206 KWindowSystem::forceActiveWindow(winId());
1207
1208 return;
1209 }
1210 }
1211
1212 #if HAVE_X11
1213 if (!Settings::useWMAssist() && m_kwinAssistPropSet)
1214 kwinAssistPropCleanup();
1215
1216 if (m_isX11 && Settings::useWMAssist() && KWindowSystem::compositingActive())
1217 kwinAssistToggleWindowState(visible);
1218 else
1219 #endif
1220 if (!m_isWayland) {
1221 xshapeToggleWindowState(visible);
1222 } else {
1223 if (visible) {
1224 sharedPreHideWindow();
1225
1226 hide();
1227
1228 sharedAfterHideWindow();
1229 } else {
1230 sharedPreOpenWindow();
1231 if (KWindowEffects::isEffectAvailable(KWindowEffects::Slide)) {
1232 KWindowEffects::slideWindow(windowHandle(), KWindowEffects::TopEdge);
1233 }
1234
1235 show();
1236
1237 sharedAfterOpenWindow();
1238 }
1239 }
1240 }
1241
1242 #if HAVE_X11
kwinAssistToggleWindowState(bool visible)1243 void MainWindow::kwinAssistToggleWindowState(bool visible)
1244 {
1245 bool gotEffect = false;
1246
1247 Display *display = QX11Info::display();
1248 Atom atom = XInternAtom(display, "_KDE_SLIDE", false);
1249 int count;
1250 Atom *list = XListProperties(display, DefaultRootWindow(display), &count);
1251
1252 if (list != nullptr) {
1253 gotEffect = (std::find(list, list + count, atom) != list + count);
1254
1255 XFree(list);
1256 }
1257
1258 if (gotEffect) {
1259 Atom atom = XInternAtom(display, "_KDE_SLIDE", false);
1260
1261 if (Settings::frames() > 0) {
1262 QVarLengthArray<long, 1024> data(4);
1263
1264 data[0] = 0;
1265 data[1] = 1;
1266 data[2] = Settings::frames() * 10;
1267 data[3] = Settings::frames() * 10;
1268
1269 XChangeProperty(display, winId(), atom, atom, 32, PropModeReplace, reinterpret_cast<unsigned char *>(data.data()), data.size());
1270
1271 m_kwinAssistPropSet = true;
1272 } else
1273 XDeleteProperty(display, winId(), atom);
1274
1275 if (visible) {
1276 sharedPreHideWindow();
1277
1278 hide();
1279
1280 sharedAfterHideWindow();
1281 } else {
1282 sharedPreOpenWindow();
1283
1284 show();
1285
1286 sharedAfterOpenWindow();
1287 }
1288
1289 return;
1290 }
1291
1292 // Fall back to legacy animation strategy if kwin doesn't have the
1293 // effect loaded.
1294 xshapeToggleWindowState(visible);
1295 }
1296
kwinAssistPropCleanup()1297 void MainWindow::kwinAssistPropCleanup()
1298 {
1299 if (!QX11Info::isPlatformX11())
1300 return;
1301
1302 Display *display = QX11Info::display();
1303 Atom atom = XInternAtom(display, "_KDE_SLIDE", false);
1304
1305 XDeleteProperty(display, winId(), atom);
1306
1307 m_kwinAssistPropSet = false;
1308 }
1309 #endif
1310
xshapeToggleWindowState(bool visible)1311 void MainWindow::xshapeToggleWindowState(bool visible)
1312 {
1313 if (m_animationTimer.isActive())
1314 return;
1315
1316 if (visible) {
1317 sharedPreHideWindow();
1318
1319 m_animationFrame = Settings::frames();
1320
1321 connect(&m_animationTimer, SIGNAL(timeout()), this, SLOT(xshapeRetractWindow()));
1322 m_animationTimer.start();
1323 } else {
1324 m_animationFrame = 0;
1325
1326 connect(&m_animationTimer, SIGNAL(timeout()), this, SLOT(xshapeOpenWindow()));
1327 m_animationTimer.start();
1328 }
1329 }
1330
xshapeOpenWindow()1331 void MainWindow::xshapeOpenWindow()
1332 {
1333 if (m_animationFrame == 0) {
1334 sharedPreOpenWindow();
1335
1336 show();
1337
1338 sharedAfterOpenWindow();
1339 }
1340
1341 if (m_animationFrame == Settings::frames()) {
1342 m_animationTimer.stop();
1343 m_animationTimer.disconnect();
1344
1345 m_titleBar->move(0, height() - m_titleBar->height());
1346 updateMask();
1347 } else {
1348 int maskHeight = m_animationStepSize * m_animationFrame;
1349
1350 QRegion newMask = m_titleBar->mask();
1351 newMask.translate(0, maskHeight);
1352 newMask += QRegion(0, 0, width(), maskHeight);
1353
1354 m_titleBar->move(0, maskHeight);
1355 setMask(newMask);
1356
1357 m_animationFrame++;
1358 }
1359 }
1360
xshapeRetractWindow()1361 void MainWindow::xshapeRetractWindow()
1362 {
1363 if (m_animationFrame == 0) {
1364 m_animationTimer.stop();
1365 m_animationTimer.disconnect();
1366
1367 hide();
1368
1369 sharedAfterHideWindow();
1370 } else {
1371 m_titleBar->move(0, m_titleBar->y() - m_animationStepSize);
1372 setMask(QRegion(mask()).translated(0, -m_animationStepSize));
1373
1374 --m_animationFrame;
1375 }
1376 }
1377
sharedPreOpenWindow()1378 void MainWindow::sharedPreOpenWindow()
1379 {
1380 applyWindowGeometry();
1381
1382 updateUseTranslucency();
1383
1384 if (Settings::pollMouse())
1385 toggleMousePoll(false);
1386 if (Settings::rememberFullscreen())
1387 setFullScreen(m_isFullscreen);
1388 }
1389
sharedAfterOpenWindow()1390 void MainWindow::sharedAfterOpenWindow()
1391 {
1392 if (!Settings::firstRun())
1393 KWindowSystem::forceActiveWindow(winId());
1394
1395 connect(qGuiApp, &QGuiApplication::focusWindowChanged, this, &MainWindow::wmActiveWindowChanged);
1396
1397 applyWindowProperties();
1398
1399 #if HAVE_KWAYLAND
1400 initWaylandSurface();
1401 #endif
1402
1403 Q_EMIT windowOpened();
1404 }
1405
sharedPreHideWindow()1406 void MainWindow::sharedPreHideWindow()
1407 {
1408 disconnect(qGuiApp, &QGuiApplication::focusWindowChanged, this, &MainWindow::wmActiveWindowChanged);
1409 }
1410
sharedAfterHideWindow()1411 void MainWindow::sharedAfterHideWindow()
1412 {
1413 if (Settings::pollMouse())
1414 toggleMousePoll(true);
1415
1416 #if HAVE_KWAYLAND
1417 delete m_plasmaShellSurface;
1418 m_plasmaShellSurface = nullptr;
1419 #endif
1420
1421 Q_EMIT windowClosed();
1422 }
1423
activate()1424 void MainWindow::activate()
1425 {
1426 KWindowSystem::activateWindow(winId());
1427 }
1428
toggleMousePoll(bool poll)1429 void MainWindow::toggleMousePoll(bool poll)
1430 {
1431 if (poll)
1432 m_mousePoller.start(Settings::pollInterval());
1433 else
1434 m_mousePoller.stop();
1435 }
1436
pollMouse()1437 void MainWindow::pollMouse()
1438 {
1439 QPoint pos = QCursor::pos();
1440 QRect workArea = getDesktopGeometry();
1441
1442 int windowX = workArea.x() + workArea.width() * Settings::position() * (100 - Settings::width()) / 10000;
1443 int windowWidth = workArea.width() * Settings::width() / 100;
1444
1445 if (pos.y() == 0 && pos.x() >= windowX && pos.x() <= (windowX + windowWidth))
1446 toggleWindowState();
1447 }
1448
setKeepOpen(bool keepOpen)1449 void MainWindow::setKeepOpen(bool keepOpen)
1450 {
1451 if (Settings::keepOpen() != keepOpen) {
1452 Settings::setKeepOpen(keepOpen);
1453 Settings::self()->save();
1454
1455 applyWindowProperties();
1456 }
1457
1458 actionCollection()->action(QStringLiteral("keep-open"))->setChecked(keepOpen);
1459 m_titleBar->setFocusButtonState(keepOpen);
1460 }
1461
setFullScreen(bool state)1462 void MainWindow::setFullScreen(bool state)
1463 {
1464 if (isVisible())
1465 m_isFullscreen = state;
1466 if (state) {
1467 setWindowState(windowState() | Qt::WindowFullScreen);
1468 setWindowGeometry(100, 100, Settings::position());
1469 } else {
1470 setWindowState(windowState() & ~Qt::WindowFullScreen);
1471 setWindowGeometry(Settings::width(), Settings::height(), Settings::position());
1472 }
1473 }
1474
getScreen()1475 int MainWindow::getScreen()
1476 {
1477 if (!Settings::screen() || Settings::screen() > QGuiApplication::screens().length()) {
1478 // Right after unplugging an external monitor and the Yakuake window was on
1479 // that monitor, QGuiApplication::screenAt() can return nullptr so we fallback on
1480 // the first monitor.
1481 QScreen *screen = QGuiApplication::screenAt(QCursor::pos());
1482 return screen ? QGuiApplication::screens().indexOf(screen) : 0;
1483 } else {
1484 return Settings::screen() - 1;
1485 }
1486 }
1487
getScreenGeometry()1488 QRect MainWindow::getScreenGeometry()
1489 {
1490 QScreen *screen = QGuiApplication::screens().at(getScreen());
1491 QRect screenGeometry = screen->geometry();
1492 screenGeometry.moveTo(screenGeometry.topLeft() / screen->devicePixelRatio());
1493 return screenGeometry;
1494 }
1495
getDesktopGeometry()1496 QRect MainWindow::getDesktopGeometry()
1497 {
1498 QRect screenGeometry = getScreenGeometry();
1499
1500 QAction *action = actionCollection()->action(QStringLiteral("view-full-screen"));
1501
1502 if (action->isChecked())
1503 return screenGeometry;
1504
1505 if (m_isWayland) {
1506 // on Wayland it's not possible to get the work area from KWindowSystem
1507 // but plasmashell provides this through dbus
1508 return m_availableScreenRect.isValid() ? m_availableScreenRect : screenGeometry;
1509 }
1510
1511 if (QGuiApplication::screens().count() > 1) {
1512 const QList<WId> allWindows = KWindowSystem::windows();
1513 QList<WId> offScreenWindows;
1514
1515 QListIterator<WId> i(allWindows);
1516
1517 while (i.hasNext()) {
1518 WId windowId = i.next();
1519
1520 if (KWindowSystem::hasWId(windowId)) {
1521 KWindowInfo windowInfo = KWindowInfo(windowId, NET::WMDesktop | NET::WMGeometry, NET::WM2ExtendedStrut);
1522
1523 // If windowInfo is valid and the window is located at the same (current)
1524 // desktop with the yakuake window...
1525 if (windowInfo.valid() && windowInfo.isOnCurrentDesktop()) {
1526 NETExtendedStrut strut = windowInfo.extendedStrut();
1527
1528 // Get the area covered by each strut.
1529 QRect topStrut(strut.top_start, 0, strut.top_end - strut.top_start, strut.top_width);
1530 QRect bottomStrut(strut.bottom_start,
1531 screenGeometry.bottom() - strut.bottom_width,
1532 strut.bottom_end - strut.bottom_start,
1533 strut.bottom_width);
1534 QRect leftStrut(0, strut.left_start, strut.left_width, strut.left_end - strut.left_start);
1535 QRect rightStrut(screenGeometry.right() - strut.right_width, strut.right_start, strut.right_width, strut.right_end - strut.right_start);
1536
1537 // If the window has no strut, no need to bother further.
1538 if (topStrut.isEmpty() && bottomStrut.isEmpty() && leftStrut.isEmpty() && rightStrut.isEmpty())
1539 continue;
1540
1541 // If any of the strut and the window itself intersects with our screen geometry,
1542 // it will be correctly handled by workArea(). If the window doesn't intersect
1543 // with our screen geometry it's most likely a plasma panel and can/should be
1544 // ignored
1545 if ((topStrut.intersects(screenGeometry) || bottomStrut.intersects(screenGeometry) || leftStrut.intersects(screenGeometry)
1546 || rightStrut.intersects(screenGeometry))
1547 && windowInfo.geometry().intersects(screenGeometry)) {
1548 continue;
1549 }
1550
1551 // This window has a strut on the same desktop as us but which does not cover our screen
1552 // geometry. It should be ignored, otherwise the returned work area will wrongly include
1553 // the strut.
1554 offScreenWindows << windowId;
1555 }
1556 }
1557 }
1558
1559 return KWindowSystem::workArea(offScreenWindows).intersected(screenGeometry);
1560 }
1561
1562 return KWindowSystem::workArea();
1563 }
1564
whatsThis()1565 void MainWindow::whatsThis()
1566 {
1567 QWhatsThis::enterWhatsThisMode();
1568 }
1569
showFirstRunDialog()1570 void MainWindow::showFirstRunDialog()
1571 {
1572 if (!m_firstRunDialog) {
1573 m_firstRunDialog = new FirstRunDialog(this);
1574
1575 connect(m_firstRunDialog, &QDialog::finished, this, &MainWindow::firstRunDialogFinished);
1576 connect(m_firstRunDialog, &QDialog::accepted, this, &MainWindow::firstRunDialogOk);
1577 }
1578
1579 m_firstRunDialog->show();
1580 }
1581
firstRunDialogFinished()1582 void MainWindow::firstRunDialogFinished()
1583 {
1584 Settings::setFirstRun(false);
1585 Settings::self()->save();
1586
1587 m_firstRunDialog->deleteLater();
1588
1589 KWindowSystem::forceActiveWindow(winId());
1590 }
1591
firstRunDialogOk()1592 void MainWindow::firstRunDialogOk()
1593 {
1594 QAction *action = static_cast<QAction *>(actionCollection()->action(QStringLiteral("toggle-window-state")));
1595
1596 KGlobalAccel::self()->setShortcut(action, QList<QKeySequence>() << m_firstRunDialog->keySequence(), KGlobalAccel::NoAutoloading);
1597
1598 actionCollection()->writeSettings();
1599 }
1600
updateUseTranslucency()1601 void MainWindow::updateUseTranslucency()
1602 {
1603 m_useTranslucency = (Settings::translucency() && KWindowSystem::compositingActive());
1604 }
1605
updateTrayTooltip()1606 void MainWindow::updateTrayTooltip()
1607 {
1608 if (!m_notifierItem) {
1609 return;
1610 }
1611
1612 auto *action = actionCollection()->action(QStringLiteral("toggle-window-state"));
1613 const QList<QKeySequence> &shortcuts = KGlobalAccel::self()->shortcut(action);
1614 if (!shortcuts.isEmpty()) {
1615 const QString shortcut(shortcuts.first().toString(QKeySequence::NativeText));
1616 m_notifierItem->setToolTip(QStringLiteral("yakuake"), QStringLiteral("Yakuake"), xi18nc("@info", "Press <shortcut>%1</shortcut> to open", shortcut));
1617 }
1618 }
1619