// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2007 Konrad Twardowski

#include "kshutdown.h"

#include "commandline.h"
#include "config.h"
#include "log.h"
#include "mainwindow.h"
#include "password.h"
#include "progressbar.h"
#include "utils.h"
#include "actions/bootentry.h"
#include "actions/lock.h"

#include <QDebug>
#include <QFormLayout>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>

#ifdef Q_OS_WIN32
	#ifndef WIN32_LEAN_AND_MEAN
		#define WIN32_LEAN_AND_MEAN
	#endif // WIN32_LEAN_AND_MEAN
	#include <windows.h>
	#include <powrprof.h>
#else
	#include <QThread>
#endif // Q_OS_WIN32

// PowerAction

// public

PowerAction::PowerAction(const QString &text, const QString &iconName, const QString &id) :
	Action(text, iconName, id)
{
	setCanBookmark(true);
}

#ifdef QT_DBUS_LIB
QDBusInterface *PowerAction::getHalDeviceInterface() {
	if (m_halDeviceInterface == nullptr) {
		// DOC: http://people.freedesktop.org/~dkukawka/hal-spec-git/hal-spec.html
		m_halDeviceInterface = new QDBusInterface(
			"org.freedesktop.Hal",
			"/org/freedesktop/Hal/devices/computer",
			"org.freedesktop.Hal.Device",
			QDBusConnection::systemBus()
		);
	}
	if (m_halDeviceInterface->isValid())
		qDebug() << "HAL backend found...";
	else
		qCritical() << "HAL backend NOT found: " << m_halDeviceInterface->lastError().message();

	return m_halDeviceInterface;
}

QDBusInterface *PowerAction::getHalDeviceSystemPMInterface() {
	if (m_halDeviceSystemPMInterface == nullptr) {
		// DOC: http://people.freedesktop.org/~dkukawka/hal-spec-git/hal-spec.html
		m_halDeviceSystemPMInterface = new QDBusInterface(
			"org.freedesktop.Hal",
			"/org/freedesktop/Hal/devices/computer",
			"org.freedesktop.Hal.Device.SystemPowerManagement",
			QDBusConnection::systemBus()
		);
	}
	if (!m_halDeviceSystemPMInterface->isValid())
		qCritical() << "No valid org.freedesktop.Hal interface found: " << m_halDeviceSystemPMInterface->lastError().message();

	return m_halDeviceSystemPMInterface;
}

QDBusInterface *PowerAction::getUPowerInterface() {
	// DOC: http://upower.freedesktop.org/docs/UPower.html
	if (m_upowerInterface == nullptr) {
		m_upowerInterface = new QDBusInterface(
			"org.freedesktop.UPower",
			"/org/freedesktop/UPower",
			"org.freedesktop.UPower",
			QDBusConnection::systemBus()
		);

		if (!m_upowerInterface->isValid()) {
			// HACK: wait for service start
			// BUG: https://sourceforge.net/p/kshutdown/bugs/34/
			qDebug() << "UPower: Trying again...";
			delete m_upowerInterface;
			m_upowerInterface = new QDBusInterface(
				"org.freedesktop.UPower",
				"/org/freedesktop/UPower",
				"org.freedesktop.UPower",
				QDBusConnection::systemBus()
			);
		}

		if (m_upowerInterface->isValid())
			qDebug() << "UPower backend found...";
		else
			qDebug() << "UPower backend NOT found...";
	}
	
	return m_upowerInterface;
}
#endif // QT_DBUS_LIB

