diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/CMakeLists.txt | 24 | ||||
-rw-r--r-- | src/adbd-client.cpp | 310 | ||||
-rw-r--r-- | src/adbd-client.h | 74 | ||||
-rw-r--r-- | src/exporter.cpp | 3 | ||||
-rw-r--r-- | src/greeter.cpp | 211 | ||||
-rw-r--r-- | src/greeter.h | 53 | ||||
-rw-r--r-- | src/indicator.h | 10 | ||||
-rw-r--r-- | src/main.cpp | 56 | ||||
-rw-r--r-- | src/rotation-lock.cpp | 418 | ||||
-rw-r--r-- | src/service.cpp | 1189 | ||||
-rw-r--r-- | src/service.h (renamed from src/rotation-lock.h) | 12 | ||||
-rw-r--r-- | src/solar.c | 206 | ||||
-rw-r--r-- | src/solar.h | 32 | ||||
-rw-r--r-- | src/usb-manager.cpp | 197 | ||||
-rw-r--r-- | src/usb-manager.h | 48 | ||||
-rw-r--r-- | src/usb-monitor.cpp | 81 | ||||
-rw-r--r-- | src/usb-monitor.h | 52 | ||||
-rw-r--r-- | src/usb-snap.cpp | 251 | ||||
-rw-r--r-- | src/usb-snap.h | 42 |
19 files changed, 1475 insertions, 1794 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b7e91aa..2788440 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,19 +4,21 @@ 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 +set (SERVICE_LIB_SOURCES + exporter.cpp + indicator.cpp + service.cpp ) +if (ENABLE_COLOR_TEMP) + list (APPEND + SERVICE_LIB_SOURCES + solar.c + ) +endif () + +add_library (${SERVICE_LIB} STATIC ${SERVICE_LIB_SOURCES}) + add_executable( ${SERVICE_EXEC} main.cpp diff --git a/src/adbd-client.cpp b/src/adbd-client.cpp deleted file mode 100644 index d5abfb5..0000000 --- a/src/adbd-client.cpp +++ /dev/null @@ -1,310 +0,0 @@ -/* - * 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 <http://www.gnu.org/licenses/>. - * - * Authors: - * Charles Kerr <charles.kerr@canonical.com> - */ - -#include <src/adbd-client.h> - -#include <gio/gio.h> -#include <gio/gunixsocketaddress.h> - -#include <algorithm> -#include <atomic> -#include <cctype> -#include <cstring> -#include <chrono> -#include <condition_variable> -#include <mutex> -#include <thread> - -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(); - if (m_worker_thread.joinable()) { - m_worker_thread.join(); - } - g_clear_object(&m_cancellable); - } - - core::Signal<const PKRequest&>& 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_, const 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<PKIdleData*>(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<PKIdleData*>(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) - { - g_debug("%s thread %p got response %d", G_STRLOC, g_thread_self(), int(response)); - - // set m_pkresponse and wake up the waiting worker thread - 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 thread %p creating a client socket to '%s'", G_STRLOC, g_thread_self(), socket_path.c_str()); - auto socket = create_client_socket(socket_path); - bool got_valid_req = false; - - g_debug("%s thread %p calling read_request", G_STRLOC, g_thread_self()); - 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 thread %p got pk [%s]", G_STRLOC, g_thread_self(), public_key.c_str()); - if (!public_key.empty()) { - got_valid_req = true; - std::unique_lock<std::mutex> lk(m_pkresponse_mutex); - m_pkresponse_ready = false; - m_pkresponse = AdbdClient::PKResponse::DENY; - 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 thread %p got response '%d', is-cancelled %d", G_STRLOC, - g_thread_self(), - 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<std::mutex> 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<checksum_len; ) { - fingerprint.append(checksum+i, checksum+i+2); - if (i < checksum_len-2) - fingerprint.append(":"); - i += 2; - } - - g_clear_pointer(&digest, g_free); - g_clear_pointer(&checksum, g_free); - return fingerprint; - } - - const std::string m_socket_path; - GCancellable* m_cancellable = nullptr; - std::thread m_worker_thread; - core::Signal<const PKRequest&> 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; - std::atomic<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<const AdbdClient::PKRequest&>& -GAdbdClient::on_pk_request() -{ - return impl->on_pk_request(); -} - diff --git a/src/adbd-client.h b/src/adbd-client.h deleted file mode 100644 index dcee2f1..0000000 --- a/src/adbd-client.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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 <http://www.gnu.org/licenses/>. - * - * Authors: - * Charles Kerr <charles.kerr@canonical.com> - */ - -#pragma once - -#include <functional> -#include <memory> -#include <string> - -#include <core/signal.h> - -/** - * 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<void(PKResponse)> respond; - }; - - virtual core::Signal<const PKRequest&>& 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<const PKRequest&>& on_pk_request() override; - -private: - class Impl; - std::unique_ptr<Impl> impl; -}; - diff --git a/src/exporter.cpp b/src/exporter.cpp index 5609ca8..c75eb5b 100644 --- a/src/exporter.cpp +++ b/src/exporter.cpp @@ -1,6 +1,6 @@ /* * Copyright 2014 Canonical Ltd. - * Copyright 2022 Robert Tari + * Copyright 2022-2023 Robert Tari * * 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 @@ -188,7 +188,6 @@ private: 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; diff --git a/src/greeter.cpp b/src/greeter.cpp deleted file mode 100644 index 02eb0be..0000000 --- a/src/greeter.cpp +++ /dev/null @@ -1,211 +0,0 @@ -/* - * 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 <http://www.gnu.org/licenses/>. - * - * Authors: - * Charles Kerr <charles.kerr@canonical.com> - */ - -#include <src/dbus-names.h> -#include <src/greeter.h> - -#include <gio/gio.h> - -class Greeter::Impl -{ -public: - - Impl() - { - m_cancellable.reset( - g_cancellable_new(), - [](GCancellable* o){ - g_cancellable_cancel(o); - g_clear_object(&o); - } - ); - - g_bus_get(G_BUS_TYPE_SESSION, m_cancellable.get(), on_bus_ready, this); - } - - ~Impl() =default; - - core::Property<State>& state() - { - return m_state; - } - -private: - - void set_state(const State& state) - { - m_state.set(state); - } - - static void on_bus_ready( - 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("Greeter: Error getting bus: %s", error->message); - g_clear_error(&error); - } - else - { - auto self = static_cast<Impl*>(gself); - - const auto watcher_id = g_bus_watch_name_on_connection( - bus, - DBusNames::Greeter::NAME, - G_BUS_NAME_WATCHER_FLAGS_AUTO_START, - on_greeter_appeared, - on_greeter_vanished, - gself, - nullptr); - - const auto subscription_id = g_dbus_connection_signal_subscribe( - bus, - DBusNames::Greeter::NAME, - DBusNames::Properties::INTERFACE, - DBusNames::Properties::PropertiesChanged::NAME, - DBusNames::Greeter::PATH, - DBusNames::Greeter::INTERFACE, - G_DBUS_SIGNAL_FLAGS_NONE, - on_properties_changed_signal, - gself, - nullptr); - - self->m_bus.reset( - bus, - [watcher_id, subscription_id](GDBusConnection* o){ - g_bus_unwatch_name(watcher_id); - g_dbus_connection_signal_unsubscribe(o, subscription_id); - g_clear_object(&o); - } - ); - } - } - - static void on_greeter_appeared( - GDBusConnection* bus, - const char* /*name*/, - const char* name_owner, - gpointer gself) - { - auto self = static_cast<Impl*>(gself); - - self->m_owner = name_owner; - - g_dbus_connection_call( - bus, - DBusNames::Greeter::NAME, - DBusNames::Greeter::PATH, - DBusNames::Properties::INTERFACE, - "Get", - g_variant_new("(ss)", DBusNames::Greeter::INTERFACE, "IsActive"), - G_VARIANT_TYPE("(v)"), - G_DBUS_CALL_FLAGS_NONE, - -1, - self->m_cancellable.get(), - on_get_is_active_ready, - gself); - } - - static void on_greeter_vanished( - GDBusConnection* /*bus*/, - const char* /*name*/, - gpointer gself) - { - auto self = static_cast<Impl*>(gself); - - self->m_owner.clear(); - self->set_state(State::UNAVAILABLE); - } - - 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("Greeter: Error getting IsActive property: %s", error->message); - } - g_clear_error(&error); - } else { - GVariant* is_active {}; - g_variant_get_child(v, 0, "v", &is_active); - static_cast<Impl*>(gself)->set_state(g_variant_get_boolean(is_active) ? State::ACTIVE : State::INACTIVE); - g_clear_pointer(&is_active, g_variant_unref); - } - g_clear_pointer(&v, g_variant_unref); - } - - static void on_properties_changed_signal( - GDBusConnection* /*bus*/, - const gchar* sender_name, - const gchar* object_path, - const gchar* interface_name, - const gchar* signal_name, - GVariant* parameters, - gpointer gself) - { - auto self = static_cast<Impl*>(gself); - - g_return_if_fail(!g_strcmp0(sender_name, self->m_owner.c_str())); - g_return_if_fail(!g_strcmp0(object_path, DBusNames::Greeter::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)) - self->set_state(is_active ? State::ACTIVE : State::INACTIVE); - g_clear_pointer(&v, g_variant_unref); - } - - core::Property<State> m_state {State::UNAVAILABLE}; - std::shared_ptr<GCancellable> m_cancellable; - std::shared_ptr<GDBusConnection> m_bus; - std::string m_owner; -}; - -/*** -**** -***/ - -BaseGreeter::BaseGreeter() =default; - -BaseGreeter::~BaseGreeter() =default; - -Greeter::Greeter(): - impl{new Impl{}} -{ -} - -Greeter::~Greeter() =default; - -core::Property<Greeter::State>& -Greeter::state() -{ - return impl->state(); -} diff --git a/src/greeter.h b/src/greeter.h deleted file mode 100644 index 0bfe026..0000000 --- a/src/greeter.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 <http://www.gnu.org/licenses/>. - * - * Authors: - * Charles Kerr <charles.kerr@canonical.com> - */ - -#pragma once - -#include <core/property.h> - -#include <memory> -#include <string> - -class BaseGreeter -{ -public: - BaseGreeter(); - virtual ~BaseGreeter(); - - enum class State { UNAVAILABLE, INACTIVE, ACTIVE }; -static inline const char* state_str(const State& state) { - static constexpr char const * state_str[] = { "Unavailable", "Inactive", "Active" }; - return state_str[int(state)]; -} - virtual core::Property<State>& state() =0; -}; - - -class Greeter: public BaseGreeter -{ -public: - Greeter(); - virtual ~Greeter(); - core::Property<State>& state() override; - -protected: - class Impl; - std::unique_ptr<Impl> impl; -}; - diff --git a/src/indicator.h b/src/indicator.h index 7379b32..db8c5ac 100644 --- a/src/indicator.h +++ b/src/indicator.h @@ -1,5 +1,6 @@ /* * Copyright 2014-2016 Canonical Ltd. + * Copyright 2023 Robert Tari * * 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 @@ -15,6 +16,7 @@ * * Authors: * Charles Kerr <charles.kerr@canonical.com> + * Robert Tari <robert@tari.in> */ #pragma once @@ -50,7 +52,7 @@ class Profile { public: virtual std::string name() const =0; - virtual const core::Property<Header>& header() const =0; + virtual core::Property<Header>& header() =0; virtual std::shared_ptr<GMenuModel> menu_model() const =0; virtual ~Profile(); @@ -63,11 +65,10 @@ class SimpleProfile: public Profile { public: SimpleProfile(const char* name, const std::shared_ptr<GMenuModel>& menu): m_name(name), m_menu(menu) {} - virtual ~SimpleProfile(); + virtual ~SimpleProfile() override; std::string name() const override {return m_name;} - core::Property<Header>& header() {return m_header;} - const core::Property<Header>& header() const override {return m_header;} + core::Property<Header>& header() override {return m_header;} std::shared_ptr<GMenuModel> menu_model() const override {return m_menu;} protected: @@ -86,4 +87,3 @@ public: virtual GSimpleActionGroup* action_group() const =0; virtual std::vector<std::shared_ptr<Profile>> profiles() const =0; }; - diff --git a/src/main.cpp b/src/main.cpp index f145504..c03a5ec 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,6 @@ /* * Copyright 2014 Canonical Ltd. + * Copyright 2022-2023 Robert Tari * * 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 @@ -15,21 +16,23 @@ * * Authors: * Charles Kerr <charles.kerr@canonical.com> + * Robert Tari <robert@tari.in> */ #include <src/exporter.h> -#include <src/rotation-lock.h> +#include <src/service.h> +#ifdef LOMIRI_FEATURES_ENABLED #include <src/greeter.h> #include <src/usb-manager.h> #include <src/usb-monitor.h> +#include <sys/stat.h> +#include <errno.h> +#endif #include <glib/gi18n.h> // bindtextdomain() #include <gio/gio.h> - #include <locale.h> -#include <sys/stat.h> -#include <errno.h> extern "C" { @@ -45,6 +48,20 @@ main(int /*argc*/, char** /*argv*/) // boilerplate i18n setlocale(LC_ALL, ""); + + // Initialize LC_NUMERIC with 'POSIX'. This assures that float number + // conversions (from float to string via e.g. g_strdup_sprintf()) always + // use a dot in decimal numbers. + // + // This resolves blackening of the screen if users with e.g. de_DE.UTF-8 + // use the brightness slider and hand over a komma-decimal to the xsct + // executable (which only understands dot-decimals). + // + // As we don't use numbers / number conversions anywhere else in the + // display indicator, this global setting of LC_NUMERIC seems to be the + // easiest approach. + setlocale(LC_NUMERIC, "POSIX"); + bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR); textdomain(GETTEXT_PACKAGE); @@ -54,44 +71,15 @@ main(int /*argc*/, char** /*argv*/) 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>()); + indicators.push_back(std::make_shared<DisplayIndicator>()); for (auto& indicator : indicators) { auto exporter = std::make_shared<Exporter>(indicator); exporter->name_lost().connect(on_name_lost); exporters.push_back(exporter); } - gboolean bHasSocket = FALSE; - - if (ayatana_common_utils_is_lomiri()) - { - struct stat cStat; - - if (stat("/dev/socket/adbd", &cStat) == 0) - { - if (S_ISSOCK(cStat.st_mode)) - { - // 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<GUDevUsbMonitor>(); - auto greeter = std::make_shared<Greeter>(); - UsbManager usb_manager {ADB_SOCKET_PATH, PUBLIC_KEYS_FILENAME, usb_monitor, greeter}; - bHasSocket = TRUE; - } - } - } - - if (bHasSocket == FALSE) - { - g_message("No /dev/socket/adbd socket found, skipping UsbManager"); - } - // let's go! g_main_loop_run(loop); diff --git a/src/rotation-lock.cpp b/src/rotation-lock.cpp deleted file mode 100644 index 1f0353f..0000000 --- a/src/rotation-lock.cpp +++ /dev/null @@ -1,418 +0,0 @@ -/* - * Copyright 2014 Canonical Ltd. - * Copyright 2022 Robert Tari - * - * 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 <http://www.gnu.org/licenses/>. - * - * Authors: - * Charles Kerr <charles.kerr@canonical.com> - * Robert Tari <robert@tari.in> - */ - -#include <src/rotation-lock.h> -#include <glib-unix.h> -#include <glib/gi18n.h> - -extern "C" -{ - #include <ayatana/common/utils.h> -} - -class RotationLockIndicator::Impl -{ -public: - - Impl() - { - GSettingsSchemaSource *pSource = g_settings_schema_source_get_default(); - - if (pSource != NULL) - { - if (ayatana_common_utils_is_lomiri()) { - - GSettingsSchema *pSchema = g_settings_schema_source_lookup(pSource, "com.lomiri.touch.system", FALSE); - - if (pSchema != NULL) - { - g_settings_schema_unref(pSchema); - m_settings = g_settings_new("com.lomiri.touch.system"); - } - else - { - g_error("No schema could be found"); - } - - } - else { - - GSettingsSchema *pSchema = g_settings_schema_source_lookup(pSource, "org.ayatana.indicator.display", FALSE); - - if (pSchema != NULL) - { - g_settings_schema_unref(pSchema); - m_settings = g_settings_new("org.ayatana.indicator.display"); - } - else - { - g_error("No schema could be found"); - } - - } - } - - m_action_group = create_action_group(); - - // build the icon - const char *rotation_lock_icon_name {"orientation-lock"}; - - if (!ayatana_common_utils_is_lomiri()) - { - rotation_lock_icon_name = "display-panel"; - } - - auto icon = g_themed_icon_new_with_default_fallbacks(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<GMenuModel> phone_menu (create_phone_menu(), menu_model_deleter); - m_phone = std::make_shared<SimpleProfile>("phone", phone_menu); - update_phone_header(); - - // build the desktop profile - std::shared_ptr<GMenuModel> desktop_menu (create_desktop_menu(), menu_model_deleter); - m_desktop = std::make_shared<SimpleProfile>("desktop", desktop_menu); - update_desktop_header(); - - g_unix_signal_add (SIGINT, onSigInt, m_settings); - onColorTemp (m_settings, "color-temp", NULL); - } - - ~Impl() - { - onColorTemp (m_settings, "color-temp", GUINT_TO_POINTER (6500)); - 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<std::shared_ptr<Profile>> profiles() - { - std::vector<std::shared_ptr<Profile>> ret; - ret.push_back(m_phone); - ret.push_back(m_desktop); - return ret; - } - -private: - - static gboolean onSigInt (gpointer pData) - { - onColorTemp (G_SETTINGS (pData), "color-temp", GUINT_TO_POINTER (6500)); - - return G_SOURCE_REMOVE; - } - - /*** - **** 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); - } - - static gboolean settingsToActionStateDouble (GValue *pValue, GVariant *pVariant, gpointer pData) - { - gdouble fVariant = (gdouble) g_variant_get_uint16 (pVariant); - GVariant *pVariantDouble = g_variant_new_double (fVariant); - g_value_set_variant (pValue, pVariantDouble); - - return TRUE; - } - - static GVariant* actionStateToSettingsInt (const GValue *pValue, const GVariantType *pVariantType, gpointer pData) - { - GVariant *pVariantDouble = g_value_get_variant (pValue); - guint16 nValue = (guint16) g_variant_get_double (pVariantDouble); - GVariant *pVariantInt = g_variant_new_uint16 (nValue); - GValue cValue = G_VALUE_INIT; - g_value_init (&cValue, G_TYPE_VARIANT); - g_value_set_variant (&cValue, pVariantInt); - - return g_value_dup_variant (&cValue); - } - - GSimpleActionGroup* create_action_group() - { - GSimpleActionGroup* group; - GSimpleAction* action; - - group = g_simple_action_group_new(); - GVariantType *pVariantType = g_variant_type_new("b"); - action = g_simple_action_new_stateful("rotation-lock", - pVariantType, - g_variant_new_boolean(false)); - g_variant_type_free(pVariantType); - 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); - - pVariantType = g_variant_type_new ("d"); - action = g_simple_action_new_stateful ("color-temp", pVariantType, g_variant_new_double (0)); - g_variant_type_free (pVariantType); - g_settings_bind_with_mapping (m_settings, "color-temp", action, "state", G_SETTINGS_BIND_DEFAULT, settingsToActionStateDouble, actionStateToSettingsInt, NULL, NULL); - g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action)); - g_object_unref(G_OBJECT (action)); - g_signal_connect (m_settings, "changed::color-temp", G_CALLBACK (onColorTemp), NULL); - - pVariantType = g_variant_type_new ("s"); - action = g_simple_action_new_stateful ("profile", pVariantType, g_variant_new_string("1")); - g_variant_type_free (pVariantType); - g_action_map_add_action(G_ACTION_MAP(group), G_ACTION(action)); - g_object_unref(G_OBJECT(action)); - - action = g_simple_action_new ("settings", NULL); - g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action)); - g_object_unref (G_OBJECT (action)); - g_signal_connect (action, "activate", G_CALLBACK (onSettings), this); - - return group; - } - - /*** - **** Phone profile - ***/ - - static void on_rotation_lock_setting_changed (gpointer gself) - { - static_cast<Impl*>(gself)->update_phone_header(); - } - - GMenuModel* create_phone_menu() - { - GMenu* menu; - GMenu* section; - GMenuItem* menu_item; - - menu = g_menu_new(); - section = g_menu_new(); - menu_item = g_menu_item_new(_("Rotation Lock"), "indicator.rotation-lock"); - g_menu_item_set_attribute(menu_item, "x-ayatana-type", "s", "org.ayatana.indicator.switch"); - g_menu_append_item(section, menu_item); - g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); - g_object_unref(section); - g_object_unref(menu_item); - - return G_MENU_MODEL(menu); - } - - static void onColorTemp (GSettings *pSettings, const gchar *sKey, gpointer pData) - { - guint16 nTemp = 0; - - if (pData) - { - nTemp = GPOINTER_TO_UINT (pData); - } - else - { - GVariant *pTemp = g_settings_get_value (pSettings, sKey); - nTemp = g_variant_get_uint16 (pTemp); - } - - GError *pError = NULL; - gchar *sCommand = g_strdup_printf ("xsct %u", nTemp); - gboolean bSuccess = g_spawn_command_line_sync (sCommand, NULL, NULL, NULL, &pError); - - if (!bSuccess) - { - g_error ("The call to '%s' failed: %s", sCommand, pError->message); - g_error_free (pError); - } - - g_free (sCommand); - } - - static void onSettings (GSimpleAction *pAction, GVariant *pVariant, gpointer pData) - { - if (ayatana_common_utils_is_mate ()) - { - ayatana_common_utils_execute_command ("mate-display-properties"); - } - else if (ayatana_common_utils_is_xfce ()) - { - ayatana_common_utils_execute_command ("xfce4-display-settings"); - } - else - { - ayatana_common_utils_execute_command ("gnome-control-center display"); - } - } - - GMenuModel* create_desktop_menu() - { - GMenu* menu; - GMenu* section; - GMenuItem* menu_item; - - menu = g_menu_new(); - - if (ayatana_common_utils_is_lomiri()) - { - section = g_menu_new(); - menu_item = g_menu_item_new(_("Rotation Lock"), "indicator.rotation-lock"); - g_menu_item_set_attribute(menu_item, "x-ayatana-type", "s", "org.ayatana.indicator.switch"); - g_menu_append_item(section, menu_item); - g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); - g_object_unref(section); - g_object_unref(menu_item); - } - else - { - section = g_menu_new (); - GIcon *pIconMin = g_themed_icon_new_with_default_fallbacks ("ayatana-indicator-display-colortemp-on"); - GIcon *pIconMax = g_themed_icon_new_with_default_fallbacks ("ayatana-indicator-display-colortemp-off"); - GVariant *pIconMinSerialised = g_icon_serialize (pIconMin); - GVariant *pIconMaxSerialised = g_icon_serialize (pIconMax); - menu_item = g_menu_item_new (_("Color temperature"), "indicator.color-temp"); - g_menu_item_set_attribute (menu_item, "x-ayatana-type", "s", "org.ayatana.indicator.slider"); - g_menu_item_set_attribute (menu_item, "x-ayatana-type", "s", "org.ayatana.indicator.slider"); - g_menu_item_set_attribute_value (menu_item, "min-icon", pIconMinSerialised); - g_menu_item_set_attribute_value (menu_item, "max-icon", pIconMaxSerialised); - g_menu_item_set_attribute (menu_item, "min-value", "d", 3500.0); - g_menu_item_set_attribute (menu_item, "max-value", "d", 6500.0); - g_menu_item_set_attribute (menu_item, "step", "d", 100.0); - g_menu_append_item (section, menu_item); - - GMenu *pMenuProfiles = g_menu_new (); - GMenuItem *pItemProfile1 = g_menu_item_new (_("Manual"), "indicator.profile::1"); - GMenuItem *pItemProfiles = g_menu_item_new_submenu (_("Color temperature profiles"), G_MENU_MODEL (pMenuProfiles)); - g_menu_append_item (pMenuProfiles, pItemProfile1); - g_object_unref (pItemProfile1); - g_menu_append_item (section, pItemProfiles); - g_object_unref (pItemProfiles); - g_object_unref (pMenuProfiles); - - g_menu_append_section (menu, NULL, G_MENU_MODEL (section)); - g_object_unref (pIconMin); - g_object_unref (pIconMax); - g_variant_unref (pIconMinSerialised); - g_variant_unref (pIconMaxSerialised); - g_object_unref (section); - g_object_unref (menu_item); - - section = g_menu_new (); - menu_item = g_menu_item_new (_("Display settingsā¦"), "indicator.settings"); - g_menu_append_item (section, menu_item); - g_menu_append_section (menu, NULL, G_MENU_MODEL (section)); - g_object_unref (section); - g_object_unref (menu_item); - } - - return G_MENU_MODEL(menu); - } - - void update_phone_header() - { - Header h; - h.title = _("Rotation"); - h.tooltip = h.title; - h.a11y = h.title; - h.is_visible = g_settings_get_boolean(m_settings, "rotation-lock"); - h.icon = m_icon; - m_phone->header().set(h); - } - - void update_desktop_header() - { - Header h; - h.title = _("Display"); - h.tooltip = _("Display settings and features"); - h.a11y = h.title; - h.is_visible = TRUE; - h.icon = m_icon; - m_desktop->header().set(h); - } - - /*** - **** - ***/ - - GSettings* m_settings = nullptr; - GSimpleActionGroup* m_action_group = nullptr; - std::shared_ptr<SimpleProfile> m_phone; - std::shared_ptr<SimpleProfile> m_desktop; - 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/service.cpp b/src/service.cpp new file mode 100644 index 0000000..b26664a --- /dev/null +++ b/src/service.cpp @@ -0,0 +1,1189 @@ +/* + * Copyright 2014 Canonical Ltd. + * Copyright 2023-2024 Robert Tari + * + * 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 <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + * Robert Tari <robert@tari.in> + */ + +#include <src/service.h> +#include <glib/gi18n.h> + +#ifdef COLOR_TEMP_ENABLED + #include <geoclue.h> +#endif + +extern "C" +{ + #include <ayatana/common/utils.h> + + #ifdef COLOR_TEMP_ENABLED + #include <act/act.h> + #include <pwd.h> + #include "solar.h" + #endif +} + +#ifdef COLOR_TEMP_ENABLED + #define GREETER_BUS_NAME "org.ayatana.greeter" + #define GREETER_BUS_PATH "/org/ayatana/greeter" + + typedef struct + { + guint nTempLow; + guint nTempHigh; + const gchar *sName; + } TempProfile; + + TempProfile m_lTempProfiles[] = + { + {0, 0, N_("Manual")}, + {4500, 6500, N_("Adaptive (Colder)")}, + {3627, 4913, N_("Adaptive")}, + {3058, 4913, N_("Adaptive (Warmer)")}, + {0, 0, NULL} + }; +#endif + +class DisplayIndicator::Impl +{ +public: + + Impl() + { +#ifdef COLOR_TEMP_ENABLED + const gchar *sTest = g_getenv ("TEST_NAME"); + this->bTest = (sTest != NULL && g_str_equal (sTest, "rotation-lock-test")); +#endif + const char *sUserName = g_get_user_name(); + this->bGreeter = g_str_equal (sUserName, "lightdm"); + GSettingsSchemaSource *pSource = g_settings_schema_source_get_default(); + + if (pSource != NULL) + { + if (ayatana_common_utils_is_lomiri()) { + + GSettingsSchema *pSchema = g_settings_schema_source_lookup(pSource, "com.lomiri.touch.system", FALSE); + + if (pSchema != NULL) + { + g_settings_schema_unref(pSchema); + m_settings = g_settings_new("com.lomiri.touch.system"); + } + else + { + g_error("No schema could be found"); + } + + } + else { + + GSettingsSchema *pSchema = g_settings_schema_source_lookup(pSource, "org.ayatana.indicator.display", FALSE); + + if (pSchema != NULL) + { + g_settings_schema_unref(pSchema); + m_settings = g_settings_new("org.ayatana.indicator.display"); + } + else + { + g_error("No schema could be found"); + } + +#ifdef COLOR_TEMP_ENABLED + this->pAccountsServiceConnection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL); + + if (!this->bGreeter) + { + const gchar *sSchema = NULL; + const gchar *sCursorSchema = NULL; + const gchar *sMetacitySchema = NULL; + + if (this->bTest) + { + sSchema = "org.ayatana.indicator.display"; + sCursorSchema = "org.ayatana.indicator.display"; + sMetacitySchema = "org.ayatana.indicator.display"; + } + else + { + if (ayatana_common_utils_is_mate ()) + { + sSchema = "org.mate.interface"; + sCursorSchema = "org.mate.peripherals-mouse"; + sMetacitySchema = "org.mate.Marco.general"; + } + else + { + sSchema = "org.gnome.desktop.interface"; + sCursorSchema = "org.gnome.desktop.interface"; + sMetacitySchema = "org.gnome.desktop.wm.preferences"; + } + } + + pSchema = g_settings_schema_source_lookup (pSource, sSchema, FALSE); + + if (pSchema != NULL) + { + g_settings_schema_unref (pSchema); + pThemeSettings = g_settings_new (sSchema); + } + else + { + g_error("No %s schema could be found", sSchema); + } + + pSchema = g_settings_schema_source_lookup (pSource, sCursorSchema, FALSE); + + if (pSchema != NULL) + { + g_settings_schema_unref (pSchema); + pCursorSettings = g_settings_new (sCursorSchema); + } + else + { + g_error("No %s schema could be found", sCursorSchema); + } + + pSchema = g_settings_schema_source_lookup (pSource, sMetacitySchema, FALSE); + + if (pSchema != NULL) + { + g_settings_schema_unref (pSchema); + pMetacitySettings = g_settings_new (sMetacitySchema); + } + else + { + g_error("No %s schema could be found", sMetacitySchema); + } + + if (this->bTest) + { + sSchema = "org.ayatana.indicator.display"; + } + else + { + sSchema = "org.gnome.desktop.interface"; + } + + pSchema = g_settings_schema_source_lookup (pSource, sSchema, FALSE); + + if (pSchema != NULL) + { + gboolean bColorScheme = g_settings_schema_has_key (pSchema, "color-scheme"); + g_settings_schema_unref (pSchema); + + if (bColorScheme) + { + pColorSchemeSettings = g_settings_new (sSchema); + } + else + { + g_warning ("org.gnome.desktop.interface::color-scheme not found. Native theme profile changes will not be triggered."); + } + } + else + { + g_error("No %s schema could be found", sSchema); + } + } + else + { + this->pConnection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); + this->nGreeterSubscription = g_dbus_connection_signal_subscribe (this->pConnection, NULL, GREETER_BUS_NAME, "UserChanged", GREETER_BUS_PATH, NULL, G_DBUS_SIGNAL_FLAGS_NONE, onUserChanged, this, NULL); + loadManager (this); + } +#endif + } + } + +#ifdef COLOR_TEMP_ENABLED + if (!this->bGreeter) + { + gint nUid = geteuid (); + getAccountsService (this, nUid); + } +#endif + m_action_group = create_action_group(); + + // build the icon + const char *icon_name {"orientation-lock"}; + + if (!ayatana_common_utils_is_lomiri()) + { + icon_name = "display-panel"; + } + + auto icon = g_themed_icon_new_with_default_fallbacks(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<GMenuModel> phone_menu (create_phone_menu(), menu_model_deleter); + m_phone = std::make_shared<SimpleProfile>("phone", phone_menu); + update_phone_header(); + + // build the desktop profile + std::shared_ptr<GMenuModel> desktop_menu (create_desktop_menu(), menu_model_deleter); + m_desktop = std::make_shared<SimpleProfile>("desktop", desktop_menu); + update_desktop_header(); + +#ifdef COLOR_TEMP_ENABLED + if (ayatana_common_utils_is_lomiri() == FALSE) + { + if (!this->bTest) + { + this->fLatitude = g_settings_get_double (this->m_settings, "latitude"); + this->fLongitude = g_settings_get_double (this->m_settings, "longitude"); + gclue_simple_new ("ayatana-indicator-display", GCLUE_ACCURACY_LEVEL_CITY, NULL, onGeoClueLoaded, this); + this->nCallback = g_timeout_add_seconds (60, updateColor, this); + updateColor (this); + } + } +#endif + } + + ~Impl() + { +#ifdef COLOR_TEMP_ENABLED + if (this->nGreeterSubscription) + { + g_dbus_connection_signal_unsubscribe (this->pConnection, this->nGreeterSubscription); + } + + if (this->lUsers) + { + g_slist_free (this->lUsers); + } + + if (this->pConnection) + { + g_object_unref (this->pConnection); + } + + if (nCallback) + { + g_source_remove (nCallback); + } + + if (sLastTheme) + { + g_free (sLastTheme); + } + + if (pThemeSettings) + { + g_clear_object (&pThemeSettings); + } + + if (pCursorSettings) + { + g_clear_object (&pCursorSettings); + } + + if (pMetacitySettings) + { + g_clear_object (&pMetacitySettings); + } + + if (pColorSchemeSettings) + { + g_clear_object (&pColorSchemeSettings); + } + + if (this->pAccountsServiceConnection) + { + g_object_unref (this->pAccountsServiceConnection); + } +#endif + + 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<std::shared_ptr<Profile>> profiles() + { + std::vector<std::shared_ptr<Profile>> ret; + ret.push_back(m_phone); + ret.push_back(m_desktop); + return ret; + } + +private: + +#ifdef COLOR_TEMP_ENABLED + static void onUserChanged (GDBusConnection *pConnection, const gchar *sSender, const gchar *sPath, const gchar *sInterface, const gchar *sSignal, GVariant *pParameters, gpointer pUserData) + { + DisplayIndicator::Impl *pImpl = static_cast<DisplayIndicator::Impl*>(pUserData); + g_variant_get (pParameters, "(s)", &pImpl->sUser); + loadManager (pImpl); + } + + static void getAccountsService (DisplayIndicator::Impl *pImpl, gint nUid) + { + pImpl->bReadingAccountsService = TRUE; + gchar *sPath = g_strdup_printf ("/org/freedesktop/Accounts/User%i", nUid); + GDBusProxy *pProxy = g_dbus_proxy_new_sync (pImpl->pAccountsServiceConnection, G_DBUS_PROXY_FLAGS_NONE, NULL, "org.freedesktop.Accounts", sPath, "org.freedesktop.DBus.Properties", NULL, NULL); + g_free (sPath); + + if (pProxy) + { + const gchar *lProperties[] = {"brightness", "color-temp", "color-temp-profile"}; + + for (gint nIndex = 0; nIndex < 3; nIndex++) + { + GVariant *pParams = g_variant_new ("(ss)", "org.ayatana.indicator.display.AccountsService", lProperties[nIndex]); + GVariant *pValue = g_dbus_proxy_call_sync (pProxy, "Get", pParams, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL); + + if (pValue) + { + GVariant *pChild0 = g_variant_get_child_value (pValue, 0); + g_variant_unref (pValue); + GVariant *pChild1 = g_variant_get_child_value (pChild0, 0); + g_variant_unref (pChild0); + g_settings_set_value (pImpl->m_settings, lProperties[nIndex], pChild1); + g_variant_unref (pChild1); + } + } + } + + pImpl->bReadingAccountsService = FALSE; + } + + static void onUserLoaded (DisplayIndicator::Impl *pImpl, ActUser *pUser) + { + g_signal_handlers_disconnect_by_func (G_OBJECT (pUser), (gpointer) G_CALLBACK (onUserLoaded), pImpl); + + if (!pImpl->sUser) + { + GError *pError = NULL; + GVariant *pGreeterUser = g_dbus_connection_call_sync (pImpl->pConnection, GREETER_BUS_NAME, GREETER_BUS_PATH, GREETER_BUS_NAME, "GetUser", NULL, G_VARIANT_TYPE ("(s)"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &pError); + + if (pError) + { + g_debug ("Failed calling GetUser, the greeter may not be ready yet: %s", pError->message); + g_error_free (pError); + + return; + } + + g_variant_get (pGreeterUser, "(s)", &pImpl->sUser); + } + + gboolean bPrefix = g_str_has_prefix (pImpl->sUser, "*"); + + if (!bPrefix) + { + const gchar *sUserName = act_user_get_user_name (pUser); + gboolean bSame = g_str_equal (pImpl->sUser, sUserName); + + if (bSame) + { + gint nUid = act_user_get_uid (pUser); + getAccountsService (pImpl, nUid); + updateColor (pImpl); + } + } + } + + static void onManagerLoaded (DisplayIndicator::Impl *pImpl) + { + ActUserManager *pManager = act_user_manager_get_default (); + + if (!pImpl->lUsers) + { + pImpl->lUsers = act_user_manager_list_users (pManager); + } + + for (GSList *lUser = pImpl->lUsers; lUser; lUser = lUser->next) + { + ActUser *pUser = static_cast<ActUser*>(lUser->data); + gboolean bLoaded = act_user_is_loaded (pUser); + + if (bLoaded) + { + onUserLoaded (pImpl, pUser); + } + else + { + g_signal_connect_swapped (pUser, "notify::is-loaded", G_CALLBACK (onUserLoaded), pImpl); + } + } + } + + static void loadManager (DisplayIndicator::Impl *pImpl) + { + ActUserManager *pManager = act_user_manager_get_default (); + gboolean bLoaded = FALSE; + g_object_get (pManager, "is-loaded", &bLoaded, NULL); + + if (bLoaded) + { + onManagerLoaded (pImpl); + } + else + { + g_signal_connect_swapped (pManager, "notify::is-loaded", G_CALLBACK (onManagerLoaded), pImpl); + } + } + + static gboolean updateColor (gpointer pData) + { + DisplayIndicator::Impl *pImpl = static_cast<DisplayIndicator::Impl*>(pData); + + if (pImpl->bReadingAccountsService) + { + return G_SOURCE_CONTINUE; + } + + guint nProfile = 0; + g_settings_get (pImpl->m_settings, "color-temp-profile", "q", &nProfile); + gdouble fBrightness = g_settings_get_double (pImpl->m_settings, "brightness"); + gchar *sThemeProfile = NULL; + gboolean bThemeAdaptive = FALSE; + + if (!pImpl->bGreeter) + { + sThemeProfile = g_settings_get_string (pImpl->m_settings, "theme-profile"); + bThemeAdaptive = g_str_equal (sThemeProfile, "adaptive"); + } + + guint nTemperature = 0; + const gchar *sColorScheme = NULL; + gchar *sTheme = NULL; + gint64 nNow = g_get_real_time (); + gdouble fElevation = solar_elevation((gdouble) nNow / 1000000.0, pImpl->fLatitude, pImpl->fLongitude); + + if (nProfile == 0) + { + g_settings_get (pImpl->m_settings, "color-temp", "q", &nTemperature); + } + else + { + gdouble fShifting = 0.0; + + if (fElevation < SOLAR_CIVIL_TWILIGHT_ELEV) + { + fShifting = 1.0; + } + else if (fElevation < 3.0) + { + fShifting = 1.0 - ((SOLAR_CIVIL_TWILIGHT_ELEV - fElevation) / (SOLAR_CIVIL_TWILIGHT_ELEV - 3.0)); + } + + nTemperature = m_lTempProfiles[nProfile].nTempHigh - (m_lTempProfiles[nProfile].nTempHigh - m_lTempProfiles[nProfile].nTempLow) * fShifting; + pImpl->bAutoSliderUpdate = TRUE; + } + + if (!pImpl->bGreeter) + { + if (!bThemeAdaptive) + { + gchar *sThemeKey = g_strdup_printf ("%s-theme", sThemeProfile); + sTheme = g_settings_get_string (pImpl->m_settings, sThemeKey); + g_free (sThemeKey); + + gboolean bLightTheme = g_str_equal (sThemeProfile, "light"); + + if (bLightTheme) + { + sColorScheme = "prefer-light"; + } + else + { + sColorScheme = "prefer-dark"; + } + } + else + { + if (fElevation < SOLAR_CIVIL_TWILIGHT_ELEV) + { + sColorScheme = "prefer-dark"; + sTheme = g_settings_get_string (pImpl->m_settings, "dark-theme"); + } + else + { + sColorScheme = "prefer-light"; + sTheme = g_settings_get_string (pImpl->m_settings, "light-theme"); + } + } + } + + if (pImpl->fLastBrightness != fBrightness || pImpl->nLasColorTemp != nTemperature) + { + g_debug ("Calling xsct with %u %f", nTemperature, fBrightness); + + GAction *pAction = g_action_map_lookup_action (G_ACTION_MAP (pImpl->m_action_group), "color-temp"); + GVariant *pTemperature = g_variant_new_double (nTemperature); + g_action_change_state (pAction, pTemperature); + + GError *pError = NULL; + gchar *sCommand = g_strdup_printf ("xsct %u %f", nTemperature, fBrightness); + gboolean bSuccess = g_spawn_command_line_sync (sCommand, NULL, NULL, NULL, &pError); + + if (!bSuccess) + { + g_error ("The call to '%s' failed: %s", sCommand, pError->message); + g_error_free (pError); + } + + pImpl->fLastBrightness = fBrightness; + pImpl->nLasColorTemp = nTemperature; + g_free (sCommand); + gint nUid = 0; + + if (!pImpl->bGreeter) + { + nUid = geteuid (); + } + else if (pImpl->sUser) + { + const struct passwd *pPasswd = getpwnam (pImpl->sUser); + + if (pPasswd) + { + nUid = pPasswd->pw_uid; + } + } + + if (nUid) + { + gchar *sPath = g_strdup_printf ("/org/freedesktop/Accounts/User%i", nUid); + GDBusProxy *pProxy = g_dbus_proxy_new_sync (pImpl->pAccountsServiceConnection, G_DBUS_PROXY_FLAGS_NONE, NULL, "org.freedesktop.Accounts", sPath, "org.freedesktop.DBus.Properties", NULL, NULL); + g_free (sPath); + GVariant *pBrightnessValue = g_variant_new ("d", pImpl->fLastBrightness); + GVariant *pBrightnessParams = g_variant_new ("(ssv)", "org.ayatana.indicator.display.AccountsService", "brightness", pBrightnessValue); + g_dbus_proxy_call (pProxy, "Set", pBrightnessParams, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); + GVariant *pColorTempValue = g_variant_new ("q", pImpl->nLasColorTemp); + GVariant *pColorTempParams = g_variant_new ("(ssv)", "org.ayatana.indicator.display.AccountsService", "color-temp", pColorTempValue); + g_dbus_proxy_call (pProxy, "Set", pColorTempParams, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); + GVariant *pProfileValue = g_variant_new ("q", nProfile); + GVariant *pProfileParams = g_variant_new ("(ssv)", "org.ayatana.indicator.display.AccountsService", "color-temp-profile", pProfileValue); + g_dbus_proxy_call (pProxy, "Set", pProfileParams, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); + } + } + + if (!pImpl->bGreeter) + { + gboolean bSameColorScheme = g_str_equal (sColorScheme, pImpl->sLastColorScheme); + + if (pImpl->pColorSchemeSettings && !bSameColorScheme) + { + g_debug ("Changing color scheme to %s", sColorScheme); + + g_settings_set_string (pImpl->pColorSchemeSettings, "color-scheme", sColorScheme); + pImpl->sLastColorScheme = sColorScheme; + } + + gboolean bSameTheme = FALSE; + + if (pImpl->sLastTheme) + { + bSameTheme = g_str_equal (pImpl->sLastTheme, sTheme); + } + + gboolean bCurrentTheme = g_str_equal ("current", sTheme); + + if (!bSameTheme && !bCurrentTheme) + { + g_debug ("Changing theme to %s", sTheme); + + g_settings_set_string (pImpl->pThemeSettings, "gtk-theme", sTheme); + gchar *sThemePath = g_strdup_printf ("/usr/share/themes/%s/index.theme", sTheme); + gboolean bThemePath = g_file_test (sThemePath, G_FILE_TEST_EXISTS); + + if (bThemePath) + { + gchar *sFile = NULL; + GError *pError = NULL; + g_file_get_contents (sThemePath, &sFile, NULL, &pError); + + if (!pError) + { + #if GLIB_CHECK_VERSION(2, 73, 0) + GRegex *pRegex = g_regex_new ("IconTheme *= *(.*)", G_REGEX_DEFAULT, G_REGEX_MATCH_DEFAULT, &pError); + #else + GRegex *pRegex = g_regex_new ("IconTheme *= *(.*)", (GRegexCompileFlags) 0, (GRegexMatchFlags) 0, &pError); + #endif + + if (!pError) + { + GMatchInfo *pMatchInfo = NULL; + + #if GLIB_CHECK_VERSION(2, 73, 0) + gboolean bMatch = g_regex_match (pRegex, sFile, G_REGEX_MATCH_DEFAULT, &pMatchInfo); + #else + gboolean bMatch = g_regex_match (pRegex, sFile, (GRegexMatchFlags) 0, &pMatchInfo); + #endif + + if (bMatch) + { + gchar *sIconTheme = g_match_info_fetch (pMatchInfo, 1); + g_settings_set_string (pImpl->pThemeSettings, "icon-theme", sIconTheme); + g_free (sIconTheme); + } + else + { + g_warning ("/usr/share/themes/%s/index.theme does not define an IconTheme", sTheme); + } + + g_match_info_free (pMatchInfo); + g_regex_unref (pRegex); + } + else + { + g_error ("PANIC: Failed to compile regex: %s", pError->message); + g_error_free (pError); + } + + #if GLIB_CHECK_VERSION(2, 73, 0) + pRegex = g_regex_new ("MetacityTheme *= *(.*)", G_REGEX_DEFAULT, G_REGEX_MATCH_DEFAULT, &pError); + #else + pRegex = g_regex_new ("MetacityTheme *= *(.*)", (GRegexCompileFlags) 0, (GRegexMatchFlags) 0, &pError); + #endif + + if (!pError) + { + GMatchInfo *pMatchInfo = NULL; + + #if GLIB_CHECK_VERSION(2, 73, 0) + gboolean bMatch = g_regex_match (pRegex, sFile, G_REGEX_MATCH_DEFAULT, &pMatchInfo); + #else + gboolean bMatch = g_regex_match (pRegex, sFile, (GRegexMatchFlags) 0, &pMatchInfo); + #endif + + if (bMatch) + { + gchar *sMetacityTheme = g_match_info_fetch (pMatchInfo, 1); + g_settings_set_string (pImpl->pMetacitySettings, "theme", sMetacityTheme); + g_free (sMetacityTheme); + } + else + { + g_warning ("/usr/share/themes/%s/index.theme does not define a MetacityTheme", sTheme); + } + + g_match_info_free (pMatchInfo); + g_regex_unref (pRegex); + } + else + { + g_error ("PANIC: Failed to compile regex: %s", pError->message); + g_error_free (pError); + } + + #if GLIB_CHECK_VERSION(2, 73, 0) + pRegex = g_regex_new ("CursorTheme *= *(.*)", G_REGEX_DEFAULT, G_REGEX_MATCH_DEFAULT, &pError); + #else + pRegex = g_regex_new ("CursorTheme *= *(.*)", (GRegexCompileFlags) 0, (GRegexMatchFlags) 0, &pError); + #endif + + if (!pError) + { + GMatchInfo *pMatchInfo = NULL; + + #if GLIB_CHECK_VERSION(2, 73, 0) + gboolean bMatch = g_regex_match (pRegex, sFile, G_REGEX_MATCH_DEFAULT, &pMatchInfo); + #else + gboolean bMatch = g_regex_match (pRegex, sFile, (GRegexMatchFlags) 0, &pMatchInfo); + #endif + + if (bMatch) + { + gchar *sCursorTheme = g_match_info_fetch (pMatchInfo, 1); + g_settings_set_string (pImpl->pCursorSettings, "cursor-theme", sTheme); + g_free (sCursorTheme); + } + else + { + g_warning ("/usr/share/themes/%s/index.theme does not define a CursorTheme", sTheme); + } + + g_match_info_free (pMatchInfo); + g_regex_unref (pRegex); + } + else + { + g_error ("PANIC: Failed to compile regex: %s", pError->message); + g_error_free (pError); + } + + g_free (sFile); + } + else + { + g_error ("PANIC: Failed to get index.theme contents: %s", pError->message); + g_error_free (pError); + } + } + else + { + g_warning ("/usr/share/themes/%s/index.theme does not exist", sTheme); + } + + g_free (sThemePath); + + if (pImpl->sLastTheme) + { + g_free (pImpl->sLastTheme); + } + + pImpl->sLastTheme = g_strdup (sTheme); + } + + g_free (sTheme); + g_free (sThemeProfile); + } + + return G_SOURCE_CONTINUE; + } + + static void onGeoClueLoaded (GObject *pObject, GAsyncResult *pResult, gpointer pData) + { + DisplayIndicator::Impl *pImpl = static_cast<DisplayIndicator::Impl*>(pData); + GError *pError = NULL; + GClueSimple *pSimple = gclue_simple_new_finish (pResult, &pError); + + if (pError != NULL) + { + g_warning ("Failed to connect to GeoClue2 service: %s", pError->message); + } + else + { + GClueLocation *pLocation = gclue_simple_get_location (pSimple); + pImpl->fLatitude = gclue_location_get_latitude (pLocation); + pImpl->fLongitude = gclue_location_get_longitude (pLocation); + g_settings_set_double (pImpl->m_settings, "latitude", pImpl->fLatitude); + g_settings_set_double (pImpl->m_settings, "longitude", pImpl->fLongitude); + } + + updateColor (pImpl); + } + + static void onColorTempSettings (GSettings *pSettings, const gchar *sKey, gpointer pData) + { + GVariant *pProfile = g_variant_new_uint16 (0); + g_settings_set_value (pSettings, "color-temp-profile", pProfile); + + updateColor (pData); + } + + static gboolean settingsIntToActionStateString (GValue *pValue, GVariant *pVariant, gpointer pData) + { + guint16 nVariant = g_variant_get_uint16 (pVariant); + gchar *sVariant = g_strdup_printf ("%u", nVariant); + GVariant *pVariantString = g_variant_new_string (sVariant); + g_free (sVariant); + g_value_set_variant (pValue, pVariantString); + + return TRUE; + } + + static GVariant* actionStateStringToSettingsInt (const GValue *pValue, const GVariantType *pVariantType, gpointer pData) + { + GVariant *pVariantString = g_value_get_variant (pValue); + const gchar *sValue = g_variant_get_string (pVariantString, NULL); + guint16 nValue = (guint16) g_ascii_strtoull (sValue, NULL, 10); + GVariant *pVariantInt = g_variant_new_uint16 (nValue); + GValue cValue = G_VALUE_INIT; + g_value_init (&cValue, G_TYPE_VARIANT); + g_value_set_variant (&cValue, pVariantInt); + + return g_value_dup_variant (&cValue); + } + + static void onColorTempState (GSimpleAction *pAction, GVariant *pVariant, gpointer pData) + { + g_simple_action_set_state (pAction, pVariant); + + DisplayIndicator::Impl *pImpl = static_cast<DisplayIndicator::Impl*>(pData); + + if (pImpl->bAutoSliderUpdate) + { + pImpl->bAutoSliderUpdate = FALSE; + + return; + } + + GVariant *pProfile = g_variant_new_uint16 (0); + g_settings_set_value (pImpl->m_settings, "color-temp-profile", pProfile); + + guint16 nTemperature = (guint16) g_variant_get_double (pVariant); + GVariant *pTemperature = g_variant_new_uint16 (nTemperature); + g_settings_set_value (pImpl->m_settings, "color-temp", pTemperature); + } +#endif + + /*** + **** 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(); + GVariantType *pVariantType = g_variant_type_new("b"); + action = g_simple_action_new_stateful("rotation-lock", + pVariantType, + g_variant_new_boolean(false)); + g_variant_type_free(pVariantType); + 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); + +#ifdef COLOR_TEMP_ENABLED + if (ayatana_common_utils_is_lomiri() == FALSE) + { + pVariantType = g_variant_type_new ("d"); + guint nTemperature = 0; + g_settings_get (this->m_settings, "color-temp", "q", &nTemperature); + action = g_simple_action_new_stateful ("color-temp", pVariantType, g_variant_new_double (nTemperature)); + g_variant_type_free (pVariantType); + g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action)); + g_signal_connect (m_settings, "changed::color-temp", G_CALLBACK (onColorTempSettings), this); + g_signal_connect (action, "change-state", G_CALLBACK (onColorTempState), this); + g_object_unref(G_OBJECT (action)); + + pVariantType = g_variant_type_new ("s"); + guint nProfile = 0; + g_settings_get (this->m_settings, "color-temp-profile", "q", &nProfile); + gchar *sProfile = g_strdup_printf ("%i", nProfile); + action = g_simple_action_new_stateful ("profile", pVariantType, g_variant_new_string (sProfile)); + g_free (sProfile); + g_variant_type_free (pVariantType); + g_settings_bind_with_mapping (this->m_settings, "color-temp-profile", action, "state", G_SETTINGS_BIND_DEFAULT, settingsIntToActionStateString, actionStateStringToSettingsInt, NULL, NULL); + 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::color-temp-profile", G_CALLBACK (updateColor), this); + + pVariantType = g_variant_type_new("d"); + gdouble fBrightness = g_settings_get_double (this->m_settings, "brightness"); + action = g_simple_action_new_stateful ("brightness", pVariantType, g_variant_new_double (fBrightness)); + g_variant_type_free(pVariantType); + g_settings_bind_with_mapping (m_settings, "brightness", action, "state", G_SETTINGS_BIND_DEFAULT, settings_to_action_state, action_state_to_settings, NULL, NULL); + 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::brightness", G_CALLBACK (updateColor), this); + + if (!this->bGreeter) + { + pVariantType = g_variant_type_new ("s"); + action = g_simple_action_new_stateful ("theme", pVariantType, g_variant_new_string ("light")); + g_variant_type_free (pVariantType); + g_settings_bind_with_mapping (this->m_settings, "theme-profile", action, "state", G_SETTINGS_BIND_DEFAULT, settings_to_action_state, action_state_to_settings, NULL, NULL); + 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::theme-profile", G_CALLBACK (updateColor), this); + g_signal_connect_swapped (m_settings, "changed::light-theme", G_CALLBACK (updateColor), this); + g_signal_connect_swapped (m_settings, "changed::dark-theme", G_CALLBACK (updateColor), this); + } + } +#endif + + if (!this->bGreeter) + { + action = g_simple_action_new ("settings", NULL); + g_action_map_add_action (G_ACTION_MAP (group), G_ACTION (action)); + g_signal_connect (action, "activate", G_CALLBACK (onSettings), this); + g_object_unref (G_OBJECT (action)); + } + + return group; + } + + /*** + **** Phone profile + ***/ + + static void on_rotation_lock_setting_changed (gpointer gself) + { + static_cast<Impl*>(gself)->update_phone_header(); + } + + GMenuModel* create_phone_menu() + { + GMenu* menu; + GMenu* section; + GMenuItem* menu_item; + + menu = g_menu_new(); + section = g_menu_new(); + menu_item = g_menu_item_new(_("Rotation Lock"), "indicator.rotation-lock(true)"); + g_menu_item_set_attribute(menu_item, "x-ayatana-type", "s", "org.ayatana.indicator.switch"); + g_menu_append_item(section, menu_item); + g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); + g_object_unref(section); + g_object_unref(menu_item); + + return G_MENU_MODEL(menu); + } + + static void onSettings (GSimpleAction *pAction, GVariant *pVariant, gpointer pData) + { + if (ayatana_common_utils_is_mate ()) + { + ayatana_common_utils_execute_command ("mate-display-properties"); + } + else if (ayatana_common_utils_is_xfce ()) + { + ayatana_common_utils_execute_command ("xfce4-display-settings"); + } + else + { + ayatana_common_utils_execute_command ("gnome-control-center display"); + } + } + + GMenuModel* create_desktop_menu() + { + GMenu* menu; + GMenu* section; + GMenuItem* menu_item; + + menu = g_menu_new(); + + if (ayatana_common_utils_is_lomiri()) + { + section = g_menu_new(); + menu_item = g_menu_item_new(_("Rotation Lock"), "indicator.rotation-lock(true)"); + g_menu_item_set_attribute(menu_item, "x-ayatana-type", "s", "org.ayatana.indicator.switch"); + g_menu_append_item(section, menu_item); + g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); + g_object_unref(section); + g_object_unref(menu_item); + } + else + { +#ifdef COLOR_TEMP_ENABLED + section = g_menu_new (); + + GIcon *pIconMin = g_themed_icon_new_with_default_fallbacks ("ayatana-indicator-display-brightness-low"); + GIcon *pIconMax = g_themed_icon_new_with_default_fallbacks ("ayatana-indicator-display-brightness-high"); + GVariant *pIconMinSerialised = g_icon_serialize (pIconMin); + GVariant *pIconMaxSerialised = g_icon_serialize (pIconMax); + menu_item = g_menu_item_new (_("Brightness"), "indicator.brightness"); + g_menu_item_set_attribute (menu_item, "x-ayatana-type", "s", "org.ayatana.indicator.slider"); + g_menu_item_set_attribute (menu_item, "x-ayatana-type", "s", "org.ayatana.indicator.slider"); + g_menu_item_set_attribute_value (menu_item, "min-icon", pIconMinSerialised); + g_menu_item_set_attribute_value (menu_item, "max-icon", pIconMaxSerialised); + g_menu_item_set_attribute (menu_item, "min-value", "d", 0.5); + g_menu_item_set_attribute (menu_item, "max-value", "d", 1.0); + g_menu_item_set_attribute (menu_item, "step", "d", 0.01); + g_menu_item_set_attribute (menu_item, "digits", "y", 2); + g_menu_append_item (section, menu_item); + + pIconMin = g_themed_icon_new_with_default_fallbacks ("ayatana-indicator-display-colortemp-on"); + pIconMax = g_themed_icon_new_with_default_fallbacks ("ayatana-indicator-display-colortemp-off"); + pIconMinSerialised = g_icon_serialize (pIconMin); + pIconMaxSerialised = g_icon_serialize (pIconMax); + menu_item = g_menu_item_new (_("Color temperature"), "indicator.color-temp"); + g_menu_item_set_attribute (menu_item, "x-ayatana-type", "s", "org.ayatana.indicator.slider"); + g_menu_item_set_attribute (menu_item, "x-ayatana-type", "s", "org.ayatana.indicator.slider"); + g_menu_item_set_attribute_value (menu_item, "min-icon", pIconMinSerialised); + g_menu_item_set_attribute_value (menu_item, "max-icon", pIconMaxSerialised); + g_menu_item_set_attribute (menu_item, "min-value", "d", 3000.0); + g_menu_item_set_attribute (menu_item, "max-value", "d", 6500.0); + g_menu_item_set_attribute (menu_item, "step", "d", 100.0); + g_menu_item_set_attribute (menu_item, "digits", "y", 0); + g_menu_append_item (section, menu_item); + + GMenu *pMenuProfiles = g_menu_new (); + GMenuItem *pItemProfiles = g_menu_item_new_submenu (_("Color temperature profile"), G_MENU_MODEL (pMenuProfiles)); + guint nProfile = 0; + + while (m_lTempProfiles[nProfile].sName != NULL) + { + gchar *sAction = g_strdup_printf ("indicator.profile::%u", nProfile); + gchar *sName = gettext (m_lTempProfiles[nProfile].sName); + GMenuItem *pItemProfile = g_menu_item_new (sName, sAction); + g_free(sAction); + g_menu_append_item (pMenuProfiles, pItemProfile); + g_object_unref (pItemProfile); + + nProfile++; + } + + g_menu_append_item (section, pItemProfiles); + g_object_unref (pItemProfiles); + g_object_unref (pMenuProfiles); + + g_menu_append_section (menu, NULL, G_MENU_MODEL (section)); + g_object_unref (pIconMin); + g_object_unref (pIconMax); + g_variant_unref (pIconMinSerialised); + g_variant_unref (pIconMaxSerialised); + g_object_unref (section); + g_object_unref (menu_item); + + if (!this->bGreeter) + { + section = g_menu_new (); + pMenuProfiles = g_menu_new (); + pItemProfiles = g_menu_item_new_submenu (_("Theme profile"), G_MENU_MODEL (pMenuProfiles)); + GMenuItem *pItemProfile = g_menu_item_new (_("Light"), "indicator.theme::light"); + g_menu_append_item (pMenuProfiles, pItemProfile); + g_object_unref (pItemProfile); + pItemProfile = g_menu_item_new (_("Dark"), "indicator.theme::dark"); + g_menu_append_item (pMenuProfiles, pItemProfile); + g_object_unref (pItemProfile); + pItemProfile = g_menu_item_new (_("Adaptive"), "indicator.theme::adaptive"); + g_menu_append_item (pMenuProfiles, pItemProfile); + g_object_unref (pItemProfile); + g_menu_append_item (section, pItemProfiles); + g_object_unref (pItemProfiles); + g_object_unref (pMenuProfiles); + g_menu_append_section (menu, NULL, G_MENU_MODEL (section)); + g_object_unref (section); + } +#endif + // cppcheck fails on !this->bGreeter + if (this->bGreeter == FALSE) + { + section = g_menu_new (); + menu_item = g_menu_item_new (_("Display settingsā¦"), "indicator.settings"); + g_menu_append_item (section, menu_item); + g_menu_append_section (menu, NULL, G_MENU_MODEL (section)); + g_object_unref (section); + g_object_unref (menu_item); + } + } + + return G_MENU_MODEL(menu); + } + + void update_phone_header() + { + Header h; + h.title = _("Rotation"); + h.tooltip = h.title; + h.a11y = h.title; + h.is_visible = g_settings_get_boolean(m_settings, "rotation-lock"); + h.icon = m_icon; + m_phone->header().set(h); + } + + void update_desktop_header() + { + Header h; + h.title = _("Display"); + h.tooltip = _("Display settings and features"); + h.a11y = h.title; + h.is_visible = TRUE; + h.icon = m_icon; + m_desktop->header().set(h); + } + + /*** + **** + ***/ + + GSettings* m_settings = nullptr; + GSimpleActionGroup* m_action_group = nullptr; + std::shared_ptr<SimpleProfile> m_phone; + std::shared_ptr<SimpleProfile> m_desktop; + std::shared_ptr<GIcon> m_icon; + gboolean bGreeter; +#ifdef COLOR_TEMP_ENABLED + gdouble fLatitude = 0.0; + gdouble fLongitude = 0.0; + gboolean bAutoSliderUpdate = FALSE; + guint nCallback = 0; + gdouble fLastBrightness = 0.0; + guint nLasColorTemp = 0; + gchar *sLastTheme = NULL; + const gchar *sLastColorScheme = "default"; + GSettings *pThemeSettings = NULL; + GSettings *pCursorSettings = NULL; + GSettings *pMetacitySettings = NULL; + GSettings *pColorSchemeSettings = NULL; + gboolean bTest; + guint nGreeterSubscription = 0; + GDBusConnection *pConnection = NULL; + gchar *sUser = NULL; + GSList *lUsers = NULL; + gboolean bReadingAccountsService = FALSE; + GDBusConnection *pAccountsServiceConnection = NULL; +#endif +}; + +/*** +**** +***/ + +DisplayIndicator::DisplayIndicator(): + impl(new Impl()) +{ +} + +DisplayIndicator::~DisplayIndicator() +{ +} + +std::vector<std::shared_ptr<Profile>> +DisplayIndicator::profiles() const +{ + return impl->profiles(); +} + +GSimpleActionGroup* +DisplayIndicator::action_group() const +{ + return impl->action_group(); +} + +const char* +DisplayIndicator::name() const +{ + return "display"; +} + +/*** +**** +***/ + diff --git a/src/rotation-lock.h b/src/service.h index 7bdfb14..f134853 100644 --- a/src/rotation-lock.h +++ b/src/service.h @@ -1,5 +1,6 @@ /* * Copyright 2014 Canonical Ltd. + * Copyright 2023 Robert Tari * * 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 @@ -15,20 +16,21 @@ * * Authors: * Charles Kerr <charles.kerr@canonical.com> + * Robert Tari <robert@tari.in> */ -#ifndef INDICATOR_DISPLAY_ROTATION_LOCK_H -#define INDICATOR_DISPLAY_ROTATION_LOCK_H +#ifndef INDICATOR_DISPLAY_SERVICE_H +#define INDICATOR_DISPLAY_SERVICE_H #include <src/indicator.h> #include <memory> // std::unique_ptr -class RotationLockIndicator: public Indicator +class DisplayIndicator: public Indicator { public: - RotationLockIndicator(); - ~RotationLockIndicator(); + DisplayIndicator(); + ~DisplayIndicator() override; const char* name() const override; GSimpleActionGroup* action_group() const override; diff --git a/src/solar.c b/src/solar.c new file mode 100644 index 0000000..d0f9d27 --- /dev/null +++ b/src/solar.c @@ -0,0 +1,206 @@ +/* + * Parts of this file have been taken from the Redshift project: + * https://github.com/jonls/redshift/ + * + * Copyright 2010 Jon Lund Steffensen + * Copyright 2023 Robert Tari + * + * 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 <http://www.gnu.org/licenses/>. + * + * Authors: + * Jon Lund Steffensen <jonlst@gmail.com> + * Robert Tari <robert@tari.in> + */ + +#include <math.h> +#include "solar.h" + +#define RAD(x) ((x)*(M_PI/180)) +#define DEG(x) ((x)*(180/M_PI)) + +/* Julian centuries since J2000.0 from Julian day */ +static double +jcent_from_jd(double jd) +{ + return (jd - 2451545.0) / 36525.0; +} + +/* Angular elevation at the location for the given hour angle. + lat: Latitude of location in degrees + decl: Declination in radians + ha: Hour angle in radians + Return: Angular elevation in radians */ +static double +elevation_from_hour_angle(double lat, double decl, double ha) +{ + return asin(cos(ha)*cos(RAD(lat))*cos(decl) + + sin(RAD(lat))*sin(decl)); +} + +/* Geometric mean anomaly of the sun. + t: Julian centuries since J2000.0 + Return: Geometric mean anomaly in radians. */ +static double +sun_geom_mean_anomaly(double t) +{ + return RAD(357.52911 + t*(35999.05029 - t*0.0001537)); +} + +/* Equation of center of the sun. + t: Julian centuries since J2000.0 + Return: Center(?) in radians */ +static double +sun_equation_of_center(double t) +{ + /* Use the first three terms of the equation. */ + double m = sun_geom_mean_anomaly(t); + double c = sin(m)*(1.914602 - t*(0.004817 + 0.000014*t)) + + sin(2*m)*(0.019993 - 0.000101*t) + + sin(3*m)*0.000289; + return RAD(c); +} + +/* Geometric mean longitude of the sun. + t: Julian centuries since J2000.0 + Return: Geometric mean logitude in radians. */ +static double +sun_geom_mean_lon(double t) +{ + /* FIXME returned value should always be positive */ + return RAD(fmod(280.46646 + t*(36000.76983 + t*0.0003032), 360)); +} + +/* True longitude of the sun. + t: Julian centuries since J2000.0 + Return: True longitude in radians */ +static double +sun_true_lon(double t) +{ + double l_0 = sun_geom_mean_lon(t); + double c = sun_equation_of_center(t); + return l_0 + c; +} + +/* Apparent longitude of the sun. (Right ascension). + t: Julian centuries since J2000.0 + Return: Apparent longitude in radians */ +static double +sun_apparent_lon(double t) +{ + double o = sun_true_lon(t); + return RAD(DEG(o) - 0.00569 - 0.00478*sin(RAD(125.04 - 1934.136*t))); +} + +/* Mean obliquity of the ecliptic + t: Julian centuries since J2000.0 + Return: Mean obliquity in radians */ +static double +mean_ecliptic_obliquity(double t) +{ + double sec = 21.448 - t*(46.815 + t*(0.00059 - t*0.001813)); + return RAD(23.0 + (26.0 + (sec/60.0))/60.0); +} + +/* Corrected obliquity of the ecliptic. + t: Julian centuries since J2000.0 + Return: Currected obliquity in radians */ +static double +obliquity_corr(double t) +{ + double e_0 = mean_ecliptic_obliquity(t); + double omega = 125.04 - t*1934.136; + return RAD(DEG(e_0) + 0.00256*cos(RAD(omega))); +} + +/* Declination of the sun. + t: Julian centuries since J2000.0 + Return: Declination in radians */ +static double +solar_declination(double t) +{ + double e = obliquity_corr(t); + double lambda = sun_apparent_lon(t); + return asin(sin(e)*sin(lambda)); +} + +/* Eccentricity of earth orbit. + t: Julian centuries since J2000.0 + Return: Eccentricity (unitless). */ +static double +earth_orbit_eccentricity(double t) +{ + return 0.016708634 - t*(0.000042037 + t*0.0000001267); +} + +/* Difference between true solar time and mean solar time. + t: Julian centuries since J2000.0 + Return: Difference in minutes */ +static double +equation_of_time(double t) +{ + double epsilon = obliquity_corr(t); + double l_0 = sun_geom_mean_lon(t); + double e = earth_orbit_eccentricity(t); + double m = sun_geom_mean_anomaly(t); + double y = pow(tan(epsilon/2.0), 2.0); + + double eq_time = y*sin(2*l_0) - 2*e*sin(m) + + 4*e*y*sin(m)*cos(2*l_0) - + 0.5*y*y*sin(4*l_0) - + 1.25*e*e*sin(2*m); + return 4*DEG(eq_time); +} + +/* Julian day from Julian centuries since J2000.0 */ +static double +jd_from_jcent(double t) +{ + return 36525.0*t + 2451545.0; +} + +/* Solar angular elevation at the given location and time. + t: Julian centuries since J2000.0 + lat: Latitude of location + lon: Longitude of location + Return: Solar angular elevation in radians */ +static double +solar_elevation_from_time(double t, double lat, double lon) +{ + /* Minutes from midnight */ + double jd = jd_from_jcent(t); + double offset = (jd - round(jd) - 0.5)*1440.0; + + double eq_time = equation_of_time(t); + double ha = RAD((720 - offset - eq_time)/4 - lon); + double decl = solar_declination(t); + return elevation_from_hour_angle(lat, decl, ha); +} + +/* Julian day from unix epoch */ +static double +jd_from_epoch(double t) +{ + return (t / 86400.0) + 2440587.5; +} + +/* Solar angular elevation at the given location and time. + date: Seconds since unix epoch + lat: Latitude of location + lon: Longitude of location + Return: Solar angular elevation in degrees */ +double +solar_elevation(double date, double lat, double lon) +{ + double jd = jd_from_epoch(date); + return DEG(solar_elevation_from_time(jcent_from_jd(jd), lat, lon)); +} diff --git a/src/solar.h b/src/solar.h new file mode 100644 index 0000000..b1faed9 --- /dev/null +++ b/src/solar.h @@ -0,0 +1,32 @@ +/* + * Parts of this file have been taken from the Redshift project: + * https://github.com/jonls/redshift/ + * + * Copyright 2010 Jon Lund Steffensen + * Copyright 2023 Robert Tari + * + * 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 <http://www.gnu.org/licenses/>. + * + * Authors: + * Jon Lund Steffensen <jonlst@gmail.com> + * Robert Tari <robert@tari.in> + */ + +#ifndef REDSHIFT_SOLAR_H +#define REDSHIFT_SOLAR_H + +#define SOLAR_CIVIL_TWILIGHT_ELEV -6.0 + +double solar_elevation(double date, double lat, double lon); + +#endif /* ! REDSHIFT_SOLAR_H */ diff --git a/src/usb-manager.cpp b/src/usb-manager.cpp deleted file mode 100644 index f83b5f1..0000000 --- a/src/usb-manager.cpp +++ /dev/null @@ -1,197 +0,0 @@ -/* - * 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 <http://www.gnu.org/licenses/>. - * - * Authors: - * Charles Kerr <charles.kerr@canonical.com> - */ - -#include <src/adbd-client.h> -#include <src/usb-manager.h> -#include <src/usb-snap.h> - -#include <glib.h> - -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <unistd.h> - -#include <set> - -class UsbManager::Impl -{ -public: - - explicit Impl( - const std::string& socket_path, - const std::string& public_keys_filename, - const std::shared_ptr<UsbMonitor>& usb_monitor, - const std::shared_ptr<Greeter>& 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*/) { - m_req.reset(); - }); - - m_greeter->state().changed().connect([this](const Greeter::State& state) { - if (state == Greeter::State::INACTIVE) { - maybe_snap(); - } else { - stop_snap(); - } - }); - - // create a new adbd 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, calling maybe_snap()", G_STRLOC, req.fingerprint.c_str()); - - m_response = AdbdClient::PKResponse::DENY; // set the fallback response - m_req.reset( - new AdbdClient::PKRequest(req), - [this](AdbdClient::PKRequest* r) { - stop_snap(); - r->respond(m_response); - delete r; - } - ); - maybe_snap(); - } - ); - } - - ~Impl() - { - if (m_request_complete_idle_tag) { - g_source_remove(m_request_complete_idle_tag); - } - } - -private: - - void stop_snap() - { - m_snap_connections.clear(); - m_snap.reset(); - } - - void maybe_snap() - { - // only prompt if there's something to prompt about - if (!m_req) { - return; - } - - // only prompt in an unlocked session - if (m_greeter->state().get() != Greeter::State::INACTIVE) { - return; - } - - snap(); - } - - void snap() - { - m_snap = std::make_shared<UsbSnap>(m_req->fingerprint); - m_snap_connections.insert((*m_snap).on_user_response().connect( - [this](AdbdClient::PKResponse response, bool remember_choice){ - - if (remember_choice && (response == AdbdClient::PKResponse::ALLOW)) { - write_public_key(m_req->public_key); - } - - m_response = response; - - // defer finishing the request into an idle func because - // ScopedConnections can't be destroyed inside their callbacks - if (m_request_complete_idle_tag == 0) { - m_request_complete_idle_tag = g_idle_add([](gpointer gself){ - auto self = static_cast<Impl*>(gself); - self->m_request_complete_idle_tag = 0; - self->m_req.reset(); - 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<UsbMonitor> m_usb_monitor; - const std::shared_ptr<Greeter> m_greeter; - - unsigned int m_request_complete_idle_tag {}; - - std::shared_ptr<GAdbdClient> m_adbd_client; - std::shared_ptr<UsbSnap> m_snap; - std::set<core::ScopedConnection> m_snap_connections; - AdbdClient::PKResponse m_response {AdbdClient::PKResponse::DENY}; - std::shared_ptr<AdbdClient::PKRequest> m_req; -}; - -/*** -**** -***/ - -UsbManager::UsbManager( - const std::string& socket_path, - const std::string& public_keys_filename, - const std::shared_ptr<UsbMonitor>& usb_monitor, - const std::shared_ptr<Greeter>& greeter -): - impl{new Impl{socket_path, public_keys_filename, usb_monitor, greeter}} -{ -} - -UsbManager::~UsbManager() -{ -} diff --git a/src/usb-manager.h b/src/usb-manager.h deleted file mode 100644 index b93992f..0000000 --- a/src/usb-manager.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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 <http://www.gnu.org/licenses/>. - * - * Authors: - * Charles Kerr <charles.kerr@canonical.com> - */ - -#pragma once - -#include <src/greeter.h> -#include <src/usb-monitor.h> - -#include <memory> -#include <string> - -/** - * 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<UsbMonitor>&, - const std::shared_ptr<Greeter>& - ); - - ~UsbManager(); - -protected: - - class Impl; - std::unique_ptr<Impl> impl; -}; diff --git a/src/usb-monitor.cpp b/src/usb-monitor.cpp deleted file mode 100644 index 5fc5a6d..0000000 --- a/src/usb-monitor.cpp +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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 <http://www.gnu.org/licenses/>. - * - * Authors: - * Charles Kerr <charles.kerr@canonical.com> - */ - -#include <src/usb-monitor.h> - -#include <glib.h> -#include <gudev/gudev.h> - -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<const std::string&>& 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<Impl*>(gself)->m_on_usb_disconnected(g_udev_device_get_name(device)); - } - - core::Signal<const std::string&> 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<const std::string&>& -GUDevUsbMonitor::on_usb_disconnected() -{ - return impl->on_usb_disconnected(); -} - diff --git a/src/usb-monitor.h b/src/usb-monitor.h deleted file mode 100644 index d9be539..0000000 --- a/src/usb-monitor.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 <http://www.gnu.org/licenses/>. - * - * Authors: - * Charles Kerr <charles.kerr@canonical.com> - */ - -#pragma once - -#include <core/signal.h> - -#include <memory> -#include <string> - -/** - * Simple interface that emits signals on USB device state changes - */ -class UsbMonitor -{ -public: - UsbMonitor(); - virtual ~UsbMonitor(); - virtual core::Signal<const std::string&>& 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<const std::string&>& on_usb_disconnected() override; - -protected: - class Impl; - std::unique_ptr<Impl> impl; -}; - diff --git a/src/usb-snap.cpp b/src/usb-snap.cpp deleted file mode 100644 index 21ca38d..0000000 --- a/src/usb-snap.cpp +++ /dev/null @@ -1,251 +0,0 @@ -/* - * 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 <http://www.gnu.org/licenses/>. - * - * Authors: - * Charles Kerr <charles.kerr@canonical.com> - */ - -#include <src/dbus-names.h> -#include <src/usb-snap.h> - -#include <glib/gi18n.h> -#include <gio/gio.h> - -/*** -**** -***/ - -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<AdbdClient::PKResponse,bool>& 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<Impl*>(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-lomiri-non-shaped-icon", g_variant_new_string("true")); - g_variant_builder_add(&hints_builder, "{sv}", "x-lomiri-snap-decisions", g_variant_new_string("true")); - g_variant_builder_add(&hints_builder, "{sv}", "x-lomiri-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<Impl*>(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<Impl*>(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); - m_notification_id = 0; - } - - 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<AdbdClient::PKResponse,bool> 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<AdbdClient::PKResponse,bool>& -UsbSnap::on_user_response() -{ - return impl->on_user_response(); -} - diff --git a/src/usb-snap.h b/src/usb-snap.h deleted file mode 100644 index 94de394..0000000 --- a/src/usb-snap.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 <http://www.gnu.org/licenses/>. - * - * Authors: - * Charles Kerr <charles.kerr@canonical.com> - */ - -#pragma once - -#include <src/adbd-client.h> // AdbdClient::PKResponse - -#include <core/signal.h> - -#include <memory> -#include <string> - -/** - * 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<AdbdClient::PKResponse,bool>& on_user_response(); - -protected: - class Impl; - std::unique_ptr<Impl> impl; -}; |