Skip to content

Commit

Permalink
client: Add support for running as a system tray process with icon
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Townsend committed Jan 18, 2019
1 parent e162dc1 commit 17ae950
Show file tree
Hide file tree
Showing 6 changed files with 245 additions and 7 deletions.
3 changes: 1 addition & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
2 changes: 2 additions & 0 deletions src/client/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -93,6 +94,7 @@ mp::Client::Client(ClientConfig& config)
add_command<cmd::Start>();
add_command<cmd::Stop>();
add_command<cmd::Suspend>();
add_command<cmd::Systray>();
add_command<cmd::Restart>();
add_command<cmd::Delete>();
add_command<cmd::Umount>();
Expand Down
8 changes: 5 additions & 3 deletions src/client/cmd/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -11,8 +11,8 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Authored by: Alberto Aguirre <[email protected]>

set(CMAKE_AUTOMOC ON)

add_library(commands STATIC
animated_spinner.cpp
Expand All @@ -31,6 +31,7 @@ add_library(commands STATIC
start.cpp
stop.cpp
suspend.cpp
systray.cpp
restart.cpp
delete.cpp
umount.cpp
Expand All @@ -45,4 +46,5 @@ target_link_libraries(commands
rpc
Qt5::Core
Qt5::Network
Qt5::Widgets
yaml)
162 changes: 162 additions & 0 deletions src/client/cmd/systray.cpp
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*
*/

#include "systray.h"
#include "common_cli.h"

#include <multipass/cli/argparser.h>
#include <multipass/cli/format_utils.h>

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<decltype(worker_mutex)> 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<decltype(worker_mutex)> 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<QMenu>(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);
}
73 changes: 73 additions & 0 deletions src/client/cmd/systray.h
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*
*/

#ifndef MULTIPASS_SYSTRAY_H
#define MULTIPASS_SYSTRAY_H

#include <multipass/cli/command.h>

#include <QMenu>
#include <QObject>
#include <QSystemTrayIcon>

#include <memory>
#include <thread>
#include <vector>

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<std::unique_ptr<QMenu>> instances_menus;
std::vector<QAction*> 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
4 changes: 2 additions & 2 deletions src/client/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
#include <multipass/ssl_cert_provider.h>
#include <multipass/utils.h>

#include <QCoreApplication>
#include <QApplication>
#include <QStandardPaths>
#include <QtGlobal>

Expand All @@ -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();

Expand Down

0 comments on commit 17ae950

Please sign in to comment.