bool PowerAction::onAction() {
#ifdef Q_OS_WIN32
	BOOL hibernate = (m_methodName == "Hibernate");
	BOOL result = ::SetSuspendState(hibernate, TRUE, FALSE);
	if (result == 0) {
		setLastError();

		return false;
	}

	return true;
#elif defined(Q_OS_HAIKU)
	return false;
#else
	// lock screen before hibernate/suspend
	if (Config::lockScreenBeforeHibernate) {
		LockAction::self()->activate();

		// HACK: wait for screensaver
		QThread::sleep(1);
	}
	
	#ifdef QT_DBUS_LIB
	QDBusInterface *login = getLoginInterface();
	QDBusInterface *upower = getUPowerInterface();
	
	QDBusError error;
	
	// systemd

	if (login->isValid()) {
// TODO: test interactivity option
		login->call(m_methodName, false);
		
		error = login->lastError();
	}

	// UPower

	else if (upower->isValid()) {
		upower->call(m_methodName);
		
		error = upower->lastError();
	}

	// HAL (lazy init)

	else {
		QDBusInterface *hal = getHalDeviceSystemPMInterface();
		if (hal->isValid()) {
			if (m_methodName == "Suspend")
				hal->call(m_methodName, 0);
			else
				hal->call(m_methodName); // no args
		
			error = hal->lastError();
		}
	}
	
	if (
		(error.type() != QDBusError::NoError) &&
		// ignore missing reply after resume from suspend/hibernation
		(error.type() != QDBusError::NoReply) &&
		// ignore unknown errors
		(error.type() != QDBusError::Other)
	) {
		m_error = error.message();

		return false;
	}
	#endif // QT_DBUS_LIB

	return true;
#endif // Q_OS_WIN32
}

// protected

bool PowerAction::isAvailable(const PowerActionType feature) {
#ifdef Q_OS_WIN32
	if (feature == PowerActionType::Hibernate)
		return ::IsPwrHibernateAllowed();

	if (feature == PowerActionType::Suspend)
		return ::IsPwrSuspendAllowed();

	return false;
#elif defined(Q_OS_HAIKU)
	Q_UNUSED(feature)

	return false;
#else
	#ifdef QT_DBUS_LIB
	QDBusInterface *login = getLoginInterface();
	if (login->isValid()) {
		switch (feature) {
			case PowerActionType::Suspend: {
				QDBusReply<QString> reply = login->call("CanSuspend");
				qDebug() << "systemd: CanSuspend: " << reply;
				
				// HACK: Sometimes "challenge" is returned shortly after a Desktop Environment start.
				// DOC: https://www.freedesktop.org/wiki/Software/systemd/logind/
// TODO: update actions after "CanXX" change
				return reply.isValid() && ((reply.value() == "yes") || (reply.value() == "challenge"));
			} break;
			case PowerActionType::Hibernate: {
				QDBusReply<QString> reply = login->call("CanHibernate");
				qDebug() << "systemd: CanHibernate: " << reply;
				
				return reply.isValid() && ((reply.value() == "yes") || (reply.value() == "challenge"));
			} break;
		}
	}

	// Use the UPower backend if available
	QDBusInterface *upower = getUPowerInterface();
	if (upower->isValid()) {
		switch (feature) {
			case PowerActionType::Suspend:
				return upower->property("CanSuspend").toBool();
			case PowerActionType::Hibernate:
				return upower->property("CanHibernate").toBool();
		}
	}

	// Use the legacy HAL backend if systemd or UPower not available
	QDBusInterface *hal = getHalDeviceInterface();
	if (hal->isValid()) {
		// try old property name as well, for backward compat.
		QList<QString> featureNames;
		switch (feature) {
			case PowerActionType::Suspend:
				featureNames.append("power_management.can_suspend");
				featureNames.append("power_management.can_suspend_to_ram");
				break;
			case PowerActionType::Hibernate:
				featureNames.append("power_management.can_hibernate");
				featureNames.append("power_management.can_suspend_to_disk");
				break;
		}
		
		QDBusReply<bool> reply;
		
		// Try the alternative feature names in order; if we get a valid answer, return it
		for (const QString &i : featureNames) {
			reply = hal->call("GetProperty", i);

			if (reply.isValid())
				return reply.value();
		}

		qCritical() << reply.error();
	}
	#else
	Q_UNUSED(feature)
	#endif // QT_DBUS_LIB
	
	return false;
#endif // Q_OS_WIN32
}

// HibernateAction

// public

