diff options
author | Charles Kerr <charles.kerr@canonical.com> | 2014-08-20 22:35:16 -0500 |
---|---|---|
committer | Charles Kerr <charles.kerr@canonical.com> | 2014-08-20 22:35:16 -0500 |
commit | 1b90575c67de3cf6459785cc18e3d661a826bece (patch) | |
tree | 6ea2f73a4556c89b8dffd761e53a5e0892d08dac /src | |
parent | ec2c7ec58b192e0b907239ad1ff840fe69b4da56 (diff) | |
download | ayatana-indicator-display-1b90575c67de3cf6459785cc18e3d661a826bece.tar.gz ayatana-indicator-display-1b90575c67de3cf6459785cc18e3d661a826bece.tar.bz2 ayatana-indicator-display-1b90575c67de3cf6459785cc18e3d661a826bece.zip |
add rotation lock indicator
Diffstat (limited to 'src')
-rw-r--r-- | src/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/exporter.cpp | 217 | ||||
-rw-r--r-- | src/exporter.h | 40 | ||||
-rw-r--r-- | src/indicator.h | 88 | ||||
-rw-r--r-- | src/main.cpp | 25 | ||||
-rw-r--r-- | src/rotation-lock.cpp | 178 | ||||
-rw-r--r-- | src/rotation-lock.h | 42 |
7 files changed, 584 insertions, 7 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 16586bf..982aa49 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,7 @@ add_definitions (-DG_LOG_DOMAIN="${CMAKE_PROJECT_NAME}") # handwritten source code... set (SERVICE_LIB_HANDWRITTEN_SOURCES + exporter.cpp rotation-lock.cpp) add_library (${SERVICE_LIB} STATIC diff --git a/src/exporter.cpp b/src/exporter.cpp new file mode 100644 index 0000000..8288b9a --- /dev/null +++ b/src/exporter.cpp @@ -0,0 +1,217 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <src/exporter.h> + +class Exporter::Impl +{ +public: + + Impl(const std::shared_ptr<Indicator>& 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_ALLOW_REPLACEMENT, + 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<std::string>& 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<Impl*>(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>& 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>& indicator, + const std::shared_ptr<Profile>& 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); + auto tmp = g_variant_print(state, true); + g_message("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<Impl*>(gthis)->emit_name_lost(name); + } + + const std::string m_bus_name; + core::Signal<std::string> m_name_lost; + std::shared_ptr<Indicator> m_indicator; + std::set<guint> 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>& indicator): + impl(new Impl(indicator)) +{ +} + +Exporter::~Exporter() +{ +} + +core::Signal<std::string>& +Exporter::name_lost() +{ + return impl->name_lost(); +} + +/*** +**** +***/ + diff --git a/src/exporter.h b/src/exporter.h new file mode 100644 index 0000000..6367f3a --- /dev/null +++ b/src/exporter.h @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <src/indicator.h> + +#include <core/signal.h> + +#include <memory> + +class Exporter +{ +public: + Exporter(const std::shared_ptr<Indicator>& indicator); + ~Exporter(); + core::Signal<std::string>& name_lost(); + +private: + class Impl; + std::unique_ptr<Impl> impl; + + Exporter(const Exporter&) =delete; + Exporter& operator=(const Exporter&) =delete; +}; + diff --git a/src/indicator.h b/src/indicator.h new file mode 100644 index 0000000..dc4df09 --- /dev/null +++ b/src/indicator.h @@ -0,0 +1,88 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#ifndef INDICATOR_DISPLAY_INDICATOR_H +#define INDICATOR_DISPLAY_INDICATOR_H + +#include <core/property.h> + +#include <gio/gio.h> // GIcon + +#include <string> +#include <vector> + +struct Header +{ + bool is_visible = false; + std::string title; + std::string label; + std::string a11y; + std::shared_ptr<GIcon> 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>& header() const =0; + virtual std::shared_ptr<GMenuModel> menu_model() const =0; + +protected: + Profile() =default; +}; + + +class SimpleProfile: public Profile +{ +public: + SimpleProfile(const char* name, const std::shared_ptr<GMenuModel>& menu): m_name(name), m_menu(menu) {} + + std::string name() const {return m_name;} + core::Property<Header>& header() {return m_header;} + const core::Property<Header>& header() const {return m_header;} + std::shared_ptr<GMenuModel> menu_model() const {return m_menu;} + +protected: + const std::string m_name; + core::Property<Header> m_header; + std::shared_ptr<GMenuModel> m_menu; +}; + + +class Indicator +{ +public: + virtual ~Indicator() =default; + + virtual const char* name() const =0; + virtual GSimpleActionGroup* action_group() const =0; + virtual std::vector<std::shared_ptr<Profile>> profiles() const =0; +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp index 5cd3581..86bdeb3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,6 +17,9 @@ * Charles Kerr <charles.kerr@canonical.com> */ +#include <src/exporter.h> +#include <src/rotation-lock.h> + #include <glib/gi18n.h> // bindtextdomain() #include <gio/gio.h> @@ -35,14 +38,22 @@ main(int /*argc*/, char** /*argv*/) 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<std::shared_ptr<Indicator>> indicators; + std::vector<std::shared_ptr<Exporter>> exporters; + indicators.push_back(std::make_shared<RotationLockIndicator>()); + for (auto& indicator : indicators) { + auto exporter = std::make_shared<Exporter>(indicator); + exporter->name_lost().connect(on_name_lost); + exporters.push_back(exporter); + } - // run until we lose the busname -// auto model = std::make_shared<MutableModel>(); - // auto world = std::shared_ptr<World>(new DBusWorld(model)); - // auto controller = std::make_shared<Controller>(model, world); - // GMenuView menu_view (model, controller); - // FIXME: listen for busname-lost - g_message("entering GMainLoop that does nothing! Woo!"); g_main_loop_run(loop); // cleanup diff --git a/src/rotation-lock.cpp b/src/rotation-lock.cpp index 0a80085..3bbe12a 100644 --- a/src/rotation-lock.cpp +++ b/src/rotation-lock.cpp @@ -17,3 +17,181 @@ * Charles Kerr <charles.kerr@canonical.com> */ +#include <src/rotation-lock.h> + +#include <glib/gi18n.h> + +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("orientation-lock"); + 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<GMenuModel> phone_menu (create_phone_menu(), menu_model_deleter); + m_phone = std::make_shared<SimpleProfile>("phone", phone_menu); + update_phone_header(); + } + + ~Impl() + { + g_clear_object(&m_action_group); + g_clear_object(&m_settings); + } + + GSimpleActionGroup* action_group() const + { + return m_action_group; + } + + std::vector<std::shared_ptr<Profile>> profiles() + { + std::vector<std::shared_ptr<Profile>> ret; + ret.push_back(m_phone); + return ret; + } + +private: + + /*** + **** Actions + ***/ + + static gboolean settings_to_action_state(GValue *value, + GVariant *variant, + gpointer /*unused*/) + { + bool is_locked = g_strcmp0(g_variant_get_string(variant, nullptr), "none"); + g_value_set_variant(value, g_variant_new_boolean(is_locked)); + return TRUE; + } + + static GVariant* action_state_to_settings(const GValue *value, + const GVariantType * /*expected_type*/, + gpointer /*unused*/) + { + // Toggling to 'on' *should* lock to the screen's current orientation. + // We don't have any way of knowing Screen.orientation in this service, + // so just pick one at random to satisfy the binding's needs. + // + // In practice this doesn't matter since the indicator isn't visible + // when the lock mode is 'none' so the end user won't ever be able + // to toggle the menuitem from None to anything else. + + auto state_is_true = g_variant_get_boolean(g_value_get_variant(value)); + return g_variant_new_string(state_is_true ? "PrimaryOrientation" : "none"); + } + + 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, "orientation-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::orientation-lock", + G_CALLBACK(on_orientation_lock_setting_changed), this); + + return group; + } + + /*** + **** Phone profile + ***/ + + static void on_orientation_lock_setting_changed (gpointer gself) + { + static_cast<Impl*>(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 lock"); + h.a11y = h.title; + h.is_visible = g_settings_get_enum(m_settings, "orientation-lock") != 0; + 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_orientation_lock_icon_name {"orientation-lock"}; + GSettings* m_settings = nullptr; + GSimpleActionGroup* m_action_group = nullptr; + std::shared_ptr<SimpleProfile> m_phone; + std::shared_ptr<GIcon> m_icon; +}; + +/*** +**** +***/ + +RotationLockIndicator::RotationLockIndicator(): + impl(new Impl()) +{ +} + +RotationLockIndicator::~RotationLockIndicator() +{ +} + +std::vector<std::shared_ptr<Profile>> +RotationLockIndicator::profiles() const +{ + return impl->profiles(); +} + +GSimpleActionGroup* +RotationLockIndicator::action_group() const +{ + return impl->action_group(); +} + +const char* +RotationLockIndicator::name() const +{ + return "rotation_lock"; +} + +/*** +**** +***/ + diff --git a/src/rotation-lock.h b/src/rotation-lock.h new file mode 100644 index 0000000..2354c4a --- /dev/null +++ b/src/rotation-lock.h @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#ifndef INDICATOR_DISPLAY_ROTATION_LOCK_H +#define INDICATOR_DISPLAY_ROTATION_LOCK_H + +#include <src/indicator.h> + +#include <memory> // std::unique_ptr + +class RotationLockIndicator: public Indicator +{ +public: + RotationLockIndicator(); + ~RotationLockIndicator(); + + const char* name() const; + GSimpleActionGroup* action_group() const; + std::vector<std::shared_ptr<Profile>> profiles() const; + +protected: + class Impl; + std::unique_ptr<Impl> impl; +}; + +#endif |