diff --git a/CMakeLists.txt b/CMakeLists.txt index 54ee2aa7be6..6c9218cff5b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,8 +44,7 @@ endif() add_subdirectory(3rd-party) # Qt config -find_package(Qt5Core REQUIRED) -find_package(Qt5Network REQUIRED) +find_package(Qt5 COMPONENTS Core Network Widgets REQUIRED) execute_process(COMMAND git describe WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} diff --git a/src/client/client.cpp b/src/client/client.cpp index 152bc0d8047..50445f0299f 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -32,6 +32,7 @@ #include "cmd/start.h" #include "cmd/stop.h" #include "cmd/suspend.h" +#include "cmd/systray.h" #include "cmd/umount.h" #include "cmd/version.h" @@ -93,6 +94,7 @@ mp::Client::Client(ClientConfig& config) add_command(); add_command(); add_command(); + add_command(); add_command(); add_command(); add_command(); diff --git a/src/client/cmd/CMakeLists.txt b/src/client/cmd/CMakeLists.txt index f2d22ddfbef..8dc81d0e93a 100644 --- a/src/client/cmd/CMakeLists.txt +++ b/src/client/cmd/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright © 2017-2018 Canonical Ltd. +# Copyright © 2017-2019 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License version 3 as @@ -11,8 +11,8 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# -# Authored by: Alberto Aguirre + +set(CMAKE_AUTOMOC ON) add_library(commands STATIC animated_spinner.cpp @@ -31,6 +31,7 @@ add_library(commands STATIC start.cpp stop.cpp suspend.cpp + systray.cpp restart.cpp delete.cpp umount.cpp @@ -45,4 +46,5 @@ target_link_libraries(commands rpc Qt5::Core Qt5::Network + Qt5::Widgets yaml) diff --git a/src/client/cmd/systray.cpp b/src/client/cmd/systray.cpp new file mode 100644 index 00000000000..bd45492ea1a --- /dev/null +++ b/src/client/cmd/systray.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2019 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "systray.h" +#include "common_cli.h" + +#include +#include + +namespace mp = multipass; +namespace cmd = multipass::cmd; +using RpcMethod = mp::Rpc::Stub; + +void cmd::Systray::run(mp::ArgParser* parser) +{ + if (!QSystemTrayIcon::isSystemTrayAvailable()) + { + cerr << "System tray not supported\n"; + return command_done(ReturnCode::CommandFail); + } + + auto ret = parse_args(parser); + if (ret != ParseCode::Ok) + { + return command_done(parser->returnCodeFrom(ret)); + } + + create_actions(); + create_menu(); + tray_icon.show(); +} + +std::string cmd::Systray::name() const +{ + return "systray"; +} + +QString cmd::Systray::short_help() const +{ + return QStringLiteral("Run client in system tray"); +} + +QString cmd::Systray::description() const +{ + return QStringLiteral("Run the client in the system tray."); +} + +mp::ParseCode cmd::Systray::parse_args(mp::ArgParser* parser) +{ + return parser->commandParse(this); +} + +void cmd::Systray::create_actions() +{ + retrieving_action = tray_icon_menu.addAction("Retrieving instances..."); + about_separator = tray_icon_menu.addSeparator(); + about_action = tray_icon_menu.addAction("About"); + quit_action = tray_icon_menu.addAction("Quit"); +} + +void cmd::Systray::create_menu() +{ + tray_icon.setContextMenu(&tray_icon_menu); + + tray_icon.setIcon(QIcon{"./ubuntu-icon.png"}); + + QObject::connect(&tray_icon_menu, &QMenu::aboutToShow, [this]() { + if (failure_action.isVisible()) + { + tray_icon_menu.removeAction(&failure_action); + } + + std::unique_lock lock{worker_mutex}; + if (!worker_running) + { + worker_running = true; + worker = std::thread([this] { + for (const auto& instance_menu : instances_menus) + { + tray_icon_menu.removeAction(instance_menu->menuAction()); + } + instances_menus.clear(); + tray_icon_menu.insertAction(about_separator, retrieving_action); + + using namespace std::literals::chrono_literals; + std::this_thread::sleep_for(5s); + retrieve_all_instances(); + + std::unique_lock lock{worker_mutex}; + worker_running = false; + }); + + worker.detach(); + } + }); + + QObject::connect(quit_action, &QAction::triggered, [this]() { QCoreApplication::quit(); }); +} + +void cmd::Systray::retrieve_all_instances() +{ + auto on_success = [this](ListReply& reply) { + tray_icon_menu.removeAction(retrieving_action); + + for (const auto& instance : reply.instances()) + { + auto state = QString::fromStdString(mp::format::status_string_for(instance.instance_status())); + auto action_string = QString("%1 (%2)").arg(QString::fromStdString(instance.name())).arg(state); + + instances_menus.push_back(std::make_unique(action_string)); + + if (state != "DELETED") + { + auto shell_action = instances_menus.back()->addAction("Open shell"); + + if (state != "RUNNING") + { + shell_action->setDisabled(true); + } + + if (state != "STOPPED") + { + instances_actions.push_back(instances_menus.back()->addAction("Stop")); + QObject::connect(instances_actions.back(), &QAction::triggered, + [this]() { fmt::print("We would have stopped here\n"); }); + } + else + { + instances_menus.back()->addAction("Start"); + } + + tray_icon_menu.insertMenu(about_separator, instances_menus.back().get()); + } + } + + return ReturnCode::Ok; + }; + + auto on_failure = [this](grpc::Status& status) { + tray_icon_menu.removeAction(retrieving_action); + tray_icon_menu.insertAction(about_separator, &failure_action); + + return standard_failure_handler_for(name(), cerr, status); + }; + + ListRequest request; + dispatch(&RpcMethod::list, request, on_success, on_failure); +} diff --git a/src/client/cmd/systray.h b/src/client/cmd/systray.h new file mode 100644 index 00000000000..55c05b7094d --- /dev/null +++ b/src/client/cmd/systray.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2019 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef MULTIPASS_SYSTRAY_H +#define MULTIPASS_SYSTRAY_H + +#include + +#include +#include +#include + +#include +#include +#include + +namespace multipass +{ +namespace cmd +{ +class Systray final : public QObject, public Command +{ + Q_OBJECT +public: + using Command::Command; + void run(ArgParser* parser) override; + + std::string name() const override; + QString short_help() const override; + QString description() const override; + +private: + ParseCode parse_args(ArgParser* parser) override; + void create_actions(); + void create_menu(); + void retrieve_all_instances(); + + QSystemTrayIcon tray_icon; + QMenu tray_icon_menu; + + QAction* retrieving_action; + QAction* about_separator; + QAction* about_action; + QAction* quit_action; + QAction failure_action{"Failure retrieving instances"}; + + std::vector> instances_menus; + std::vector instances_actions; + + bool worker_running{false}; + std::mutex worker_mutex; + std::thread worker; + +signals: + void get_instances(); +}; +} // namespace cmd +} // namespace multipass +#endif // MULTIPASS_SYSTRAY_H diff --git a/src/client/main.cpp b/src/client/main.cpp index 7d1552efbd3..31351a52964 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -22,7 +22,7 @@ #include #include -#include +#include #include #include @@ -45,7 +45,7 @@ std::string get_server_address() int main(int argc, char* argv[]) { - QCoreApplication app(argc, argv); + QApplication app(argc, argv); QCoreApplication::setApplicationName("multipass"); mp::Console::setup_environment();