HibernateAction::HibernateAction() :
	PowerAction(
		i18n("Hibernate"),
		"system-suspend-hibernate", "hibernate"
) {
	m_methodName = "Hibernate";
	if (!isAvailable(PowerActionType::Hibernate))
		setEnabled(false, i18n("Cannot hibernate computer"));

	setCommandLineOption({ "H", "hibernate" });

	uiAction()->setToolTip(
		i18n("Hibernate Computer") + "\n" +
		i18n("Save the contents of RAM to disk\nthen turn off the computer.")
	);
}

// SuspendAction

// public

SuspendAction::SuspendAction() :
	PowerAction(
		i18n("Sleep"), // NOTE: Matches https://phabricator.kde.org/D19184
		"system-suspend", "suspend") {
	m_methodName = "Suspend";
	if (!isAvailable(PowerActionType::Suspend))
		setEnabled(false, i18n("Cannot suspend computer"));

	setCommandLineOption({ "S", "sleep", "suspend" });

	uiAction()->setToolTip(
		i18n("Suspend Computer") + "\n" +
		i18n("Enter in a low-power state mode.")
	);
}

// ExitAction

// public

ExitAction::ExitAction(const QString &text, const QString &iconName, const QString &id, const UShutdownType type) :
	Action(text, iconName, id),
	m_type(type) {
	
	setCanBookmark(true);

	m_forceCheckBox = new QCheckBox(i18n("Do not save session / Force shutdown"));
	m_forceCheckBox->setObjectName("force");

	connect(m_forceCheckBox, &QCheckBox::clicked, [this]() {
		if (m_forceCheckBox->isChecked()) {
			UMessageBuilder message(UMessageBuilder::Type::Question);
			message.okText(i18n("Enable"));
			message.showKey("KShutdown Force Exit");
			message.plainText(i18n("Are you sure you want to enable this option?\n\nData in all unsaved documents will be lost!"));

			if (! message.exec(m_forceCheckBox))
				m_forceCheckBox->setChecked(false);
		}
	});

// TODO: clean up kshutdown.cpp, move this to LogoutAction
	#ifdef QT_DBUS_LIB
	if (m_kdeSessionInterface == nullptr) {
		m_kdeSessionInterface = new QDBusInterface(
			"org.kde.ksmserver",
			"/KSMServer",
			"org.kde.KSMServerInterface"
		);
		QDBusReply<bool> reply = m_kdeSessionInterface->call("canShutdown");
		m_kdeShutDownAvailable = reply.isValid() && reply.value();

		if (Utils::isKDEFullSession() && !m_kdeShutDownAvailable && (type != U_SHUTDOWN_TYPE_LOGOUT))
			qDebug() << "No org.kde.ksmserver API available";
	}
	#endif // QT_DBUS_LIB
}

void ExitAction::initContainerWidget() {
	makeFormLayout();

	#ifdef Q_OS_WIN32
	auto *layout = getContainerForm();
	layout->addRow(m_forceCheckBox);
	#endif // Q_OS_WIN32
}

