./0000755000015600001650000000000012675011365011103 5ustar jenkinsjenkins./cmake/0000755000015600001650000000000012675011365012163 5ustar jenkinsjenkins./cmake/Translations.cmake0000644000015600001650000000304212675011356015645 0ustar jenkinsjenkins# Translations.cmake, CMake macros written for Marlin, feel free to re-use them macro(add_translations_directory NLS_PACKAGE) add_custom_target (i18n ALL) find_program (MSGFMT_EXECUTABLE msgfmt) file (GLOB PO_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.po) foreach (PO_INPUT ${PO_FILES}) get_filename_component (PO_INPUT_BASE ${PO_INPUT} NAME_WE) set (MO_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${PO_INPUT_BASE}.mo) add_custom_command (TARGET i18n COMMAND ${MSGFMT_EXECUTABLE} -o ${MO_OUTPUT} ${PO_INPUT}) install (FILES ${MO_OUTPUT} DESTINATION ${CMAKE_INSTALL_LOCALEDIR}/${PO_INPUT_BASE}/LC_MESSAGES RENAME ${NLS_PACKAGE}.mo) endforeach (PO_INPUT ${PO_FILES}) endmacro(add_translations_directory) macro(add_translations_catalog NLS_PACKAGE) add_custom_target (pot COMMENT “Building translation catalog.”) find_program (XGETTEXT_EXECUTABLE xgettext) # init this list, which will hold all the sources across all dirs set(SOURCES "") # add each directory's sources to the overall sources list foreach(FILES_INPUT ${ARGN}) set (DIR ${CMAKE_CURRENT_SOURCE_DIR}/${FILES_INPUT}) file (GLOB_RECURSE DIR_SOURCES ${DIR}/*.c ${DIR}/*.cc ${DIR}/*.cpp ${DIR}/*.cxx ${DIR}/*.vala) set (SOURCES ${SOURCES} ${DIR_SOURCES}) endforeach() add_custom_command (TARGET pot COMMAND ${XGETTEXT_EXECUTABLE} -d ${NLS_PACKAGE} -o ${CMAKE_CURRENT_SOURCE_DIR}/${NLS_PACKAGE}.pot ${SOURCES} --keyword="_" --keyword="N_" --from-code=UTF-8 ) endmacro() ./tests/0000755000015600001650000000000012675011365012245 5ustar jenkinsjenkins./tests/integration/0000755000015600001650000000000012675011365014570 5ustar jenkinsjenkins./tests/integration/usb-manager-test.cpp0000644000015600001650000002150712675011365020457 0ustar jenkinsjenkins/* * Copyright 2016 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Charles Kerr */ #include #include #include #include #include #include #include #include #include #include #include #include /*** **** ***/ class UsbManagerFixture: public QtFixture { using super = QtFixture; public: UsbManagerFixture(): dbusMock{dbusTestRunner} { dbusTestRunner.startServices(); } ~UsbManagerFixture() =default; protected: static void file_deleter (std::string* s) { fprintf(stderr, "remove \"%s\"\n", s->c_str()); g_remove(s->c_str()); delete s; } void SetUp() override { super::SetUp(); m_usb_monitor.reset(new MockUsbMonitor{}); m_greeter.reset(new MockGreeter{}); char tmpl[] = {"usb-manager-test-XXXXXX"}; m_tmpdir.reset(new std::string{g_mkdtemp(tmpl)}, file_deleter); g_message("using tmpdir '%s'", m_tmpdir->c_str()); dbusMock.registerNotificationDaemon(); dbusTestRunner.startServices(); } OrgFreedesktopDBusMockInterface& notificationsMockInterface() { return dbusMock.mockInterface(DBusNames::Notify::NAME, DBusNames::Notify::PATH, DBusNames::Notify::INTERFACE, QDBusConnection::SessionBus); } QtDBusTest::DBusTestRunner dbusTestRunner; QtDBusMock::DBusMock dbusMock; std::shared_ptr m_tmpdir; std::shared_ptr m_usb_monitor; std::shared_ptr m_greeter; }; TEST_F(UsbManagerFixture, Allow) { const std::shared_ptr socket_path {new std::string{*m_tmpdir+"/socket"}, file_deleter}; const std::shared_ptr public_keys_path {new std::string{*m_tmpdir+"/adb_keys"}, file_deleter}; // add a signal spy to listen to the notification daemon QSignalSpy notificationsSpy( ¬ificationsMockInterface(), SIGNAL(MethodCalled(const QString &, const QVariantList &)) ); // start a mock AdbdServer ready to submit a request const std::string public_key {"qAAAALUHllFjEZjl5jbS9ivjpQpaTNpibl28Re71D/S8sV3usNJTkbpvZYoVPfxtmHSNdCgLkWN6qcDZsHZqE/4myzmx/8Y/RqBy1oirudugi3YUUcJh7aWkY8lKQe9shCLTcrT7cFLZIJIidTvfmWTm0UcU+xmdPALze11I3lGo1Ty5KpCe9oP+qYM8suHbxhm78LKLlo0QJ2QqM8T5isr1pvoPHDgRb+mSESElG+xDIfPWA2BTu77/xk4EnXmOYfcuCr5akF3N4fRo/ACnYgXWDZFX2XdklBXyDj78lVlinF37xdMk7BMQh166X7UNkpH1uG2y5F6lUzyLg8SsFtRnJkw7eVe/gnJj3feQaFQbF5oVDhWhLMtWLtejhX6umvroVBVA4rynG4xEgs00K4u4ly8DUIIJYDO22Ml4myFR5CUm3lOlyitNdzYGh0utLXPq9oc8EbMVxM3i+O7PRxQw5Ul04X6K8GLiGUDV98DB+xYUqfEveq1BRnXi/ZrdPDhQ8Lfkg5xnLccPTFamAqutPtZXV6s7dXJInBTZf0NtBaWL0RdR2cOJBrpeBYkrc9yIyeqFLFdxr66rjaehjaa4pS4S+CD6PkGiIpPWSQtwNC4RlT10qTQ0/K9lRux2p0D8Z8ubUTFuh4kBScGUkN1OV3Z+7d7B+ghmBtZrrgleXsbehjRuKgEAAQA= foo@bar"}; const std::string fingerprint {"12:23:5f:2d:8c:40:ae:1d:05:7b:ae:bd:88:8a:f0:80"}; auto adbd_server = std::make_shared(*socket_path, std::vector{"PK"+public_key}); // set up a UsbManager to process the request auto usb_manager = std::make_shared(*socket_path, *public_keys_path, m_usb_monitor, m_greeter); // wait for the notification to show up, confirm it looks right wait_for_signals(notificationsSpy, 1); { QVariantList const& call(notificationsSpy.at(0)); EXPECT_EQ("Notify", call.at(0)); QVariantList const& args(call.at(1).toList()); ASSERT_EQ(8, args.size()); EXPECT_EQ("", args.at(0)); // app name EXPECT_EQ(0, args.at(1)); // replaces-id EXPECT_EQ("computer-symbolic", args.at(2)); // icon name EXPECT_EQ("Allow USB Debugging?", args.at(3)); // summary EXPECT_EQ(QString::fromUtf8("The computer's RSA key fingerprint is: ") + QString::fromUtf8(fingerprint.c_str()), args.at(4)); // body EXPECT_EQ(QStringList({"allow", "Allow", "deny", "Don't Allow"}), args.at(5)); // actions EXPECT_EQ(-1, args.at(7)); QVariantMap hints; ASSERT_TRUE(qDBusArgumentToMap(args.at(6), hints)); ASSERT_EQ(3, hints.size()); ASSERT_TRUE(hints.contains("x-canonical-private-affirmative-tint")); ASSERT_TRUE(hints.contains("x-canonical-non-shaped-icon")); ASSERT_TRUE(hints.contains("x-canonical-snap-decisions")); } notificationsSpy.clear(); // click on allow in the notification notificationsMockInterface().EmitSignal( DBusNames::Notify::INTERFACE, DBusNames::Notify::ActionInvoked::NAME, "us", QVariantList() << uint32_t(1) << "allow" ); // confirm that the AdbdServer got the right response wait_for([adbd_server](){return !adbd_server->m_responses.empty();}, 2000); ASSERT_EQ(1, adbd_server->m_responses.size()); EXPECT_EQ("OK", adbd_server->m_responses.front()); // confirm that the public_keys file got the public key appended to it std::ifstream ifkeys {*public_keys_path}; std::vector lines; std::string line; while(getline(ifkeys, line)) lines.emplace_back(std::move(line)); ASSERT_EQ(1, lines.size()); EXPECT_EQ(public_key, lines[0]); } TEST_F(UsbManagerFixture, USBDisconnectedDuringPrompt) { const std::shared_ptr socket_path {new std::string{*m_tmpdir+"/socket"}, file_deleter}; const std::shared_ptr public_keys_path {new std::string{*m_tmpdir+"/adb_keys"}, file_deleter}; // start a mock AdbdServer ready to submit a request const std::string public_key {"public_key"}; auto adbd_server = std::make_shared(*socket_path, std::vector{"PK"+public_key}); // set up a UsbManager to process the request auto usb_manager = std::make_shared(*socket_path, *public_keys_path, m_usb_monitor, m_greeter); for (int i=0; i<3; i++) { // add a signal spy to listen to the notification daemon QSignalSpy notificationsSpy( ¬ificationsMockInterface(), SIGNAL(MethodCalled(const QString &, const QVariantList &)) ); // wait for a notification to show up wait_for_signals(notificationsSpy, 1); EXPECT_EQ("Notify", notificationsSpy.at(0).at(0)); notificationsSpy.clear(); // wait for UsbSnap to receive dbusmock's response to the Notify request. // there's no event to key off of for this, so just wait for a moment wait_msec(); // disconnect the USB before the user has a chance to allow/deny m_usb_monitor->m_on_usb_disconnected("android0"); // confirm that we requested the notification to be pulled down wait_for_signals(notificationsSpy, 1); EXPECT_EQ("CloseNotification", notificationsSpy.at(0).at(0)); notificationsSpy.clear(); } } TEST_F(UsbManagerFixture, Greeter) { const std::shared_ptr socket_path {new std::string{*m_tmpdir+"/socket"}, file_deleter}; const std::shared_ptr public_keys_path {new std::string{*m_tmpdir+"/adb_keys"}, file_deleter}; // start a mock AdbdServer ready to submit a request const std::string public_key {"public_key"}; auto adbd_server = std::make_shared(*socket_path, std::vector{"PK"+public_key}); // set up a UsbManager to process the request m_greeter->m_is_active.set(true); auto usb_manager = std::make_shared(*socket_path, *public_keys_path, m_usb_monitor, m_greeter); // add a signal spy to listen to the notification daemon QSignalSpy notificationsSpy( ¬ificationsMockInterface(), SIGNAL(MethodCalled(const QString &, const QVariantList &)) ); // the greeter is active, so the notification should not appear EXPECT_FALSE(notificationsSpy.wait(2000)); // disable the greeter, the notification should appear m_greeter->m_is_active.set(false); wait_for_signals(notificationsSpy, 1); EXPECT_EQ("Notify", notificationsSpy.at(0).at(0)); notificationsSpy.clear(); } ./tests/integration/CMakeLists.txt0000644000015600001650000000124212675011365017327 0ustar jenkinsjenkinsset(SERVICE_LINK_LIBRARIES ${SERVICE_LIB} ${SERVICE_DEPS_LIBRARIES} ) set(QT_LINK_LIBRARIES test-utils Qt5::Core Qt5::Test Qt5::DBus ) set(TEST_LINK_LIBRARIES ${TEST_DEPS_LIBRARIES} ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES} ) function(add_qt_test_by_name name) set(TEST_NAME ${name}) add_executable (${TEST_NAME} ${TEST_NAME}.cpp) add_test(${TEST_NAME} ${TEST_NAME}) set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT ${CTEST_ENVIRONMENT}) target_link_libraries(${TEST_NAME} ${SERVICE_LINK_LIBRARIES} ${QT_LINK_LIBRARIES} ${TEST_LINK_LIBRARIES} ${THREAD_LINK_LIBRARIES}) endfunction() add_qt_test_by_name(usb-manager-test) ./tests/utils/0000755000015600001650000000000012675011365013405 5ustar jenkinsjenkins./tests/utils/glib-fixture.h0000644000015600001650000001551512675011365016166 0ustar jenkinsjenkins/* * Copyright 2013 Canonical Ltd. * * Authors: * Charles Kerr * * 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . */ #pragma once #include // std::function #include #include // std::shared_ptr #include #include #include #include #include // setlocale() class GlibFixture : public ::testing::Test { public: virtual ~GlibFixture() =default; protected: virtual void SetUp() override { setlocale(LC_ALL, "C.UTF-8"); loop = g_main_loop_new(nullptr, false); #ifdef SCHEMA_DIR // only use local, temporary settings g_assert(g_setenv("GSETTINGS_SCHEMA_DIR", SCHEMA_DIR, true)); g_assert(g_setenv("GSETTINGS_BACKEND", "memory", true)); g_debug("SCHEMA_DIR is %s", SCHEMA_DIR); #endif // fail on unexpected messages from this domain g_log_set_fatal_mask(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING); g_unsetenv("DISPLAY"); } virtual void TearDown() override { g_test_assert_expected_messages (); g_clear_pointer(&loop, g_main_loop_unref); } void expectLogMessage (const gchar *domain, GLogLevelFlags level, const gchar *pattern) { g_test_expect_message (domain, level, pattern); } private: static gboolean wait_for_signal__timeout(gpointer name) { g_error("%s: timed out waiting for signal '%s'", G_STRLOC, static_cast(name)); return G_SOURCE_REMOVE; } static gboolean wait_msec__timeout(gpointer loop) { g_main_loop_quit(static_cast(loop)); return G_SOURCE_CONTINUE; } protected: /* convenience func to loop while waiting for a GObject's signal */ void wait_for_signal(gpointer o, const gchar * signal, guint timeout_seconds=5) { // wait for the signal or for timeout, whichever comes first const auto handler_id = g_signal_connect_swapped(o, signal, G_CALLBACK(g_main_loop_quit), loop); const auto timeout_id = g_timeout_add_seconds(timeout_seconds, wait_for_signal__timeout, loop); g_main_loop_run(loop); g_source_remove(timeout_id); g_signal_handler_disconnect(o, handler_id); } /* convenience func to loop for N msec */ void wait_msec(guint msec=50) { const auto id = g_timeout_add(msec, wait_msec__timeout, loop); g_main_loop_run(loop); g_source_remove(id); } bool wait_for(std::function test_function, guint timeout_msec=1000) { auto timer = std::shared_ptr(g_timer_new(), [](GTimer* t){g_timer_destroy(t);}); const auto timeout_sec = timeout_msec / 1000.0; for (;;) { if (test_function()) return true; //g_message("%f ... %f", g_timer_elapsed(timer.get(), nullptr), timeout_sec); if (g_timer_elapsed(timer.get(), nullptr) >= timeout_sec) return false; wait_msec(); } } bool wait_for_name_owned(GDBusConnection* connection, const gchar* name, guint timeout_msec=1000, GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START) { struct Data { GMainLoop* loop = nullptr; bool owned = false; }; Data data; auto on_name_appeared = [](GDBusConnection* /*connection*/, const gchar* /*name_*/, const gchar* name_owner, gpointer gdata) { if (name_owner == nullptr) return; auto tmp = static_cast(gdata); tmp->owned = true; g_main_loop_quit(tmp->loop); }; const auto timeout_id = g_timeout_add(timeout_msec, wait_msec__timeout, loop); data.loop = loop; const auto watch_id = g_bus_watch_name_on_connection(connection, name, flags, on_name_appeared, nullptr, /* name_vanished */ &data, nullptr); /* user_data_free_func */ g_main_loop_run(loop); g_bus_unwatch_name(watch_id); g_source_remove(timeout_id); return data.owned; } void EXPECT_NAME_OWNED_EVENTUALLY(GDBusConnection* connection, const gchar* name, guint timeout_msec=1000, GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START) { EXPECT_TRUE(wait_for_name_owned(connection, name, timeout_msec, flags)) << "name: " << name; } void EXPECT_NAME_NOT_OWNED_EVENTUALLY(GDBusConnection* connection, const gchar* name, guint timeout_msec=1000, GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START) { EXPECT_FALSE(wait_for_name_owned(connection, name, timeout_msec, flags)) << "name: " << name; } void ASSERT_NAME_OWNED_EVENTUALLY(GDBusConnection* connection, const gchar* name, guint timeout_msec=1000, GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START) { ASSERT_TRUE(wait_for_name_owned(connection, name, timeout_msec, flags)) << "name: " << name; } void ASSERT_NAME_NOT_OWNED_EVENTUALLY(GDBusConnection* connection, const gchar* name, guint timeout_msec=1000, GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START) { ASSERT_FALSE(wait_for_name_owned(connection, name, timeout_msec, flags)) << "name: " << name; } GMainLoop * loop; }; ./tests/utils/mock-greeter.h0000644000015600001650000000172512675011365016147 0ustar jenkinsjenkins/* * Copyright 2016 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Charles Kerr */ #pragma once #include class MockGreeter: public Greeter { public: MockGreeter() =default; virtual ~MockGreeter() =default; core::Property& is_active() override {return m_is_active;} core::Property m_is_active {false}; }; ./tests/utils/test-dbus-fixture.h0000644000015600001650000000551412675011365017161 0ustar jenkinsjenkins/* * Copyright 2013 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Charles Kerr */ #pragma once #include "glib-fixture.h" /*** **** ***/ class TestDBusFixture: public GlibFixture { public: TestDBusFixture() =default; virtual ~TestDBusFixture() =default; explicit TestDBusFixture(const std::vector& service_dirs_in): service_dirs(service_dirs_in) {} private: typedef GlibFixture super; static void on_bus_opened (GObject* /*object*/, GAsyncResult * res, gpointer gself) { auto self = static_cast(gself); GError * err = 0; self->system_bus = g_bus_get_finish (res, &err); g_assert_no_error (err); g_main_loop_quit (self->loop); } static void on_bus_closed (GObject* /*object*/, GAsyncResult * res, gpointer gself) { auto self = static_cast(gself); GError * err = 0; g_dbus_connection_close_finish (self->system_bus, res, &err); g_assert_no_error (err); g_main_loop_quit (self->loop); } protected: GTestDBus * test_dbus = nullptr; GDBusConnection * system_bus = nullptr; const std::vector service_dirs; virtual void SetUp() override { super::SetUp (); // pull up a test dbus test_dbus = g_test_dbus_new (G_TEST_DBUS_NONE); for (const auto& dir : service_dirs) g_test_dbus_add_service_dir (test_dbus, dir.c_str()); g_test_dbus_up (test_dbus); const char * address = g_test_dbus_get_bus_address (test_dbus); g_setenv ("DBUS_SYSTEM_BUS_ADDRESS", address, true); g_setenv ("DBUS_SESSION_BUS_ADDRESS", address, true); g_debug ("test_dbus's address is %s", address); // wait for the GDBusConnection before returning g_bus_get (G_BUS_TYPE_SYSTEM, nullptr, on_bus_opened, this); g_main_loop_run (loop); } virtual void TearDown() override { wait_msec(); // close the system bus g_dbus_connection_close(system_bus, nullptr, on_bus_closed, this); g_main_loop_run(loop); g_clear_object(&system_bus); // tear down the test dbus g_test_dbus_down(test_dbus); g_clear_object(&test_dbus); super::TearDown(); } }; ./tests/utils/qmain.cpp0000644000015600001650000000272212675011365015221 0ustar jenkinsjenkins/* * Copyright © 2014 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3, * as published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Authors: * Pete Woods */ //#include #include #include #include #include using namespace QtDBusMock; class Runner: public QObject { Q_OBJECT public Q_SLOTS: void run() { QCoreApplication::exit(RUN_ALL_TESTS()); } }; int main(int argc, char **argv) { qputenv("LANG", "C.UTF-8"); unsetenv("LC_ALL"); // boilerplate i18n setlocale(LC_ALL, ""); bindtextdomain(GETTEXT_PACKAGE, GNOMELOCALEDIR); textdomain(GETTEXT_PACKAGE); QCoreApplication application(argc, argv); DBusMock::registerMetaTypes(); ::testing::InitGoogleTest(&argc, argv); Runner runner; QTimer::singleShot(0, &runner, SLOT(run())); return application.exec(); } #include "qmain.moc" ./tests/utils/qt-fixture.h0000644000015600001650000000370112675011365015667 0ustar jenkinsjenkins/* * Copyright 2016 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Charles Kerr */ #pragma once #define QT_NO_KEYWORDS #include #include #include #include #include #include #include #define wait_for_signals(signalSpy,signalsExpected) \ { \ while (signalSpy.size() < signalsExpected) \ { \ ASSERT_TRUE(signalSpy.wait()); \ } \ \ ASSERT_EQ(signalsExpected, signalSpy.size()); \ } class QtFixture: public GlibFixture { using super = GlibFixture; public: QtFixture() { DBusTypes::registerMetaTypes(); } ~QtFixture() =default; protected: bool qDBusArgumentToMap(QVariant const& variant, QVariantMap& map) { if (variant.canConvert()) { QDBusArgument value(variant.value()); if (value.currentType() == QDBusArgument::MapType) { value >> map; return true; } } return false; } }; ./tests/utils/gtest-qt-print-helpers.h0000644000015600001650000000174712675011365020131 0ustar jenkinsjenkins #pragma once #include #include #include #include inline QString qVariantToString(const QVariant& variant) { QString output; QDebug dbg(&output); dbg << variant; return output; } inline void PrintTo(const QVariant& variant, std::ostream* os) { QString output; QDebug dbg(&output); dbg << variant; *os << "QVariant(" << output.toStdString() << ")"; } inline void PrintTo(const QString& s, std::ostream* os) { *os << "\"" << s.toStdString() << "\""; } inline void PrintTo(const QStringList& list, std::ostream* os) { QString output; QDebug dbg(&output); dbg << list; *os << "QStringList(" << output.toStdString() << ")"; } inline void PrintTo(const QList& list, std::ostream* os) { QString output; for (const auto& path: list) { output.append("\"" + path.path() + "\","); } *os << "QList(" << output.toStdString() << ")"; } ./tests/utils/dbus-types.h0000644000015600001650000000235512675011365015662 0ustar jenkinsjenkins/* * Copyright (C) 2013-2016 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Author: Pete Woods */ #pragma once #include #include #include #include typedef QMap QVariantDictMap; Q_DECLARE_METATYPE(QVariantDictMap) typedef QMap QStringMap; Q_DECLARE_METATYPE(QStringMap) namespace DBusTypes { inline void registerMetaTypes() { qRegisterMetaType("QVariantDictMap"); qRegisterMetaType("QStringMap"); qDBusRegisterMetaType(); qDBusRegisterMetaType(); } } ./tests/utils/mock-usb-monitor.h0000644000015600001650000000202312675011365016760 0ustar jenkinsjenkins/* * Copyright 2016 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Charles Kerr */ #pragma once #include class MockUsbMonitor: public UsbMonitor { public: MockUsbMonitor() =default; virtual ~MockUsbMonitor() =default; core::Signal& on_usb_disconnected() override {return m_on_usb_disconnected;} core::Signal m_on_usb_disconnected; }; ./tests/utils/CMakeLists.txt0000644000015600001650000000030312675011365016141 0ustar jenkinsjenkinsinclude_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ) add_library( test-utils STATIC qmain.cpp ) qt5_use_modules( test-utils Core DBus ) ./tests/utils/adbd-server.h0000644000015600001650000001212512675011365015755 0ustar jenkinsjenkins/* * Copyright 2016 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Charles Kerr */ #include #include #include #include #include /** * A Mock ADBD server. * * Binds to a local domain socket, sends public key requests across it, * and reads back the client's responses. */ class GAdbdServer { public: GAdbdServer(const std::string& socket_path, const std::vector& requests): m_requests{requests}, m_socket_path{socket_path}, m_cancellable{g_cancellable_new()}, m_worker_thread{&GAdbdServer::worker_func, this} { } ~GAdbdServer() { // tell the worker thread to stop whatever it's doing and exit. g_cancellable_cancel(m_cancellable); m_worker_thread.join(); g_clear_object(&m_cancellable); } const std::vector m_requests; std::vector m_responses; private: void worker_func() // runs in worker thread { auto server_socket = create_server_socket(m_socket_path); auto requests = m_requests; GError* error {}; g_socket_listen (server_socket, &error); g_assert_no_error (error); while (!g_cancellable_is_cancelled(m_cancellable) && !requests.empty()) { // wait for a client connection g_message("GAdbdServer::Impl::worker_func() calling g_socket_accept()"); auto client_socket = g_socket_accept(server_socket, m_cancellable, &error); if (error != nullptr) { if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_message("GAdbdServer: Error accepting socket connection: %s", error->message); g_clear_error(&error); break; } // pop the next request off the stack auto request = requests.front(); // send the request g_message("GAdbdServer::Impl::worker_func() sending req [%s]", request.c_str()); g_socket_send(client_socket, request.c_str(), request.size(), m_cancellable, &error); if (error != nullptr) { if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_message("GAdbdServer: Error sending request: %s", error->message); g_clear_error(&error); g_clear_object(&client_socket); break; } // read the response g_message("GAdbdServer::Impl::worker_func() reading response"); char buf[4096]; const auto n_bytes = g_socket_receive(client_socket, buf, sizeof(buf), m_cancellable, &error); if (error != nullptr) { if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_message("GAdbdServer: Error reading response: %s", error->message); g_clear_error(&error); g_clear_object(&client_socket); continue; } const std::string response(buf, std::string::size_type(n_bytes)); g_message("server read %d bytes, got response: '%s'", int(n_bytes), response.c_str()); if (!response.empty()) { m_responses.push_back(response); requests.erase(requests.begin()); } // cleanup g_clear_object(&client_socket); } g_clear_object(&server_socket); } // bind to a local domain socket static GSocket* create_server_socket(const std::string& socket_path) { GError* error {}; auto socket = g_socket_new(G_SOCKET_FAMILY_UNIX, G_SOCKET_TYPE_STREAM, G_SOCKET_PROTOCOL_DEFAULT, &error); g_assert_no_error(error); auto address = g_unix_socket_address_new (socket_path.c_str()); g_socket_bind (socket, address, false, &error); g_assert_no_error (error); g_clear_object (&address); return socket; } const std::string m_socket_path; GCancellable* m_cancellable = nullptr; std::thread m_worker_thread; }; ./tests/unit/0000755000015600001650000000000012675011365013224 5ustar jenkinsjenkins./tests/unit/rotation-lock-test.cpp0000644000015600001650000000302512675011365017472 0ustar jenkinsjenkins/* * Copyright 2014 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Charles Kerr */ #include #include class RotationLockFixture: public TestDBusFixture { private: typedef TestDBusFixture super; protected: void SetUp() { super::SetUp(); } void TearDown() { super::TearDown(); } }; /*** **** ***/ TEST_F(RotationLockFixture, CheckIndicator) { RotationLockIndicator indicator; ASSERT_STREQ("rotation_lock", indicator.name()); auto actions = indicator.action_group(); ASSERT_TRUE(actions != nullptr); ASSERT_TRUE(g_action_group_has_action(G_ACTION_GROUP(actions), "rotation-lock")); std::vector> profiles = indicator.profiles(); ASSERT_EQ(1, profiles.size()); std::shared_ptr phone = profiles[0]; ASSERT_EQ(std::string("phone"), phone->name()); ASSERT_FALSE(phone->header()->is_visible); } ./tests/unit/adbd-client-test.cpp0000644000015600001650000000620712675011365017060 0ustar jenkinsjenkins/* * Copyright 2016 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Charles Kerr */ #include #include #include class AdbdClientFixture: public TestDBusFixture { private: typedef TestDBusFixture super; protected: static void file_deleter (std::string* s) { fprintf(stderr, "remove \"%s\"\n", s->c_str()); g_remove(s->c_str()); delete s; } std::shared_ptr m_tmpdir; void SetUp() { super::SetUp(); char tmpl[] = {"adb-client-test-XXXXXX"}; m_tmpdir.reset(new std::string{g_mkdtemp(tmpl)}, file_deleter); g_message("using tmpdir '%s'", m_tmpdir->c_str()); } }; TEST_F(AdbdClientFixture, SocketPlumbing) { struct { const std::string request; const std::string expected_pk; AdbdClient::PKResponse response; const std::string expected_response; } tests[] = { { "PKHelloWorld", "HelloWorld", AdbdClient::PKResponse::ALLOW, "OK" }, { "PKHelloWorld", "HelloWorld", AdbdClient::PKResponse::DENY, "NO" }, { "PKFooBar", "FooBar", AdbdClient::PKResponse::ALLOW, "OK" }, { "PK", "", AdbdClient::PKResponse::DENY, "NO" } }; const auto main_thread = g_thread_self(); const auto socket_path = *m_tmpdir + "/test-socket-plumbing"; g_message("socket_path is %s", socket_path.c_str()); for (const auto& test : tests) { // start an AdbdClient that listens for PKRequests std::string pk; auto adbd_client = std::make_shared(socket_path); adbd_client->on_pk_request().connect([&pk, main_thread, test](const AdbdClient::PKRequest& req){ EXPECT_EQ(main_thread, g_thread_self()); g_message("in on_pk_request with %s", req.public_key.c_str()); pk = req.public_key; req.respond(test.response); }); // start a mock AdbdServer with to fire test key requests and wait for a response auto adbd_server = std::make_shared(socket_path, std::vector{test.request}); wait_for([adbd_server](){return !adbd_server->m_responses.empty();}, 2000); EXPECT_EQ(test.expected_pk, pk); ASSERT_EQ(1, adbd_server->m_responses.size()); EXPECT_EQ(test.expected_response, adbd_server->m_responses.front()); // cleanup adbd_client.reset(); adbd_server.reset(); g_unlink(socket_path.c_str()); } } ./tests/unit/CMakeLists.txt0000644000015600001650000000211112675011365015757 0ustar jenkinsjenkinsset(SERVICE_LINK_LIBRARIES ${SERVICE_LIB} ${SERVICE_DEPS_LIBRARIES} ) set(QT_LINK_LIBRARIES test-utils Qt5::Core Qt5::Test Qt5::DBus ) set(TEST_LINK_LIBRARIES ${TEST_DEPS_LIBRARIES} ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES} ) function(add_test_by_name name) set(TEST_NAME ${name}) add_executable (${TEST_NAME} ${TEST_NAME}.cpp) add_test(${TEST_NAME} ${TEST_NAME}) set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT ${CTEST_ENVIRONMENT}) target_link_libraries(${TEST_NAME} ${SERVICE_LINK_LIBRARIES} ${TEST_LINK_LIBRARIES} ${THREAD_LINK_LIBRARIES}) endfunction() add_test_by_name(adbd-client-test) add_test_by_name(rotation-lock-test) function(add_qt_test_by_name name) set(TEST_NAME ${name}) add_executable (${TEST_NAME} ${TEST_NAME}.cpp) add_test(${TEST_NAME} ${TEST_NAME}) set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT ${CTEST_ENVIRONMENT}) target_link_libraries(${TEST_NAME} ${SERVICE_LINK_LIBRARIES} ${QT_LINK_LIBRARIES} ${TEST_LINK_LIBRARIES} ${THREAD_LINK_LIBRARIES}) endfunction() add_qt_test_by_name(usb-snap-test) ./tests/unit/usb-snap-test.cpp0000644000015600001650000001167212675011365016444 0ustar jenkinsjenkins/* * Copyright 2016 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Charles Kerr */ #include #include #include #include #include #include class UsbSnapFixture: public QtFixture { using super = QtFixture; public: UsbSnapFixture(): dbusMock{dbusTestRunner} { dbusTestRunner.startServices(); } ~UsbSnapFixture() =default; protected: void SetUp() override { super::SetUp(); dbusMock.registerNotificationDaemon(); dbusTestRunner.startServices(); } OrgFreedesktopDBusMockInterface& notificationsMockInterface() { return dbusMock.mockInterface(DBusNames::Notify::NAME, DBusNames::Notify::PATH, DBusNames::Notify::INTERFACE, QDBusConnection::SessionBus); } QtDBusTest::DBusTestRunner dbusTestRunner; QtDBusMock::DBusMock dbusMock; }; TEST_F(UsbSnapFixture, TestRoundTrip) { struct { const char* fingerprint; const char* action_to_invoke; const AdbdClient::PKResponse expected_response; } tests[] = { { "Fingerprint", "allow", AdbdClient::PKResponse::ALLOW }, { "Fingerprint", "deny", AdbdClient::PKResponse::DENY } }; uint32_t next_id = 1; for(const auto& test : tests) { // Minor wart: we don't have a way of getting the fdo notification id // from dbusmock so instead we copy its (simple) id generation here const auto id = next_id++; QSignalSpy notificationsSpy( ¬ificationsMockInterface(), SIGNAL(MethodCalled(const QString &, const QVariantList &))); // start up a UsbSnap to ask about a fingerprint auto snap = std::make_shared(test.fingerprint); AdbdClient::PKResponse user_response {}; bool user_response_set = false; snap->on_user_response().connect([&user_response,&user_response_set](AdbdClient::PKResponse response, bool /*remember*/){ user_response = response; user_response_set = true; }); // test that UsbSnap creates a fdo notification wait_for_signals(notificationsSpy, 1); { QVariantList const& call(notificationsSpy.at(0)); EXPECT_EQ("Notify", call.at(0)); QVariantList const& args(call.at(1).toList()); ASSERT_EQ(8, args.size()); EXPECT_EQ("", args.at(0)); // app name EXPECT_EQ(0, args.at(1)); // replaces-id EXPECT_EQ("computer-symbolic", args.at(2)); // icon name EXPECT_EQ("Allow USB Debugging?", args.at(3)); // summary EXPECT_EQ(QString::fromUtf8("The computer's RSA key fingerprint is: ") + test.fingerprint, args.at(4)); // body EXPECT_EQ(QStringList({"allow", "Allow", "deny", "Don't Allow"}), args.at(5)); // actions EXPECT_EQ(-1, args.at(7)); QVariantMap hints; ASSERT_TRUE(qDBusArgumentToMap(args.at(6), hints)); ASSERT_EQ(3, hints.size()); ASSERT_TRUE(hints.contains("x-canonical-private-affirmative-tint")); ASSERT_TRUE(hints.contains("x-canonical-non-shaped-icon")); ASSERT_TRUE(hints.contains("x-canonical-snap-decisions")); } notificationsSpy.clear(); // fake a user interaction with the fdo notification notificationsMockInterface().EmitSignal( DBusNames::Notify::INTERFACE, DBusNames::Notify::ActionInvoked::NAME, "us", QVariantList() << id << test.action_to_invoke); // test that UsbSnap emits on_user_response() as a result wait_for([&user_response_set](){return user_response_set;}); EXPECT_TRUE(user_response_set); ASSERT_EQ(test.expected_response, user_response); // confirm that the snap dtor cleans up the notification snap.reset(); wait_for_signals(notificationsSpy, 1); { QVariantList const& call(notificationsSpy.at(0)); EXPECT_EQ("CloseNotification", call.at(0)); QVariantList const& args(call.at(1).toList()); EXPECT_EQ(id, args.at(0)); } } } ./tests/CMakeLists.txt0000644000015600001650000000170312675011365015006 0ustar jenkinsjenkinsset(CMAKE_AUTOMOC ON) find_package(GMock REQUIRED) find_package(Qt5Core REQUIRED) find_package(Qt5Test REQUIRED) find_package(Qt5DBus COMPONENTS Qt5DBusMacros REQUIRED) pkg_check_modules(TEST_DEPS libqtdbustest-1 REQUIRED libqtdbusmock-1 REQUIRED ) include_directories(SYSTEM ${DBUSTEST_INCLUDE_DIRS} ${TEST_DEPS_INCLUDE_DIRS} ${GTEST_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS} ) list(APPEND CTEST_ENVIRONMENT G_MESSAGES_DEBUG=all G_DBUS_DEBUG=call,signal,return,message ) # turn off the warnings that break Google Test if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") list(APPEND CXX_WARNING_ARGS -Wno-global-constructors -Wno-weak-vtables) endif() add_compile_options(${CXX_WARNING_ARGS}) add_test(cppcheck cppcheck --enable=all -USCHEMA_DIR --error-exitcode=2 --inline-suppr -I${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/tests) add_subdirectory(integration) add_subdirectory(unit) add_subdirectory(utils) ./tests/manual0000644000015600001650000000171512675011356013451 0ustar jenkinsjenkins Test-case indicator-display/rotation-indicator
On the phone, enable the rotation lock in ubuntu-system-settings.
The rotation lock indicator should appear, and its switch menuitem should be set to 'true'.
With the rotation locked, click on the indicator's switch menuitem to toggle from locked to unlocked.
The rotation lock indicator should disappear
In ubuntu-system-settings, the rotation lock control should change to 'none'.
If all actions produce the expected results listed, please submit a 'passed' result. If an action fails, or produces an unexpected result, please submit a 'failed' result and file a bug. Please be sure to include the bug number when you submit your result. ./src/0000755000015600001650000000000012675011365011672 5ustar jenkinsjenkins./src/usb-snap.cpp0000644000015600001650000002227212675011365014133 0ustar jenkinsjenkins/* * Copyright 2016 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Charles Kerr */ #include #include #include #include /*** **** ***/ class UsbSnap::Impl { public: explicit Impl(const std::string& fingerprint): m_fingerprint{fingerprint}, m_cancellable{g_cancellable_new()} { g_bus_get (G_BUS_TYPE_SESSION, m_cancellable, on_bus_ready_static, this); } ~Impl() { g_cancellable_cancel(m_cancellable); g_clear_object(&m_cancellable); if (m_subscription_id != 0) g_dbus_connection_signal_unsubscribe (m_bus, m_subscription_id); if (m_notification_id != 0) { GError* error {}; g_dbus_connection_call_sync(m_bus, DBusNames::Notify::NAME, DBusNames::Notify::PATH, DBusNames::Notify::INTERFACE, "CloseNotification", g_variant_new("(u)", m_notification_id), nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error); if (error != nullptr) { g_warning("Error closing notification: %s", error->message); g_clear_error(&error); } } g_clear_object(&m_bus); } core::Signal& on_user_response() { return m_on_user_response; } private: static void on_bus_ready_static(GObject* /*source*/, GAsyncResult* res, gpointer gself) { GError* error {}; auto bus = g_bus_get_finish (res, &error); if (error != nullptr) { if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning("UsbSnap: Error getting session bus: %s", error->message); g_clear_error(&error); } else { static_cast(gself)->on_bus_ready(bus); } g_clear_object(&bus); } void on_bus_ready(GDBusConnection* bus) { m_bus = G_DBUS_CONNECTION(g_object_ref(G_OBJECT(bus))); m_subscription_id = g_dbus_connection_signal_subscribe(m_bus, DBusNames::Notify::NAME, DBusNames::Notify::INTERFACE, nullptr, DBusNames::Notify::PATH, nullptr, G_DBUS_SIGNAL_FLAGS_NONE, on_notification_signal_static, this, nullptr); auto body = g_strdup_printf(_("The computer's RSA key fingerprint is: %s"), m_fingerprint.c_str()); GVariantBuilder actions_builder; g_variant_builder_init(&actions_builder, G_VARIANT_TYPE_STRING_ARRAY); g_variant_builder_add(&actions_builder, "s", ACTION_ALLOW); g_variant_builder_add(&actions_builder, "s", _("Allow")); g_variant_builder_add(&actions_builder, "s", ACTION_DENY); g_variant_builder_add(&actions_builder, "s", _("Don't Allow")); GVariantBuilder hints_builder; g_variant_builder_init(&hints_builder, G_VARIANT_TYPE_VARDICT); g_variant_builder_add(&hints_builder, "{sv}", "x-canonical-non-shaped-icon", g_variant_new_string("true")); g_variant_builder_add(&hints_builder, "{sv}", "x-canonical-snap-decisions", g_variant_new_string("true")); g_variant_builder_add(&hints_builder, "{sv}", "x-canonical-private-affirmative-tint", g_variant_new_string("true")); auto args = g_variant_new("(susssasa{sv}i)", "", uint32_t(0), "computer-symbolic", _("Allow USB Debugging?"), body, &actions_builder, &hints_builder, -1); g_dbus_connection_call(m_bus, DBusNames::Notify::NAME, DBusNames::Notify::PATH, DBusNames::Notify::INTERFACE, "Notify", args, G_VARIANT_TYPE("(u)"), G_DBUS_CALL_FLAGS_NONE, -1, // timeout m_cancellable, on_notify_reply_static, this); g_clear_pointer(&body, g_free); } static void on_notify_reply_static(GObject* obus, GAsyncResult* res, gpointer gself) { GError* error {}; auto reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION(obus), res, &error); if (error != nullptr) { if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning("UsbSnap: Error calling Notify: %s", error->message); g_clear_error(&error); } else { uint32_t id {}; g_variant_get(reply, "(u)", &id); static_cast(gself)->on_notify_reply(id); } g_clear_pointer(&reply, g_variant_unref); } void on_notify_reply(uint32_t id) { m_notification_id = id; } static void on_notification_signal_static(GDBusConnection* /*connection*/, const gchar* /*sender_name*/, const gchar* object_path, const gchar* interface_name, const gchar* signal_name, GVariant* parameters, gpointer gself) { g_return_if_fail(!g_strcmp0(object_path, DBusNames::Notify::PATH)); g_return_if_fail(!g_strcmp0(interface_name, DBusNames::Notify::INTERFACE)); auto self = static_cast(gself); if (!g_strcmp0(signal_name, DBusNames::Notify::ActionInvoked::NAME)) { uint32_t id {}; const char* action_name {}; g_variant_get(parameters, "(u&s)", &id, &action_name); if (id == self->m_notification_id) self->on_action_invoked(action_name); } else if (!g_strcmp0(signal_name, DBusNames::Notify::NotificationClosed::NAME)) { uint32_t id {}; uint32_t close_reason {}; g_variant_get(parameters, "(uu)", &id, &close_reason); if (id == self->m_notification_id) self->on_notification_closed(close_reason); } } void on_action_invoked(const char* action_name) { const auto response = !g_strcmp0(action_name, ACTION_ALLOW) ? AdbdClient::PKResponse::ALLOW : AdbdClient::PKResponse::DENY; // FIXME: the current default is to cover the most common use case. // We need to get the notification ui's checkbox working ASAP so // that the user can provide this flag const bool remember_this_choice = response == AdbdClient::PKResponse::ALLOW; m_on_user_response(response, remember_this_choice); } void on_notification_closed(uint32_t close_reason) { if (close_reason == DBusNames::Notify::NotificationClosed::Reason::EXPIRED) m_on_user_response(AdbdClient::PKResponse::DENY, false); m_notification_id = 0; } static constexpr char const * ACTION_ALLOW {"allow"}; static constexpr char const * ACTION_DENY {"deny"}; const std::string m_fingerprint; core::Signal m_on_user_response; GCancellable* m_cancellable {}; GDBusConnection* m_bus {}; uint32_t m_notification_id {}; unsigned int m_subscription_id {}; }; /*** **** ***/ UsbSnap::UsbSnap(const std::string& public_key): impl{new Impl{public_key}} { } UsbSnap::~UsbSnap() { } core::Signal& UsbSnap::on_user_response() { return impl->on_user_response(); } ./src/indicator.cpp0000644000015600001650000000153012675011365014351 0ustar jenkinsjenkins/* * Copyright 2016 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Charles Kerr */ #include Profile::Profile() { } Profile::~Profile() { } SimpleProfile::~SimpleProfile() { } Indicator::~Indicator() { } ./src/exporter.h0000644000015600001650000000225312675011356013715 0ustar jenkinsjenkins/* * Copyright 2014 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Charles Kerr */ #ifndef INDICATOR_DISPLAY_EXPORTER_H #define INDICATOR_DISPLAY_EXPORTER_H #include #include #include class Exporter { public: explicit Exporter(const std::shared_ptr& indicator); ~Exporter(); core::Signal& name_lost(); private: class Impl; std::unique_ptr impl; Exporter(const Exporter&) =delete; Exporter& operator=(const Exporter&) =delete; }; #endif /* INDICATOR_DISPLAY_EXPORTER_H */ ./src/rotation-lock.h0000644000015600001650000000222512675011356014631 0ustar jenkinsjenkins/* * Copyright 2014 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Charles Kerr */ #ifndef INDICATOR_DISPLAY_ROTATION_LOCK_H #define INDICATOR_DISPLAY_ROTATION_LOCK_H #include #include // std::unique_ptr class RotationLockIndicator: public Indicator { public: RotationLockIndicator(); ~RotationLockIndicator(); const char* name() const; GSimpleActionGroup* action_group() const; std::vector> profiles() const; protected: class Impl; std::unique_ptr impl; }; #endif ./src/main.cpp0000644000015600001650000000501212675011365013320 0ustar jenkinsjenkins/* * Copyright 2014 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Charles Kerr */ #include #include #include #include #include #include // bindtextdomain() #include #include int main(int /*argc*/, char** /*argv*/) { // Work around a deadlock in glib's type initialization. // It can be removed when https://bugzilla.gnome.org/show_bug.cgi?id=674885 is fixed. g_type_ensure(G_TYPE_DBUS_CONNECTION); // boilerplate i18n setlocale(LC_ALL, ""); bindtextdomain(GETTEXT_PACKAGE, GNOMELOCALEDIR); textdomain(GETTEXT_PACKAGE); auto loop = g_main_loop_new(nullptr, false); auto on_name_lost = [loop](const std::string& name){ g_warning("busname lost: '%s'", name.c_str()); g_main_loop_quit(loop); }; // build all our indicators. // Right now we've only got one -- rotation lock -- but hey, we can dream. std::vector> indicators; std::vector> exporters; indicators.push_back(std::make_shared()); for (auto& indicator : indicators) { auto exporter = std::make_shared(indicator); exporter->name_lost().connect(on_name_lost); exporters.push_back(exporter); } // We need the ADBD handler running, // even though it doesn't have an indicator component yet static constexpr char const * ADB_SOCKET_PATH {"/dev/socket/adbd"}; static constexpr char const * PUBLIC_KEYS_FILENAME {"/data/misc/adb/adb_keys"}; auto usb_monitor = std::make_shared(); auto greeter = std::make_shared(); UsbManager usb_manager {ADB_SOCKET_PATH, PUBLIC_KEYS_FILENAME, usb_monitor, greeter}; // let's go! g_main_loop_run(loop); // cleanup g_main_loop_unref(loop); return 0; } ./src/adbd-client.h0000644000015600001650000000374112675011365014216 0ustar jenkinsjenkins/* * Copyright 2016 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Charles Kerr */ #pragma once #include #include #include #include /** * Receives public key requests from ADBD and sends a response back. * * AdbClient only provides a receive/respond mechanism. The decision * of what response gets sent is delegated out to a listener via * the on_pk_request signal. * * The decider should connect to on_pk_request, listen for PKRequests, * and call the request's `respond' method with the desired response. */ class AdbdClient { public: virtual ~AdbdClient(); enum class PKResponse { DENY, ALLOW }; struct PKRequest { std::string public_key; std::string fingerprint; std::function respond; }; virtual core::Signal& on_pk_request() =0; protected: AdbdClient() =default; }; /** * An AdbdClient designed to work with GLib's event loop. * * The on_pk_request() signal will be called in global GMainContext's thread; * ie, just like a function passed to g_idle_add() or g_timeout_add(). */ class GAdbdClient: public AdbdClient { public: explicit GAdbdClient(const std::string& socket_path); ~GAdbdClient(); core::Signal& on_pk_request() override; private: class Impl; std::unique_ptr impl; }; ./src/usb-manager.cpp0000644000015600001650000001223712675011365014604 0ustar jenkinsjenkins/* * Copyright 2016 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Charles Kerr */ #include #include #include #include #include #include #include #include #include class UsbManager::Impl { public: explicit Impl( const std::string& socket_path, const std::string& public_keys_filename, const std::shared_ptr& usb_monitor, const std::shared_ptr& greeter ): m_socket_path{socket_path}, m_public_keys_filename{public_keys_filename}, m_usb_monitor{usb_monitor}, m_greeter{greeter} { m_usb_monitor->on_usb_disconnected().connect([this](const std::string& /*usb_name*/) { restart(); }); m_greeter->is_active().changed().connect([this](bool /*is_active*/) { maybe_snap(); }); restart(); } ~Impl() { if (m_restart_idle_tag) g_source_remove(m_restart_idle_tag); clear(); } private: void clear() { // clear out old state m_snap_connections.clear(); m_snap.reset(); m_req = decltype(m_req)(); m_adbd_client.reset(); } void restart() { clear(); // set a new client m_adbd_client.reset(new GAdbdClient{m_socket_path}); m_adbd_client->on_pk_request().connect( [this](const AdbdClient::PKRequest& req) { g_debug("%s got pk request: %s", G_STRLOC, req.fingerprint.c_str()); m_req = req; maybe_snap(); } ); } void maybe_snap() { // don't prompt in the greeter! if (!m_req.public_key.empty() && !m_greeter->is_active().get()) snap(); } void snap() { m_snap = std::make_shared(m_req.fingerprint); m_snap_connections.insert((*m_snap).on_user_response().connect( [this](AdbdClient::PKResponse response, bool remember_choice){ g_debug("%s user responded! response %d, remember %d", G_STRLOC, int(response), int(remember_choice)); m_req.respond(response); if (remember_choice && (response == AdbdClient::PKResponse::ALLOW)) write_public_key(m_req.public_key); m_restart_idle_tag = g_idle_add([](gpointer gself){ auto self = static_cast(gself); self->m_restart_idle_tag = 0; self->restart(); return G_SOURCE_REMOVE; }, this); } )); } void write_public_key(const std::string& public_key) { g_debug("%s writing public key '%s' to '%s'", G_STRLOC, public_key.c_str(), m_public_keys_filename.c_str()); // confirm the directory exists auto dirname = g_path_get_dirname(m_public_keys_filename.c_str()); const auto dir_exists = g_file_test(dirname, G_FILE_TEST_IS_DIR); if (!dir_exists) g_warning("ADB data directory '%s' does not exist", dirname); g_clear_pointer(&dirname, g_free); if (!dir_exists) return; // open the file in append mode, with user rw and group r permissions const auto fd = open( m_public_keys_filename.c_str(), O_APPEND|O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR|S_IRGRP ); if (fd == -1) { g_warning("Error opening ADB datafile: %s", g_strerror(errno)); return; } // write the new public key on its own line std::string buf {public_key + '\n'}; if (write(fd, buf.c_str(), buf.size()) == -1) g_warning("Error writing ADB datafile: %d %s", errno, g_strerror(errno)); close(fd); } const std::string m_socket_path; const std::string m_public_keys_filename; const std::shared_ptr m_usb_monitor; const std::shared_ptr m_greeter; unsigned int m_restart_idle_tag {}; std::shared_ptr m_adbd_client; AdbdClient::PKRequest m_req; std::shared_ptr m_snap; std::set m_snap_connections; }; /*** **** ***/ UsbManager::UsbManager( const std::string& socket_path, const std::string& public_keys_filename, const std::shared_ptr& usb_monitor, const std::shared_ptr& greeter ): impl{new Impl{socket_path, public_keys_filename, usb_monitor, greeter}} { } UsbManager::~UsbManager() { } ./src/greeter.h0000644000015600001650000000213412675011365013500 0ustar jenkinsjenkins/* * Copyright 2016 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Charles Kerr */ #pragma once #include #include #include class Greeter { public: Greeter(); virtual ~Greeter(); virtual core::Property& is_active() =0; }; class UnityGreeter: public Greeter { public: UnityGreeter(); virtual ~UnityGreeter(); core::Property& is_active() override; protected: class Impl; std::unique_ptr impl; }; ./src/rotation-lock.cpp0000644000015600001650000001153512675011365015170 0ustar jenkinsjenkins/* * Copyright 2014 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Authors: * Charles Kerr */ #include #include class RotationLockIndicator::Impl { public: Impl(): m_settings(g_settings_new(m_schema_name)), m_action_group(create_action_group()) { // build the rotation lock icon auto icon = g_themed_icon_new_with_default_fallbacks(m_rotation_lock_icon_name); auto icon_deleter = [](GIcon* o){g_object_unref(G_OBJECT(o));}; m_icon.reset(icon, icon_deleter); // build the phone profile auto menu_model_deleter = [](GMenuModel* o){g_object_unref(G_OBJECT(o));}; std::shared_ptr phone_menu (create_phone_menu(), menu_model_deleter); m_phone = std::make_shared("phone", phone_menu); update_phone_header(); } ~Impl() { g_signal_handlers_disconnect_by_data(m_settings, this); g_clear_object(&m_action_group); g_clear_object(&m_settings); } GSimpleActionGroup* action_group() const { return m_action_group; } std::vector> profiles() { std::vector> ret; ret.push_back(m_phone); return ret; } private: /*** **** Actions ***/ static gboolean settings_to_action_state(GValue *value, GVariant *variant, gpointer /*unused*/) { g_value_set_variant(value, variant); return TRUE; } static GVariant* action_state_to_settings(const GValue *value, const GVariantType * /*expected_type*/, gpointer /*unused*/) { return g_value_dup_variant(value); } GSimpleActionGroup* create_action_group() { GSimpleActionGroup* group; GSimpleAction* action; group = g_simple_action_group_new(); action = g_simple_action_new_stateful("rotation-lock", nullptr, g_variant_new_boolean(false)); g_settings_bind_with_mapping(m_settings, "rotation-lock", action, "state", G_SETTINGS_BIND_DEFAULT, settings_to_action_state, action_state_to_settings, nullptr, nullptr); g_action_map_add_action(G_ACTION_MAP(group), G_ACTION(action)); g_object_unref(G_OBJECT(action)); g_signal_connect_swapped(m_settings, "changed::rotation-lock", G_CALLBACK(on_rotation_lock_setting_changed), this); return group; } /*** **** Phone profile ***/ static void on_rotation_lock_setting_changed (gpointer gself) { static_cast(gself)->update_phone_header(); } GMenuModel* create_phone_menu() { GMenu* menu; GMenuItem* menu_item; menu = g_menu_new(); menu_item = g_menu_item_new(_("Rotation Lock"), "indicator.rotation-lock"); g_menu_item_set_attribute(menu_item, "x-canonical-type", "s", "com.canonical.indicator.switch"); g_menu_append_item(menu, menu_item); g_object_unref(menu_item); return G_MENU_MODEL(menu); } void update_phone_header() { Header h; h.title = _("Rotation"); h.a11y = h.title; h.is_visible = g_settings_get_boolean(m_settings, "rotation-lock"); h.icon = m_icon; m_phone->header().set(h); } /*** **** ***/ static constexpr char const * m_schema_name {"com.ubuntu.touch.system"}; static constexpr char const * m_rotation_lock_icon_name {"orientation-lock"}; GSettings* m_settings = nullptr; GSimpleActionGroup* m_action_group = nullptr; std::shared_ptr m_phone; std::shared_ptr m_icon; }; /*** **** ***/ RotationLockIndicator::RotationLockIndicator(): impl(new Impl()) { } RotationLockIndicator::~RotationLockIndicator() { } std::vector> RotationLockIndicator::profiles() const { return impl->profiles(); } GSimpleActionGroup* RotationLockIndicator::action_group() const { return impl->action_group(); } const char* RotationLockIndicator::name() const { return "rotation_lock"; } /*** **** ***/ ./src/usb-snap.h0000644000015600001650000000217512675011365013600 0ustar jenkinsjenkins/* * Copyright 2016 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Charles Kerr */ #pragma once #include // AdbdClient::PKResponse #include #include #include /** * A snap decision prompt for whether or not to allow an ADB connection */ class UsbSnap { public: explicit UsbSnap(const std::string& public_key); ~UsbSnap(); core::Signal& on_user_response(); protected: class Impl; std::unique_ptr impl; }; ./src/dbus-names.h0000644000015600001650000000357212675011365014110 0ustar jenkinsjenkins/* * Copyright 2016 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Charles Kerr */ #pragma once namespace DBusNames { namespace Notify { static constexpr char const * NAME = "org.freedesktop.Notifications"; static constexpr char const * PATH = "/org/freedesktop/Notifications"; static constexpr char const * INTERFACE = "org.freedesktop.Notifications"; namespace ActionInvoked { static constexpr char const * NAME = "ActionInvoked"; } namespace NotificationClosed { static constexpr char const * NAME = "NotificationClosed"; enum Reason { EXPIRED=1, DISMISSED=2, API=3, UNDEFINED=4 }; } } namespace UnityGreeter { static constexpr char const * NAME = "com.canonical.UnityGreeter"; static constexpr char const * PATH = "/"; static constexpr char const * INTERFACE = "com.canonical.UnityGreeter"; } namespace Properties { static constexpr char const * INTERFACE = "org.freedesktop.DBus.Properties"; namespace PropertiesChanged { static constexpr char const* NAME = "PropertiesChanged"; static constexpr char const* ARGS_VARIANT_TYPE = "(sa{sv}as)"; } } } ./src/usb-manager.h0000644000015600001650000000230612675011365014245 0ustar jenkinsjenkins/* * Copyright 2016 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Charles Kerr */ #pragma once #include #include #include #include /** * Manager class that connects the AdbdClient, UsbSnap, and manages the public key file */ class UsbManager { public: UsbManager( const std::string& socket_path, const std::string& public_key_filename, const std::shared_ptr&, const std::shared_ptr& ); ~UsbManager(); protected: class Impl; std::unique_ptr impl; }; ./src/adbd-client.cpp0000644000015600001650000002355012675011365014551 0ustar jenkinsjenkins/* * Copyright 2016 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Charles Kerr */ #include #include #include #include #include #include #include #include #include #include class GAdbdClient::Impl { public: explicit Impl(const std::string& socket_path): m_socket_path{socket_path}, m_cancellable{g_cancellable_new()}, m_worker_thread{&Impl::worker_func, this} { } ~Impl() { // tell the worker thread to stop whatever it's doing and exit. g_debug("%s Client::Impl dtor, cancelling m_cancellable", G_STRLOC); g_cancellable_cancel(m_cancellable); m_pkresponse_cv.notify_one(); m_sleep_cv.notify_one(); m_worker_thread.join(); g_clear_object(&m_cancellable); } core::Signal& on_pk_request() { return m_on_pk_request; } private: // struct to carry request info from the worker thread to the GMainContext thread struct PKIdleData { Impl* self = nullptr; GCancellable* cancellable = nullptr; const std::string public_key; PKIdleData(Impl* self_, GCancellable* cancellable_, std::string public_key_): self(self_), cancellable(G_CANCELLABLE(g_object_ref(cancellable_))), public_key(public_key_) {} ~PKIdleData() {g_clear_object(&cancellable);} }; void pass_public_key_to_main_thread(const std::string& public_key) { g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, on_public_key_request_static, new PKIdleData{this, m_cancellable, public_key}, [](gpointer id){delete static_cast(id);}); } static gboolean on_public_key_request_static (gpointer gdata) // runs in main thread { /* NB: It's possible (though unlikely) that data.self was destroyed while this callback was pending, so we must check is-cancelled FIRST */ auto data = static_cast(gdata); if (!g_cancellable_is_cancelled(data->cancellable)) { // notify our listeners of the request auto self = data->self; struct PKRequest req; req.public_key = data->public_key; req.fingerprint = get_fingerprint(req.public_key); req.respond = [self](PKResponse response){self->on_public_key_response(response);}; self->m_on_pk_request(req); } return G_SOURCE_REMOVE; } void on_public_key_response(PKResponse response) { // set m_pkresponse and wake up the waiting worker thread std::unique_lock lk(m_pkresponse_mutex); m_pkresponse = response; m_pkresponse_ready = true; m_pkresponse_cv.notify_one(); } /*** **** ***/ void worker_func() // runs in worker thread { const std::string socket_path {m_socket_path}; while (!g_cancellable_is_cancelled(m_cancellable)) { g_debug("%s creating a client socket to '%s'", G_STRLOC, socket_path.c_str()); auto socket = create_client_socket(socket_path); bool got_valid_req = false; g_debug("%s calling read_request", G_STRLOC); std::string reqstr; if (socket != nullptr) reqstr = read_request(socket); if (!reqstr.empty()) g_debug("%s got request [%s]", G_STRLOC, reqstr.c_str()); if (reqstr.substr(0,2) == "PK") { PKResponse response = PKResponse::DENY; const auto public_key = reqstr.substr(2); g_debug("%s got pk [%s]", G_STRLOC, public_key.c_str()); if (!public_key.empty()) { got_valid_req = true; std::unique_lock lk(m_pkresponse_mutex); m_pkresponse_ready = false; pass_public_key_to_main_thread(public_key); m_pkresponse_cv.wait(lk, [this](){ return m_pkresponse_ready || g_cancellable_is_cancelled(m_cancellable); }); response = m_pkresponse; g_debug("%s got response '%d', is-cancelled %d", G_STRLOC, int(response), int(g_cancellable_is_cancelled(m_cancellable))); } if (!g_cancellable_is_cancelled(m_cancellable)) send_pk_response(socket, response); } else if (!reqstr.empty()) { g_warning("Invalid ADB request: [%s]", reqstr.c_str()); } g_clear_object(&socket); // If nothing interesting's happening, sleep a bit. // (Interval copied from UsbDebuggingManager.java) static constexpr std::chrono::seconds sleep_interval {std::chrono::seconds(1)}; if (!got_valid_req && !g_cancellable_is_cancelled(m_cancellable)) { std::unique_lock lk(m_sleep_mutex); m_sleep_cv.wait_for(lk, sleep_interval); } } } // connect to a local domain socket GSocket* create_client_socket(const std::string& socket_path) { GError* error {}; auto socket = g_socket_new(G_SOCKET_FAMILY_UNIX, G_SOCKET_TYPE_STREAM, G_SOCKET_PROTOCOL_DEFAULT, &error); if (error != nullptr) { g_warning("Error creating adbd client socket: %s", error->message); g_clear_error(&error); g_clear_object(&socket); return nullptr; } auto address = g_unix_socket_address_new(socket_path.c_str()); const auto connected = g_socket_connect(socket, address, m_cancellable, &error); g_clear_object(&address); if (!connected) { g_debug("unable to connect to '%s': %s", socket_path.c_str(), error->message); g_clear_error(&error); g_clear_object(&socket); return nullptr; } return socket; } std::string read_request(GSocket* socket) { char buf[4096] = {}; g_debug("%s calling g_socket_receive()", G_STRLOC); const auto n_bytes = g_socket_receive (socket, buf, sizeof(buf), m_cancellable, nullptr); std::string ret; if (n_bytes > 0) ret.append(buf, std::string::size_type(n_bytes)); g_debug("%s g_socket_receive got %d bytes: [%s]", G_STRLOC, int(n_bytes), ret.c_str()); return ret; } void send_pk_response(GSocket* socket, PKResponse response) { std::string response_str; switch(response) { case PKResponse::ALLOW: response_str = "OK"; break; case PKResponse::DENY: response_str = "NO"; break; } g_debug("%s sending reply: [%s]", G_STRLOC, response_str.c_str()); GError* error {}; g_socket_send(socket, response_str.c_str(), response_str.size(), m_cancellable, &error); if (error != nullptr) { if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning("GAdbdServer: Error accepting socket connection: %s", error->message); g_clear_error(&error); } } static std::string get_fingerprint(const std::string& public_key) { // The first token is base64-encoded data, so cut on the first whitespace const std::string base64 ( public_key.begin(), std::find_if( public_key.begin(), public_key.end(), [](const std::string::value_type& ch){return std::isspace(ch);} ) ); gsize digest_len {}; auto digest = g_base64_decode(base64.c_str(), &digest_len); auto checksum = g_compute_checksum_for_data(G_CHECKSUM_MD5, digest, digest_len); const gsize checksum_len = checksum ? strlen(checksum) : 0; // insert ':' between character pairs; eg "ff27b5f3" --> "ff:27:b5:f3" std::string fingerprint; for (gsize i=0; i m_on_pk_request; std::mutex m_sleep_mutex; std::condition_variable m_sleep_cv; std::mutex m_pkresponse_mutex; std::condition_variable m_pkresponse_cv; bool m_pkresponse_ready = false; PKResponse m_pkresponse = PKResponse::DENY; }; /*** **** ***/ AdbdClient::~AdbdClient() { } /*** **** ***/ GAdbdClient::GAdbdClient(const std::string& socket_path): impl{new Impl{socket_path}} { } GAdbdClient::~GAdbdClient() { } core::Signal& GAdbdClient::on_pk_request() { return impl->on_pk_request(); } ./src/usb-monitor.h0000644000015600001650000000246712675011365014332 0ustar jenkinsjenkins/* * Copyright 2016 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Charles Kerr */ #pragma once #include #include #include /** * Simple interface that emits signals on USB device state changes */ class UsbMonitor { public: UsbMonitor(); virtual ~UsbMonitor(); virtual core::Signal& on_usb_disconnected() =0; }; /** * Simple GUDev wrapper that notifies on android_usb device state changes */ class GUDevUsbMonitor: public UsbMonitor { public: GUDevUsbMonitor(); virtual ~GUDevUsbMonitor(); core::Signal& on_usb_disconnected() override; protected: class Impl; std::unique_ptr impl; }; ./src/CMakeLists.txt0000644000015600001650000000110312675011365014425 0ustar jenkinsjenkins add_definitions(-DG_LOG_DOMAIN="${CMAKE_PROJECT_NAME}") add_compile_options( ${CXX_WARNING_ARGS} ) add_library( ${SERVICE_LIB} STATIC adbd-client.cpp exporter.cpp greeter.cpp indicator.cpp rotation-lock.cpp usb-manager.cpp usb-monitor.cpp usb-snap.cpp ) add_executable( ${SERVICE_EXEC} main.cpp ) target_link_libraries(${SERVICE_EXEC} ${SERVICE_LIB} ${SERVICE_DEPS_LIBRARIES} ${THREAD_LINK_LIBRARIES} ) install( TARGETS ${SERVICE_EXEC} RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_PKGLIBEXECDIR} ) ./src/indicator.h0000644000015600001650000000432412675011365014022 0ustar jenkinsjenkins/* * Copyright 2014-2016 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Charles Kerr */ #pragma once #include #include // GIcon #include #include struct Header { bool is_visible = false; std::string title; std::string label; std::string a11y; std::shared_ptr icon; bool operator== (const Header& that) const { return (is_visible == that.is_visible) && (title == that.title) && (label == that.label) && (a11y == that.a11y) && (icon == that.icon); } bool operator!= (const Header& that) const { return !(*this == that);} }; class Profile { public: virtual std::string name() const =0; virtual const core::Property
& header() const =0; virtual std::shared_ptr menu_model() const =0; virtual ~Profile(); protected: Profile(); }; class SimpleProfile: public Profile { public: SimpleProfile(const char* name, const std::shared_ptr& menu): m_name(name), m_menu(menu) {} virtual ~SimpleProfile(); std::string name() const {return m_name;} core::Property
& header() {return m_header;} const core::Property
& header() const {return m_header;} std::shared_ptr menu_model() const {return m_menu;} protected: const std::string m_name; core::Property
m_header; std::shared_ptr m_menu; }; class Indicator { public: virtual ~Indicator(); virtual const char* name() const =0; virtual GSimpleActionGroup* action_group() const =0; virtual std::vector> profiles() const =0; }; ./src/usb-monitor.cpp0000644000015600001650000000404312675011365014655 0ustar jenkinsjenkins/* * Copyright 2016 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Charles Kerr */ #include #include #include class GUDevUsbMonitor::Impl { public: Impl() { const char* subsystems[] = {"android_usb", nullptr}; m_udev_client = g_udev_client_new(subsystems); g_signal_connect(m_udev_client, "uevent", G_CALLBACK(on_android_usb_event), this); } ~Impl() { g_signal_handlers_disconnect_by_data(m_udev_client, this); g_clear_object(&m_udev_client); } core::Signal& on_usb_disconnected() { return m_on_usb_disconnected; } private: static void on_android_usb_event(GUdevClient*, gchar* action, GUdevDevice* device, gpointer gself) { if (!g_strcmp0(action, "change")) if (!g_strcmp0(g_udev_device_get_property(device, "USB_STATE"), "DISCONNECTED")) static_cast(gself)->m_on_usb_disconnected(g_udev_device_get_name(device)); } core::Signal m_on_usb_disconnected; GUdevClient* m_udev_client = nullptr; }; /*** **** ***/ UsbMonitor::UsbMonitor() =default; UsbMonitor::~UsbMonitor() =default; GUDevUsbMonitor::GUDevUsbMonitor(): impl{new Impl{}} { } GUDevUsbMonitor::~GUDevUsbMonitor() =default; core::Signal& GUDevUsbMonitor::on_usb_disconnected() { return impl->on_usb_disconnected(); } ./src/exporter.cpp0000644000015600001650000001476212675011356014260 0ustar jenkinsjenkins/* * Copyright 2014 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Charles Kerr */ #include class Exporter::Impl { public: explicit Impl(const std::shared_ptr& indicator): m_indicator(indicator) { auto bus_name = g_strdup_printf("com.canonical.indicator.%s", indicator->name()); m_own_id = g_bus_own_name(G_BUS_TYPE_SESSION, bus_name, G_BUS_NAME_OWNER_FLAGS_NONE, on_bus_acquired, nullptr, on_name_lost, this, nullptr); g_free(bus_name); } ~Impl() { if (m_bus != nullptr) { for(const auto& id : m_exported_menu_ids) g_dbus_connection_unexport_menu_model(m_bus, id); if (m_exported_actions_id) g_dbus_connection_unexport_action_group(m_bus, m_exported_actions_id); } if (m_own_id) g_bus_unown_name(m_own_id); g_clear_object(&m_bus); } core::Signal& name_lost() { return m_name_lost; } private: void emit_name_lost(const char* bus_name) { m_name_lost(bus_name); } static void on_bus_acquired(GDBusConnection * connection, const gchar * name, gpointer gself) { static_cast(gself)->on_bus_acquired(connection, name); } void on_bus_acquired(GDBusConnection* connection, const gchar* /*name*/) { m_bus = G_DBUS_CONNECTION(g_object_ref(G_OBJECT(connection))); export_actions(m_indicator); for (auto& profile : m_indicator->profiles()) export_profile(m_indicator, profile); } void export_actions(const std::shared_ptr& indicator) { GError* error; char* object_path; guint id; // export the actions error = nullptr; object_path = g_strdup_printf("/com/canonical/indicator/%s", indicator->name()); id = g_dbus_connection_export_action_group(m_bus, object_path, G_ACTION_GROUP(indicator->action_group()), &error); if (id) m_exported_actions_id = id; else g_warning("Can't export action group to '%s': %s", object_path, error->message); g_clear_error(&error); g_free(object_path); } static GVariant* create_header_state(const Header& h) { GVariantBuilder b; g_variant_builder_init(&b, G_VARIANT_TYPE_VARDICT); g_variant_builder_add(&b, "{sv}", "visible", g_variant_new_boolean(h.is_visible)); if (!h.title.empty()) g_variant_builder_add(&b, "{sv}", "title", g_variant_new_string(h.title.c_str())); if (!h.label.empty()) g_variant_builder_add(&b, "{sv}", "label", g_variant_new_string(h.label.c_str())); if (!h.title.empty() || !h.label.empty()) g_variant_builder_add(&b, "{sv}", "accessible-desc", g_variant_new_string(!h.label.empty() ? h.label.c_str() : h.title.c_str())); if (h.icon) g_variant_builder_add(&b, "{sv}", "icon", g_icon_serialize(h.icon.get())); return g_variant_builder_end (&b); } void export_profile(const std::shared_ptr& indicator, const std::shared_ptr& profile) { // build the header action auto action_group = indicator->action_group(); std::string action_name = profile->name() + "-header"; auto a = g_simple_action_new_stateful(action_name.c_str(), nullptr, create_header_state(profile->header())); g_action_map_add_action(G_ACTION_MAP(action_group), G_ACTION(a)); profile->header().changed().connect([action_group,action_name](const Header& header){ auto state = create_header_state(header); char* tmp = g_variant_print(state, true); g_debug("header changed; updating action state to '%s'", tmp); g_action_group_change_action_state(G_ACTION_GROUP(action_group), action_name.c_str(), create_header_state(header)); g_free(tmp); }); // build the header menu auto detailed_action = g_strdup_printf("indicator.%s", action_name.c_str()); GMenuItem* header = g_menu_item_new(nullptr, detailed_action); g_menu_item_set_attribute(header, "x-canonical-type", "s", "com.canonical.indicator.root"); g_menu_item_set_submenu(header, profile->menu_model().get()); g_free(detailed_action); // build the menu auto menu = g_menu_new(); g_menu_append_item(menu, header); g_object_unref(header); // export the menu auto object_path = g_strdup_printf("/com/canonical/indicator/%s/%s", indicator->name(), profile->name().c_str()); GError* error = nullptr; auto id = g_dbus_connection_export_menu_model(m_bus, object_path, G_MENU_MODEL(menu), &error); if (id) m_exported_menu_ids.insert(id); else if (error != nullptr) g_warning("cannot export '%s': %s", object_path, error->message); g_free(object_path); g_clear_error(&error); //g_object_unref(menu); } static void on_name_lost(GDBusConnection * /*connection*/, const gchar * name, gpointer gthis) { static_cast(gthis)->emit_name_lost(name); } const std::string m_bus_name; core::Signal m_name_lost; std::shared_ptr m_indicator; std::set m_exported_menu_ids; guint m_own_id = 0; guint m_exported_actions_id = 0; GDBusConnection * m_bus = nullptr; }; /*** **** ***/ Exporter::Exporter(const std::shared_ptr& indicator): impl(new Impl(indicator)) { } Exporter::~Exporter() { } core::Signal& Exporter::name_lost() { return impl->name_lost(); } /*** **** ***/ ./src/greeter.cpp0000644000015600001650000001347412675011365014044 0ustar jenkinsjenkins/* * Copyright 2016 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 published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . * * Authors: * Charles Kerr */ #include #include #include class UnityGreeter::Impl { public: Impl(): m_cancellable{g_cancellable_new()} { g_bus_get(G_BUS_TYPE_SESSION, m_cancellable, on_bus_ready_static, this); } ~Impl() { g_cancellable_cancel(m_cancellable); g_clear_object(&m_cancellable); if (m_subscription_id != 0) g_dbus_connection_signal_unsubscribe (m_bus, m_subscription_id); g_clear_object(&m_bus); } core::Property& is_active() { return m_is_active; } private: static void on_bus_ready_static(GObject* /*source*/, GAsyncResult* res, gpointer gself) { GError* error {}; auto bus = g_bus_get_finish (res, &error); if (error != nullptr) { if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning("UsbSnap: Error getting session bus: %s", error->message); g_clear_error(&error); } else { static_cast(gself)->on_bus_ready(bus); } g_clear_object(&bus); } void on_bus_ready(GDBusConnection* bus) { m_bus = G_DBUS_CONNECTION(g_object_ref(G_OBJECT(bus))); g_dbus_connection_call(m_bus, DBusNames::UnityGreeter::NAME, DBusNames::UnityGreeter::PATH, DBusNames::Properties::INTERFACE, "Get", g_variant_new("(ss)", DBusNames::UnityGreeter::INTERFACE, "IsActive"), G_VARIANT_TYPE("(v)"), G_DBUS_CALL_FLAGS_NONE, -1, m_cancellable, on_get_is_active_ready, this); m_subscription_id = g_dbus_connection_signal_subscribe(m_bus, DBusNames::UnityGreeter::NAME, DBusNames::Properties::INTERFACE, DBusNames::Properties::PropertiesChanged::NAME, DBusNames::UnityGreeter::PATH, DBusNames::UnityGreeter::INTERFACE, G_DBUS_SIGNAL_FLAGS_NONE, on_properties_changed_signal, this, nullptr); } static void on_get_is_active_ready(GObject* source, GAsyncResult* res, gpointer gself) { GError* error {}; auto v = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &error); if (error != nullptr) { if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning("UsbSnap: Error getting session bus: %s", error->message); g_clear_error(&error); } else { GVariant* is_active {}; g_variant_get_child(v, 0, "v", &is_active); static_cast(gself)->m_is_active.set(g_variant_get_boolean(is_active)); g_clear_pointer(&is_active, g_variant_unref); } g_clear_pointer(&v, g_variant_unref); } static void on_properties_changed_signal(GDBusConnection* /*connection*/, const gchar* /*sender_name*/, const gchar* object_path, const gchar* interface_name, const gchar* signal_name, GVariant* parameters, gpointer gself) { g_return_if_fail(!g_strcmp0(object_path, DBusNames::UnityGreeter::PATH)); g_return_if_fail(!g_strcmp0(interface_name, DBusNames::Properties::INTERFACE)); g_return_if_fail(!g_strcmp0(signal_name, DBusNames::Properties::PropertiesChanged::NAME)); g_return_if_fail(g_variant_is_of_type(parameters, G_VARIANT_TYPE(DBusNames::Properties::PropertiesChanged::ARGS_VARIANT_TYPE))); auto v = g_variant_get_child_value (parameters, 1); gboolean is_active {}; if (g_variant_lookup(v, "IsActive", "b", &is_active)) { g_debug("%s is_active changed to %d", G_STRLOC, int(is_active)); static_cast(gself)->m_is_active.set(is_active); } g_clear_pointer(&v, g_variant_unref); } core::Property m_is_active; GCancellable* m_cancellable {}; GDBusConnection* m_bus {}; unsigned int m_subscription_id {}; }; /*** **** ***/ Greeter::Greeter() =default; Greeter::~Greeter() =default; UnityGreeter::UnityGreeter(): impl{new Impl{}} { } UnityGreeter::~UnityGreeter() =default; core::Property& UnityGreeter::is_active() { return impl->is_active(); } ./README0000644000015600001650000000007412675011356011764 0ustar jenkinsjenkinsControl of display related things, currently rotation lock. ./po/0000755000015600001650000000000012675011365011521 5ustar jenkinsjenkins./po/CMakeLists.txt0000644000015600001650000000017712675011356014266 0ustar jenkinsjenkinsinclude (Translations) add_translations_directory("${GETTEXT_PACKAGE}") add_translations_catalog("${GETTEXT_PACKAGE}" ../src/) ./po/POTFILES.in0000644000015600001650000000004712675011365013277 0ustar jenkinsjenkinssrc/rotation-lock.cpp src/usb-snap.cpp ./data/0000755000015600001650000000000012675011356012014 5ustar jenkinsjenkins./data/indicator-display.desktop.in0000644000015600001650000000034012675011356017430 0ustar jenkinsjenkins[Desktop Entry] Type=Application Name=Indicator Display Exec=@pkglibexecdir@/indicator-display-service OnlyShowIn=Unity;GNOME; NoDisplay=true StartupNotify=false Terminal=false AutostartCondition=GNOME3 unless-session gnome ./data/indicator-display.upstart.desktop.in0000644000015600001650000000026712675011356021141 0ustar jenkinsjenkins[Desktop Entry] Type=Application Name=Indicator Display Exec=@pkglibexecdir@/indicator-display-service OnlyShowIn=Unity; NoDisplay=true StartupNotify=false Terminal=false Hidden=true ./data/indicator-display.conf.in0000644000015600001650000000030512675011356016705 0ustar jenkinsjenkinsdescription "Indicator Display Backend" start on indicator-services-start stop on desktop-end or indicator-services-end respawn respawn limit 2 10 exec @pkglibexecdir@/indicator-display-service ./data/CMakeLists.txt0000644000015600001650000000472112675011356014560 0ustar jenkinsjenkins## ## Upstart Job File ## # where to install set (UPSTART_JOBS_DIR "${CMAKE_INSTALL_FULL_DATADIR}/upstart/sessions") message (STATUS "${UPSTART_JOBS_DIR} is the Upstart Jobs File install dir") set (UPSTART_JOB_NAME "${CMAKE_PROJECT_NAME}.conf") set (UPSTART_JOB_FILE "${CMAKE_CURRENT_BINARY_DIR}/${UPSTART_JOB_NAME}") set (UPSTART_JOB_FILE_IN "${CMAKE_CURRENT_SOURCE_DIR}/${UPSTART_JOB_NAME}.in") # build it set (pkglibexecdir "${CMAKE_INSTALL_FULL_PKGLIBEXECDIR}") configure_file ("${UPSTART_JOB_FILE_IN}" "${UPSTART_JOB_FILE}") # install it install (FILES "${UPSTART_JOB_FILE}" DESTINATION "${UPSTART_JOBS_DIR}") ## ## XDG Autostart File ## # where to install set (XDG_AUTOSTART_DIR "/etc/xdg/autostart") message (STATUS "${XDG_AUTOSTART_DIR} is the XDG Autostart install dir") set (XDG_AUTOSTART_NAME "${CMAKE_PROJECT_NAME}.desktop") set (XDG_AUTOSTART_FILE "${CMAKE_CURRENT_BINARY_DIR}/${XDG_AUTOSTART_NAME}") set (XDG_AUTOSTART_FILE_IN "${CMAKE_CURRENT_SOURCE_DIR}/${XDG_AUTOSTART_NAME}.in") # build it set (pkglibexecdir "${CMAKE_INSTALL_FULL_PKGLIBEXECDIR}") configure_file ("${XDG_AUTOSTART_FILE_IN}" "${XDG_AUTOSTART_FILE}") # install it install (FILES "${XDG_AUTOSTART_FILE}" DESTINATION "${XDG_AUTOSTART_DIR}") ## ## Upstart XDG Autostart Override ## # where to install set (UPSTART_XDG_AUTOSTART_DIR "${CMAKE_INSTALL_FULL_DATAROOTDIR}/upstart/xdg/autostart") message (STATUS "${UPSTART_XDG_AUTOSTART_DIR} is the Upstart XDG autostart override dir") set (UPSTART_XDG_AUTOSTART_NAME "${CMAKE_PROJECT_NAME}.upstart.desktop") set (UPSTART_XDG_AUTOSTART_FILE "${CMAKE_CURRENT_BINARY_DIR}/${UPSTART_XDG_AUTOSTART_NAME}") set (UPSTART_XDG_AUTOSTART_FILE_IN "${CMAKE_CURRENT_SOURCE_DIR}/${UPSTART_XDG_AUTOSTART_NAME}.in") # build it set (pkglibexecdir "${CMAKE_INSTALL_FULL_PKGLIBEXECDIR}") configure_file ("${UPSTART_XDG_AUTOSTART_FILE_IN}" "${UPSTART_XDG_AUTOSTART_FILE}") # install it install (FILES "${UPSTART_XDG_AUTOSTART_FILE}" DESTINATION "${UPSTART_XDG_AUTOSTART_DIR}" RENAME "${XDG_AUTOSTART_NAME}") ## ## Unity Indicator File ## # where to install set (UNITY_INDICATOR_DIR "${CMAKE_INSTALL_FULL_DATAROOTDIR}/unity/indicators") message (STATUS "${UNITY_INDICATOR_DIR} is the Unity Indicator install dir") set (UNITY_INDICATOR_NAME "com.canonical.indicator.rotation_lock") set (UNITY_INDICATOR_FILE "${CMAKE_CURRENT_SOURCE_DIR}/${UNITY_INDICATOR_NAME}") install (FILES "${UNITY_INDICATOR_FILE}" DESTINATION "${UNITY_INDICATOR_DIR}") ./data/com.canonical.indicator.rotation_lock0000644000015600001650000000037212675011356021266 0ustar jenkinsjenkins[Indicator Service] Name=indicator-rotation-lock ObjectPath=/com/canonical/indicator/rotation_lock Position=90 [phone] ObjectPath=/com/canonical/indicator/rotation_lock/phone [phone_greeter] ObjectPath=/com/canonical/indicator/rotation_lock/phone ./CMakeLists.txt0000644000015600001650000000421012675011365013640 0ustar jenkinsjenkinsproject(indicator-display LANGUAGES C CXX) cmake_minimum_required(VERSION 2.8.9) list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) set(PACKAGE ${CMAKE_PROJECT_NAME}) set(GETTEXT_PACKAGE indicator-display) add_definitions( -DGETTEXT_PACKAGE="${GETTEXT_PACKAGE}" -DGNOMELOCALEDIR="${CMAKE_INSTALL_FULL_LOCALEDIR}" ) set(SERVICE_LIB ${PACKAGE}) set(SERVICE_EXEC "${PACKAGE}-service") option(enable_tests "Build the package's automatic tests." ON) option(enable_coverage "Generate code coverage reports." ON) ## ## GNU standard paths ## include(GNUInstallDirs) if(EXISTS "/etc/debian_version") # Workaround for libexecdir on debian set(CMAKE_INSTALL_LIBEXECDIR "${CMAKE_INSTALL_LIBDIR}") set(CMAKE_INSTALL_FULL_LIBEXECDIR "${CMAKE_INSTALL_FULL_LIBDIR}") endif() set(CMAKE_INSTALL_PKGLIBEXECDIR "${CMAKE_INSTALL_LIBEXECDIR}/${CMAKE_PROJECT_NAME}") set(CMAKE_INSTALL_FULL_PKGLIBEXECDIR "${CMAKE_INSTALL_FULL_LIBEXECDIR}/${CMAKE_PROJECT_NAME}") ## ## Check for prerequisites ## # threads... set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) if(${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} LESS 3.1) set(THREAD_LINK_LIBRARIES -pthread) else() set(THREAD_LINK_LIBRARIES Threads::Threads) # introduced in cmake 3.1 endif() find_package(PkgConfig REQUIRED) # glib... set(GLIB_MINIMUM 2.36) pkg_check_modules(SERVICE_DEPS REQUIRED gio-unix-2.0>=${GLIB_MINIMUM} glib-2.0>=${GLIB_MINIMUM} gudev-1.0 ) include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS} ) ## ## Compiler settings ## include_directories(${CMAKE_CURRENT_SOURCE_DIR}) # set the compiler warnings if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") list(APPEND CXX_WARNING_ARGS -Weverything -Wno-c++98-compat -Wno-padded) elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") list(APPEND CXX_WARNING_ARGS -Wall -Wextra -Wpedantic) endif() add_compile_options(-std=c++14 -fPIC -g) ## ## Testing & Coverage ## if(${enable_tests}) enable_testing() if(${enable_coverage}) include(EnableCoverageReport) endif() endif() add_subdirectory(src) add_subdirectory(data) add_subdirectory(po) if (${enable_tests}) add_subdirectory(tests) endif ()