bool ExitAction::onAction() {
	m_totalExit = true;
#ifdef Q_OS_WIN32
	UINT flags = 0;
	switch (m_type) {
		case U_SHUTDOWN_TYPE_LOGOUT:
			flags = EWX_LOGOFF;
			break;
		case U_SHUTDOWN_TYPE_REBOOT:
			flags = EWX_REBOOT;
			break;
		case U_SHUTDOWN_TYPE_HALT:
			flags = EWX_POWEROFF;
			break;
		default:
			qCritical() << "WTF? Unknown m_type: " << m_type;

			return false; // do nothing
	}

	// adjust privileges

	HANDLE hToken = 0;
	if (::OpenProcessToken(::GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {
		TOKEN_PRIVILEGES tp;
		if (!::LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tp.Privileges[0].Luid)) {
			setLastError();

			return false;
		}

		tp.PrivilegeCount = 1;
		tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
		if (!::AdjustTokenPrivileges(
			hToken,
			FALSE,
			&tp,
			sizeof(TOKEN_PRIVILEGES),
			(PTOKEN_PRIVILEGES)NULL,
			(PDWORD)NULL
		)) {
			setLastError();

			return false;
		}
/*
		if (::GetLastError() == ERROR_NOT_ALL_ASSIGNED) {
			m_error = "ERROR_NOT_ALL_ASSIGNED";

			return false;
		}
*/
	}
	else {
		setLastError();

		return false;
	}

/*
	OSVERSIONINFO os;
	::ZeroMemory(&os, sizeof(OSVERSIONINFO));
	os.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
	if (
		::GetVersionEx(&os) &&
		((os.dwMajorVersion == 5) && (os.dwMinorVersion == 1)) // win xp
	) {
	}
*/
		
	if (
		(m_type == U_SHUTDOWN_TYPE_HALT) ||
		(m_type == U_SHUTDOWN_TYPE_REBOOT)
	) {
		BOOL bForceAppsClosed = m_forceCheckBox->isChecked() ? TRUE : FALSE;

		BOOL bRebootAfterShutdown = (m_type == U_SHUTDOWN_TYPE_REBOOT) ? TRUE : FALSE;
		
		if (::InitiateSystemShutdown(
			NULL, NULL, 0, // unused
			bForceAppsClosed,
			bRebootAfterShutdown
		) == 0) {
// TODO: handle ERROR_NOT_READY
			if (::GetLastError() == ERROR_MACHINE_LOCKED) {
				
				bForceAppsClosed = TRUE;
				
				if (::InitiateSystemShutdown(
					NULL, NULL, 0, // unused
					bForceAppsClosed,
					bRebootAfterShutdown
				) == 0) {
					setLastError();
					
					return false;
				}
			}
			else {
				setLastError();
				
				return false;
			}
		}
	}
	else {
		flags += EWX_FORCEIFHUNG;
		if (m_forceCheckBox->isChecked()) {
			flags += EWX_FORCE;
		}

		if (::ExitWindowsEx(flags, SHTDN_REASON_MAJOR_APPLICATION | SHTDN_REASON_FLAG_PLANNED) == 0) {
			setLastError();

			return false;
		}
	}

	return true;

#elif defined(Q_OS_HAIKU)

	if (m_type == U_SHUTDOWN_TYPE_REBOOT) {
		QStringList args;
		args << "-r";
		
		if (Utils::run("shutdown", args)) {
			// HACK: fixes "Asking kshutdown to quit" message
			QApplication::quit();

			return true;
		}
	}
	else if (m_type == U_SHUTDOWN_TYPE_HALT) {
		QStringList args;
		
		if (Utils::run("shutdown", args)) {
			// HACK: fixes "Asking kshutdown to quit" message
			QApplication::quit();

			return true;
		}
	}
	
	return false;
	
#else

	if (strategyManager()->run())
		return true;

	// GNOME Shell, Unity

	if (Utils::isGNOME() || Utils::isUnity()) {
		if (m_type == U_SHUTDOWN_TYPE_LOGOUT) {
			QStringList args;
// TODO: --force
			args << "--logout";
			args << "--no-prompt";

			if (Utils::run("gnome-session-quit", args))
				return true;
		}
	}

	// Trinity

	else if (Utils::isTrinity()) {
		if (m_type == U_SHUTDOWN_TYPE_LOGOUT) {
			QStringList args;
			args << "ksmserver";
			args << "ksmserver"; // not a paste bug
			args << "logout";

			// DOC: http://api.kde.org/4.x-api/kde-workspace-apidocs/plasma-workspace/libkworkspace/html/namespaceKWorkSpace.html
			args << "0"; // no confirm
			args << "0"; // logout
			args << "2"; // force now

			if (Utils::run("dcop", args))
				return true;
		}
	}

	// Xfce

/* TODO: ?
	else if (Utils::xfce) {
		switch (m_type) {
			case U_SHUTDOWN_TYPE_LOGOUT: {
			} break;

			case U_SHUTDOWN_TYPE_REBOOT: {
				QStringList args;
				args << "--reboot";
				if (Utils::run("xfce4-session-logout", args))
					return true;
			} break;
			case U_SHUTDOWN_TYPE_HALT: {
				QStringList args;
				args << "--halt";
				if (Utils::run("xfce4-session-logout", args))
					return true;
			} break;
			default:
				qCritical() << "WTF? Unknown m_type: " << m_type;

				return false; // do nothing
*/

	// native KDE shutdown API

	#ifdef QT_DBUS_LIB
	if (
		Utils::isKDEFullSession() &&
		(m_kdeShutDownAvailable || (m_type == U_SHUTDOWN_TYPE_LOGOUT))
	) {
		/*QDBusReply<void> reply = */m_kdeSessionInterface->call(
			"logout",
			0, // KWorkSpace::ShutdownConfirmNo
			m_type,
			2 // KWorkSpace::ShutdownModeForceNow
		);
/* TODO: error checking?
		if (reply.isValid())
			return true;
*/

		return true;
	}
	#endif // QT_DBUS_LIB

	// Openbox

	// NOTE: Put this after m_kdeSessionInterface call
	// because in case of Openbox/KDE session KShutdown
	// will detect both Openbox and KDE at the same time.
	// ISSUE: https://sourceforge.net/p/kshutdown/feature-requests/20/
	if (Utils::isOpenbox()) {
		if (m_type == U_SHUTDOWN_TYPE_LOGOUT) {
			QStringList args;
			args << "--exit";

			if (Utils::run("openbox", args))
				return true;
		}
	}

	// fallback to systemd/logind/ConsoleKit/HAL/whatever
	
	#ifdef QT_DBUS_LIB

	// try systemd/logind

	QDBusInterface *login = getLoginInterface();
	if ((m_type == U_SHUTDOWN_TYPE_HALT) && login->isValid()) {
// TODO: check CanPowerOff/CanReboot
		QDBusReply<void> reply = login->call("PowerOff", false);

		if (reply.isValid())
			return true;
	}
	else if ((m_type == U_SHUTDOWN_TYPE_REBOOT) && login->isValid()) {
		QDBusReply<void> reply = login->call("Reboot", false);

		if (reply.isValid())
			return true;
	}

	// try ConsoleKit
	
	if ((m_type == U_SHUTDOWN_TYPE_HALT) && (m_consoleKitInterface != nullptr) && m_consoleKitInterface->isValid()) {
		QDBusReply<void> reply = m_consoleKitInterface->call("Stop");

		if (reply.isValid())
			return true;
	}
	else if ((m_type == U_SHUTDOWN_TYPE_REBOOT) && (m_consoleKitInterface != nullptr) && m_consoleKitInterface->isValid()) {
		QDBusReply<void> reply = m_consoleKitInterface->call("Restart");

		if (reply.isValid())
			return true;
	}

	// try HAL (lazy init)
	
	QDBusInterface *hal = PowerAction::getHalDeviceSystemPMInterface();

	if ((m_type == U_SHUTDOWN_TYPE_HALT) && hal->isValid()) {
		QDBusReply<int> reply = hal->call("Shutdown");

		if (reply.isValid())
			return true;
	}
	else if ((m_type == U_SHUTDOWN_TYPE_REBOOT) && hal->isValid()) {
		QDBusReply<int> reply = hal->call("Reboot");

		if (reply.isValid())
			return true;
	}
	#endif // QT_DBUS_LIB
	
	// show error
	
	return unsupportedAction();
#endif // Q_OS_WIN32
}

// protected

#ifdef QT_DBUS_LIB
void ExitAction::checkAvailable(const QString &consoleKitName) {
	bool available = false;
	QString error = "";
// TODO: clean up; return bool
// TODO: win32: check if shutdown/reboot action is available

	// try systemd

	QDBusInterface *login = getLoginInterface();
	if (login->isValid()) {
// TODO: CanPowerOff, etc.
		available = true;
	}

	// try ConsoleKit

	if (!consoleKitName.isEmpty()) {
		if (m_consoleKitInterface == nullptr) {
			m_consoleKitInterface = new QDBusInterface(
				"org.freedesktop.ConsoleKit",
				"/org/freedesktop/ConsoleKit/Manager",
				"org.freedesktop.ConsoleKit.Manager",
				QDBusConnection::systemBus()
			);
		}
		if (!available && !m_consoleKitInterface->isValid()) {
			// HACK: wait for service start
			qDebug() << "ConsoleKit: Trying again...";
			delete m_consoleKitInterface;
			m_consoleKitInterface = new QDBusInterface(
				"org.freedesktop.ConsoleKit",
				"/org/freedesktop/ConsoleKit/Manager",
				"org.freedesktop.ConsoleKit.Manager",
				QDBusConnection::systemBus()
			);
		}
		if (m_consoleKitInterface->isValid()) {
			QDBusReply<bool> reply = m_consoleKitInterface->call(consoleKitName);
			if (!reply.isValid()) {
				qCritical() << reply.error().message();
				if (error.isEmpty())
					error = reply.error().name();
			}
			else {
				available = reply.value();
			}
		}
		else {
			qCritical() << "ConsoleKit Error: " << m_consoleKitInterface->lastError().message();
			if (error.isEmpty())
				error = "No valid org.freedesktop.ConsoleKit interface found";
		}
	}
	
	// try HAL (lazy init)

	if (!available) {
		QDBusInterface *hal = PowerAction::getHalDeviceSystemPMInterface();
		available = hal->isValid();
	}
	
	// BUG #19 - disable only if both ConsoleKit and native KDE API is unavailable
	if (!available && !m_kdeShutDownAvailable)
		setEnabled(false, error);
}
#endif // QT_DBUS_LIB

// LogoutAction

// public

LogoutAction::LogoutAction() :
	ExitAction(
		#ifdef Q_OS_WIN32
		i18n("Log Off"),
		#else
		i18n("Logout"),
		#endif // Q_OS_WIN32
		"system-log-out", "logout", U_SHUTDOWN_TYPE_LOGOUT
) {
	QString currentUser = Utils::getUser();
	
	if (currentUser.isEmpty())
		currentUser = "?";

	uiAction()->setToolTip(
		i18n("End Session") + "\n" +
		"\n" +
		i18n("Current User: %0").arg(currentUser)
	);

	setCommandLineOption({ "l", "logoff", "logout" }, Utils::makeTitle(originalText(), i18n("End Session")));

	#ifdef Q_OS_HAIKU
	setEnabled(false, "");
	#endif // Q_OS_HAIKU

	auto sm = strategyManager();

	// by desktop environment

	#ifdef QT_DBUS_LIB
	sm->lazySessionBus(Utils::kde, "org.kde.Shutdown", "/Shutdown", "org.kde.Shutdown", "logout");
	#endif // QT_DBUS_LIB

	QString lxdeDummy = "Terminate $_LXSESSION_PID PID";
	if (Utils::lxde) {
		if (auto pid = Utils::toInt64(qEnvironmentVariable("_LXSESSION_PID"))) {
			sm->terminate(Utils::lxde, pid.value());
		}
		else {
			sm->dummy(Utils::lxde, lxdeDummy);
			setEnabled(false, "No LXDE session found");
		}
	}
	else {
		sm->dummy(Utils::lxde, lxdeDummy);
	}

	sm->program(Utils::cinnamon, "cinnamon-session-quit", { "--logout", "--no-prompt" });

// TODO: use D-Bus
	sm->program(Utils::enlightenment, "enlightenment_remote", { "-exit" });

	sm->program(Utils::mate, "mate-session-save", { "--logout" });
	sm->program(Utils::xfce, "xfce4-session-logout", { "--logout" });

	#ifdef QT_DBUS_LIB
	auto lxqtStrategy = sm->sessionBus(Utils::lxqt, "org.lxqt.session", "/LXQtSession", "org.lxqt.session", "logout");

	if (lxqtStrategy->notAvailable("canLogout"))
		setEnabled(false, "No LXQt session found");
	#endif // QT_DBUS_LIB

	// info

	sm->dummy(Utils::windows, "WinAPI ::ExitWindowsEx");

/* TODO:
	if (Utils::kde) {
		setWarningStatus(i18n("You will be asked to save your documents."));
	}
*/
}

// RebootAction

// public

RebootAction::RebootAction() :
	ExitAction(
		i18n("Restart"),
		QString(), "reboot", U_SHUTDOWN_TYPE_REBOOT
	)
	#ifdef Q_OS_LINUX
	,
// TODO: better help or example
	systemOption("system", i18n("Select an Operating System to boot (used with --reboot)"), "entry")
	#endif // Q_OS_LINUX
{
	uiAction()->setToolTip(i18n("Restart Computer"));

// TODO: Utils::fromTheme(QString &name, QString &fallbackName) #api
#ifdef KS_KF5
	// NOTE: follow the Icon Naming Specification and use "system-reboot" instead of "system-restart"
	// <http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html>
	uiAction()->setIcon(QIcon::fromTheme("system-reboot"));
#else
	if (Utils::isKDE())
		uiAction()->setIcon(QIcon::fromTheme("system-reboot"));
	// HACK: missing "system-reboot" in some icon themes
	else
		uiAction()->setIcon(QIcon::fromTheme("view-refresh"));
#endif // KS_KF5

	setCommandLineOption({ "r", "reboot", "restart" });
	#ifdef Q_OS_LINUX
	CLI::getArgs()->addOption(systemOption);
	#endif // Q_OS_LINUX

	#ifdef QT_DBUS_LIB
/* FIXME: blocking
	auto sm = strategyManager();

	if (m_kdeShutdownInterface != nullptr)
		sm->sessionBus(Utils::kde, m_kdeShutdownInterface, "logoutAndReboot");
*/
	checkAvailable("CanRestart");
	#endif // QT_DBUS_LIB

	m_bootEntryComboBox = new BootEntryComboBox();
}

void RebootAction::initContainerWidget() {
	ExitAction::initContainerWidget();

	auto *layout = getContainerForm();
	layout->addRow(i18n("System:"), m_bootEntryComboBox);

	updateBootEntryView();
}

bool RebootAction::onAction() {
	if (m_bootEntryComboBox->currentIndex() > 0) {
		BootEntry::setDefault(m_bootEntryComboBox->currentText());
	}

/* TEST:
	qDebug() << "DRY RUN";
// TODO: --dry-run CLI option
	return true;
*/
	return ExitAction::onAction();
}

bool RebootAction::onCommandLineOption() {
	#ifdef Q_OS_LINUX
	QString entry = CLI::getArgs()->value(systemOption);

	if (entry.isEmpty()) {
		m_bootEntryComboBox->setCurrentIndex(0); // default
	}
	else {
// TODO: error message or fallback if no match
		m_bootEntryComboBox->setCurrentText(entry);
	}
	#endif // Q_OS_LINUX

	return true;
}

void RebootAction::updateBootEntryView() {
	m_bootEntryComboBox->updateBootEntryList();

	bool visible = Config::bootEntriesVisibleInWindow.getBool();

	getContainerForm()->labelForField(m_bootEntryComboBox)
		->setVisible(visible);

	m_bootEntryComboBox->setVisible(visible);
}

// ShutDownAction

// public

ShutDownAction::ShutDownAction() :
	ExitAction(
		i18n("Shut Down"),
		"system-shutdown", "shutdown", U_SHUTDOWN_TYPE_HALT
) {
	uiAction()->setToolTip(i18n("Turn Off Computer"));

	setCommandLineOption({
		"h", "halt",
		"s", "shutdown"
	});

	#ifdef QT_DBUS_LIB
/* FIXME: blocking
	auto sm = strategyManager();

	if (m_kdeShutdownInterface != nullptr)
		sm->sessionBus(Utils::kde, m_kdeShutdownInterface, "logoutAndShutdown");
*/
	checkAvailable("CanStop");
	#endif // QT_DBUS_LIB
}
