diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a1b316df..f828251c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -9,7 +9,7 @@ jobs: steps: - run: echo 'http://dl-cdn.alpinelinux.org/alpine/v3.22/community' > /etc/apk/repositories - run: echo 'http://dl-cdn.alpinelinux.org/alpine/v3.22/main' >> /etc/apk/repositories - - run: apk --no-cache add git g++ binutils pkgconf meson ninja musl-dev gtkmm4-dev vala gobject-introspection gobject-introspection-dev pulseaudio-dev libdbusmenu-glib-dev alsa-lib-dev yyjson-dev + - run: apk --no-cache add git g++ binutils pkgconf meson ninja musl-dev gtkmm4-dev vala gobject-introspection gobject-introspection-dev pulseaudio-dev libdbusmenu-glib-dev alsa-lib-dev yyjson-dev linux-pam-dev util-linux-login openssl-dev - run: echo 'http://dl-cdn.alpinelinux.org/alpine/edge/testing' >> /etc/apk/repositories - run: echo 'http://dl-cdn.alpinelinux.org/alpine/edge/main' >> /etc/apk/repositories - run: apk --no-cache add wayland-protocols wayfire-dev gtk4-layer-shell-dev gtk4-layer-shell diff --git a/data/css/default.css b/data/css/default.css index eed40763..24322657 100644 --- a/data/css/default.css +++ b/data/css/default.css @@ -146,6 +146,16 @@ animation-fill-mode: forwards; } +revealer.wf-locker { + border: 2px solid rgba(128,128,128,0.1); +} +.wf-locker .mpris image.albumart { + -gtk-icon-size:96px; +} +.wf-locker .user image{ + -gtk-icon-size:96px; +} + @keyframes embiggen { to { -gtk-icon-size: 64px; diff --git a/data/meson.build b/data/meson.build index a06c63fb..1880bc06 100644 --- a/data/meson.build +++ b/data/meson.build @@ -12,4 +12,6 @@ install_data(join_paths('icons', '256x256', 'wayfire.png'), install_dir: join_pa install_data(join_paths('icons', '512x512', 'wayfire.png'), install_dir: join_paths(get_option('prefix'), 'share', 'icons', 'hicolor', '512x512', 'apps')) install_data(join_paths('icons', 'scalable', 'wayfire.svg'), install_dir: join_paths(get_option('prefix'), 'share', 'icons', 'hicolor', 'scalable', 'apps')) +install_data('wf-locker-password', install_dir:'/etc/pam.d/') + subdir('css') \ No newline at end of file diff --git a/data/wf-locker-password b/data/wf-locker-password new file mode 100644 index 00000000..c1355e38 --- /dev/null +++ b/data/wf-locker-password @@ -0,0 +1 @@ +auth include login diff --git a/meson.build b/meson.build index f50bcee9..3c7b9a76 100644 --- a/meson.build +++ b/meson.build @@ -14,6 +14,7 @@ project( ) wayfire = dependency('wayfire') +pam = dependency('pam') wayland_client = dependency('wayland-client') wayland_protos = dependency('wayland-protocols') gtkmm = dependency('gtkmm-4.0', version: '>=4.12') @@ -25,6 +26,7 @@ dbusmenu_gtk = dependency('dbusmenu-glib-0.4') libgvc = subproject('gvc', default_options: ['static=true'], required: get_option('pulse')) xkbregistry = dependency('xkbregistry') json = subproject('wf-json').get_variable('wfjson') +openssl = dependency('openssl') if get_option('wayland-logout') == true wayland_logout = subproject('wayland-logout') diff --git a/metadata/locker.xml b/metadata/locker.xml new file mode 100644 index 00000000..bf3c7075 --- /dev/null +++ b/metadata/locker.xml @@ -0,0 +1,508 @@ + + + + <_short>Locker + Shell + + <_short>General + + + + <_short>Password + + + + + <_short>PIN + + + + + + + <_short>Fingerprint + + + + + + + <_short>Clock + + + + + + + <_short>User + + + + + + <_short>Now Playing + + + + + + + <_short>Battery + + + + + + + + + + + + + + <_short>Instant Unlock + + + + + + diff --git a/metadata/meson.build b/metadata/meson.build index eb98e8c2..18c0b544 100644 --- a/metadata/meson.build +++ b/metadata/meson.build @@ -7,3 +7,4 @@ configure_file(input: 'background.xml.in', output: 'background.xml', install_data('dock.xml', install_dir: metadata_dir) install_data('panel.xml', install_dir: metadata_dir) +install_data('locker.xml', install_dir: metadata_dir) diff --git a/src/locker/locker.cpp b/src/locker/locker.cpp new file mode 100644 index 00000000..2b4d32e8 --- /dev/null +++ b/src/locker/locker.cpp @@ -0,0 +1,224 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "css-config.hpp" +#include "lockergrid.hpp" +#include "plugin/battery.hpp" +#include "plugin/clock.hpp" +#include "plugin/instant.hpp" +#include "plugin/password.hpp" +#include "plugin/pin.hpp" +#include "plugin/fingerprint.hpp" +#include "plugin/user.hpp" +#include "plugin/volume.hpp" +#include "plugin/mpris.hpp" +#include "wf-shell-app.hpp" +#include "locker.hpp" + + +WayfireLockerApp::~WayfireLockerApp() +{} + +void WayfireLockerApp::on_activate() +{ + WayfireShellApp::on_activate(); + auto debug = Glib::getenv("WF_LOCKER_DEBUG"); + if (debug == "1") + { + m_is_debug = true; + } + + std::cout << "Locker activate" << std::endl; + lock = gtk_session_lock_instance_new(); + /* Session lock callbacks */ + g_signal_connect(lock, "locked", G_CALLBACK(on_session_locked_c), lock); + g_signal_connect(lock, "failed", G_CALLBACK(on_session_lock_failed_c), lock); + g_signal_connect(lock, "unlocked", G_CALLBACK(on_session_unlocked_c), lock); + g_signal_connect(lock, "monitor", G_CALLBACK(on_monitor_present_c), lock); + alternative_monitors = true; /* Don't use WayfireShellApp monitor tracking, we get a different set */ + new CssFromConfigString("locker/background_color", ".wf-locker {background-color:", ";}"); + new CssFromConfigFont("locker/clock_font", ".wf-locker .clock {", "}"); + new CssFromConfigFont("locker/user_font", ".wf-locker .user {", "}"); + new CssFromConfigFont("locker/pin_pad_font", ".wf-locker .pinpad-button {", "}"); + new CssFromConfigFont("locker/pin_reply_font", ".wf-locker .pinpad-current {", "}"); + new CssFromConfigFont("locker/fingerprint_font", ".wf-locker .fingerprint-text {", "}"); + new CssFromConfigFont("locker/battery_percent_font", ".wf-locker .battery-percent {", "}"); + new CssFromConfigFont("locker/battery_description_font", ".wf-locker .battery-description {", "}"); + new CssFromConfigFont("locker/instant_unlock_font", ".wf-locker .instant-unlock {", "}"); + new CssFromConfigInt("locker/battery_icon_size", ".wf-locker .battery-image {-gtk-icon-size:", "px;}"); + new CssFromConfigInt("locker/fingerprint_icon_size", ".wf-locker .fingerprint-icon {-gtk-icon-size:", + "px;}"); + + /* Init plugins */ + plugins.emplace("clock", Plugin(new WayfireLockerClockPlugin())); + plugins.emplace("battery", Plugin(new WayfireLockerBatteryPlugin())); + plugins.emplace("password", Plugin(new WayfireLockerPasswordPlugin())); + plugins.emplace("instant", (Plugin(new WayfireLockerInstantPlugin()))); + plugins.emplace("pin", Plugin(new WayfireLockerPinPlugin())); + plugins.emplace("fingerprint", Plugin(new WayfireLockerFingerprintPlugin())); + plugins.emplace("volume", Plugin(new WayfireLockerVolumePlugin())); + plugins.emplace("mpris", Plugin(new WayfireLockerMPRISPlugin())); + plugins.emplace("aboutuser", Plugin(new WayfireLockerUserPlugin())); + + for (auto& it : plugins) + { + if (it.second->should_enable()) + { + it.second->init(); + } + } + + if (is_debug()) + { + on_monitor_present(nullptr); + } else + { + /* Demand the session be locked */ + gtk_session_lock_instance_lock(lock); + } +} + +/* A new monitor has been added to the lockscreen */ +void WayfireLockerApp::on_monitor_present(GdkMonitor *monitor) +{ + int id = window_id_count; + window_id_count++; + /* Create lockscreen with a grid for contents */ + auto window = new Gtk::Window(); + window->add_css_class("wf-locker"); + auto grid = new WayfireLockerGrid(); + + window->set_child(*grid); + grid->set_expand(true); + + for (auto& it : plugins) + { + if (it.second->should_enable()) + { + it.second->add_output(id, grid); + } + } + + window->signal_close_request().connect([this, id] () + { + for (auto& it : plugins) + { + it.second->remove_output(id); + } + + return false; + }, false); + if (is_debug()) + { + window->present(); + } else + { + gtk_session_lock_instance_assign_window_to_monitor(lock, window->gobj(), monitor); + } +} + +/* Called on any successful auth to unlock & end locker */ +void WayfireLockerApp::unlock() +{ + if (is_debug()) + { + exit(0); + } + + gtk_session_lock_instance_unlock(lock); +} + +void WayfireLockerApp::create(int argc, char **argv) +{ + if (instance) + { + throw std::logic_error("Running WayfireLockerApp twice!"); + } + + instance = std::unique_ptr(new WayfireLockerApp{}); + instance->run(argc, argv); +} + +/* Starting point */ +int main(int argc, char **argv) +{ + if (!gtk_session_lock_is_supported()) + { + std::cerr << "This session does not support locking" << std::endl; + exit(0); + } + + WayfireLockerApp::create(argc, argv); + std::cout << "Exit" << std::endl; + return 0; +} + +/* lock session calllbacks */ +void on_session_locked_c(GtkSessionLockInstance *lock, void *data) +{ + std::cout << "Session locked" << std::endl; +} + +void on_session_lock_failed_c(GtkSessionLockInstance *lock, void *data) +{ + std::cout << "Session lock failed" << std::endl; + exit(0); +} + +void on_session_unlocked_c(GtkSessionLockInstance *lock, void *data) +{ + std::cout << "Session unlocked" << std::endl; + // Exiting here causes wf to lock up + Glib::signal_timeout().connect_seconds([] () -> bool + { + exit(0); + return 0; + }, 1); +} + +void on_monitor_present_c(GtkSessionLockInstance *lock, GdkMonitor *monitor, void *data) +{ + WayfireLockerApp::get().on_monitor_present(monitor); +} + +/* Find user config */ +std::string WayfireLockerApp::get_config_file() +{ + if (cmdline_config.has_value()) + { + return cmdline_config.value(); + } + + std::string config_dir; + + char *config_home = getenv("XDG_CONFIG_HOME"); + if (config_home == NULL) + { + config_dir = std::string(getenv("HOME")) + "/.config"; + } else + { + config_dir = std::string(config_home); + } + + return config_dir + "/wf-shell.ini"; +} + +Plugin WayfireLockerApp::get_plugin(std::string name) +{ + if (plugins.find(name) == plugins.end()) + { + return nullptr; + } + + return plugins.at(name); +} diff --git a/src/locker/locker.hpp b/src/locker/locker.hpp new file mode 100644 index 00000000..f0948fd2 --- /dev/null +++ b/src/locker/locker.hpp @@ -0,0 +1,56 @@ +#pragma once +#include +#include +#include +#include +#include + +#include "wf-shell-app.hpp" +#include "plugin.hpp" + +using Plugin = std::shared_ptr; +void on_session_locked_c(GtkSessionLockInstance *lock, void *data); +void on_session_lock_failed_c(GtkSessionLockInstance *lock, void *data); +void on_session_unlocked_c(GtkSessionLockInstance *lock, void *data); +void on_monitor_present_c(GtkSessionLockInstance *lock, GdkMonitor *monitor, void *data); + +class WayfireLockerApp : public WayfireShellApp +{ + private: + WayfireLockerApp(WayfireLockerApp const& copy); + WayfireLockerApp& operator =(WayfireLockerApp const& copy); + + std::string get_config_file() override; + + GtkSessionLockInstance *lock; + std::map plugins = {}; + + bool m_is_debug = false; + int window_id_count = 0; + + std::vector> css_rules; + + public: + using WayfireShellApp::WayfireShellApp; + static void create(int argc, char **argv); + static WayfireLockerApp& get() + { + return (WayfireLockerApp&)WayfireShellApp::get(); + } + + /* Starts the program. get() is valid afterward the first (and the only) + * call to create() */ + void on_monitor_present(GdkMonitor *monitor); + void on_activate() override; + bool is_debug() + { + return m_is_debug; + } + + ~WayfireLockerApp(); + + Plugin get_plugin(std::string name); + void unlock(); + + private: +}; diff --git a/src/locker/lockergrid.hpp b/src/locker/lockergrid.hpp new file mode 100644 index 00000000..089d456d --- /dev/null +++ b/src/locker/lockergrid.hpp @@ -0,0 +1,120 @@ +#pragma once +#include +#include +#include +#include + +class WayfireLockerGrid : public Gtk::CenterBox +{ + Gtk::CenterBox row1, row2, row3; + Gtk::Box box[9]; + + public: + /* Config string to box from grid */ + void attach(Gtk::Widget & widget, std::string pos_string) + { + if (pos_string == "top-left") + { + attach(widget, 0, 0); + } else if (pos_string == "top-center") + { + attach(widget, 1, 0); + } else if (pos_string == "top-right") + { + attach(widget, 2, 0); + } else if (pos_string == "center-left") + { + attach(widget, 0, 1); + } else if (pos_string == "center-center") + { + attach(widget, 1, 1); + } else if (pos_string == "center-right") + { + attach(widget, 2, 1); + } else if (pos_string == "bottom-left") + { + attach(widget, 0, 2); + } else if (pos_string == "bottom-center") + { + attach(widget, 1, 2); + } else if (pos_string == "bottom-right") + { + attach(widget, 2, 2); + } else + { + throw std::exception(); + } + } + + void attach(Gtk::Widget & widget, int col, int row) + { + if ((col > 2) || (row > 2) || (col < 0) || (row < 0)) + { + throw std::exception(); + } + if(col==0) + { + widget.set_halign(Gtk::Align::START); + } else if (col == 1) + { + widget.set_halign(Gtk::Align::CENTER); + } else if (col == 2) + { + widget.set_halign(Gtk::Align::END); + } + if(row==0) + { + widget.set_valign(Gtk::Align::START); + } else if (row == 1) + { + widget.set_valign(Gtk::Align::CENTER); + } else if (row == 2) + { + widget.set_valign(Gtk::Align::END); + } + + + box[col + (row * 3)].append(widget); + } + + WayfireLockerGrid() + { + set_start_widget(row1); + set_center_widget(row2); + set_end_widget(row3); + set_orientation(Gtk::Orientation::VERTICAL); + row1.set_start_widget(box[0]); + row1.set_center_widget(box[1]); + row1.set_end_widget(box[2]); + row2.set_start_widget(box[3]); + row2.set_center_widget(box[4]); + row2.set_end_widget(box[5]); + row3.set_start_widget(box[6]); + row3.set_center_widget(box[7]); + row3.set_end_widget(box[8]); + for (int i = 0; i < 9; i++) + { + box[i].set_orientation(Gtk::Orientation::VERTICAL); + } + + box[0].set_valign(Gtk::Align::START); + box[1].set_valign(Gtk::Align::START); + box[2].set_valign(Gtk::Align::START); + box[3].set_valign(Gtk::Align::CENTER); + box[4].set_valign(Gtk::Align::CENTER); + box[5].set_valign(Gtk::Align::CENTER); + box[6].set_valign(Gtk::Align::END); + box[7].set_valign(Gtk::Align::END); + box[8].set_valign(Gtk::Align::END); + + box[0].set_halign(Gtk::Align::START); + box[1].set_halign(Gtk::Align::CENTER); + box[2].set_halign(Gtk::Align::END); + box[3].set_halign(Gtk::Align::START); + box[4].set_halign(Gtk::Align::CENTER); + box[5].set_halign(Gtk::Align::END); + box[6].set_halign(Gtk::Align::START); + box[7].set_halign(Gtk::Align::CENTER); + box[8].set_halign(Gtk::Align::END); + } +}; diff --git a/src/locker/meson.build b/src/locker/meson.build new file mode 100644 index 00000000..1eac5805 --- /dev/null +++ b/src/locker/meson.build @@ -0,0 +1,34 @@ +widget_sources = [ + 'plugin/clock.cpp', + 'plugin/battery.cpp', + 'plugin/password.cpp', + 'plugin/instant.cpp', + 'plugin/pin.cpp', + 'plugin/fingerprint.cpp', + 'plugin/mpris.cpp', + 'plugin/user.cpp', +] +deps = [ + gtklayershell, + gtkmm, + wayland_client, + libutil, + wf_protos, + wfconfig, + xkbregistry, + json, + pam, + openssl +] + +if libpulse.found() + widget_sources += 'plugin/volume.cpp' + deps += [libpulse, libgvc] +endif + +executable( + 'wf-locker', + ['locker.cpp'] + widget_sources, + dependencies: deps, + install: true, +) diff --git a/src/locker/plugin.hpp b/src/locker/plugin.hpp new file mode 100644 index 00000000..58c644f5 --- /dev/null +++ b/src/locker/plugin.hpp @@ -0,0 +1,24 @@ +#pragma once +#include "lockergrid.hpp" +#define DEFAULT_PANEL_HEIGHT "48" +#define DEFAULT_ICON_SIZE 32 + +#define PANEL_POSITION_BOTTOM "bottom" +#define PANEL_POSITION_TOP "top" + +/* Differs to panel widgets. + * One instance of each plugin exists and it must account for + * multiple widgets in multiple windows. */ +class WayfireLockerPlugin +{ + public: + /* If true, plugin should be instantiated, init called, and used in lock screen + * If false, plugin will be instantiated but never init'ed. + * + * We *do not* do config reloading. Sample config on construction or init, but not live */ + virtual bool should_enable() = 0; + virtual void add_output(int id, WayfireLockerGrid *grid) = 0; + virtual void remove_output(int id) = 0; + virtual void init() = 0; + virtual ~WayfireLockerPlugin() = default; +}; diff --git a/src/locker/plugin/battery.cpp b/src/locker/plugin/battery.cpp new file mode 100644 index 00000000..8f8017e0 --- /dev/null +++ b/src/locker/plugin/battery.cpp @@ -0,0 +1,287 @@ +#include +#include +#include +#include + +#include "lockergrid.hpp" +#include "battery.hpp" + + +static const std::string BATTERY_STATUS_ICON = "icon"; // icon +static const std::string BATTERY_STATUS_PERCENT = "percentage"; // icon + percentage +static const std::string BATTERY_STATUS_FULL = "full"; // icon + percentage + TimeToFull/TimeToEmpty + +static bool is_charging(uint32_t state) +{ + return (state == 1) || (state == 5); +} + +static bool is_discharging(uint32_t state) +{ + return (state == 2) || (state == 6); +} + +static std::string state_descriptions[] = { + "Unknown", // 0 + "Charging", // 1 + "Discharging", // 2 + "Empty", // 3 + "Fully charged", // 4 + "Pending charge", // 5 + "Pending discharge", // 6 +}; + +static std::string get_device_type_description(uint32_t type) +{ + if (type == 2) + { + return "Battery "; + } + + if (type == 3) + { + return "UPS "; + } + + return ""; +} + +static std::string format_digit(int digit) +{ + return digit <= 9 ? ("0" + std::to_string(digit)) : + std::to_string(digit); +} + +static std::string uint_to_time(int64_t time) +{ + int hrs = time / 3600; + int min = (time / 60) % 60; + + return format_digit(hrs) + ":" + format_digit(min); +} + +bool WayfireLockerBatteryPlugin::should_enable() +{ + return (bool)enable; +} + +void WayfireLockerBatteryPlugin::update_percentages(std::string text) +{ + for (auto& it : labels) + { + it.second->set_label(text); + } +} + +void WayfireLockerBatteryPlugin::update_descriptions(std::string text) +{ + for (auto& it : subtexts) + { + it.second->set_label(text); + } +} + +void WayfireLockerBatteryPlugin::update_images() +{ + Glib::Variant icon_name; + display_device->get_cached_property(icon_name, ICON); + for (auto& it : images) + { + it.second->set_from_icon_name(icon_name.get()); + } +} + +void WayfireLockerBatteryPlugin::add_output(int id, WayfireLockerGrid *grid) +{ + Gtk::Grid *batt_grid = new Gtk::Grid(); + labels.emplace(id, std::shared_ptr(new Gtk::Label())); + subtexts.emplace(id, std::shared_ptr(new Gtk::Label())); + images.emplace(id, std::shared_ptr(new Gtk::Image())); + auto label = labels[id]; + auto subtext = subtexts[id]; + auto image = images[id]; + + if (!show_state) + { + label->hide(); + subtext->hide(); + image->hide(); + } + + label->add_css_class("battery-percent"); + subtext->add_css_class("battery-description"); + image->add_css_class("battery-image"); + + batt_grid->attach(*image, 0, 0); + batt_grid->attach(*label, 1, 0); + batt_grid->attach(*subtext, 0, 1, 2, 1); + + grid->attach(*batt_grid, (std::string)battery_position); + + update_details(); +} + +void WayfireLockerBatteryPlugin::remove_output(int id) +{ + labels.erase(id); +} + +void WayfireLockerBatteryPlugin::init() +{ + if (!setup_dbus()) + { + return; + } +} + +bool WayfireLockerBatteryPlugin::setup_dbus() +{ + auto cancellable = Gio::Cancellable::create(); + connection = Gio::DBus::Connection::get_sync(Gio::DBus::BusType::SYSTEM, cancellable); + if (!connection) + { + std::cerr << "Failed to connect to dbus" << std::endl; + return false; + } + + upower_proxy = Gio::DBus::Proxy::create_sync(connection, UPOWER_NAME, + "/org/freedesktop/UPower", + "org.freedesktop.UPower"); + if (!upower_proxy) + { + std::cerr << "Failed to connect to UPower" << std::endl; + return false; + } + + display_device = Gio::DBus::Proxy::create_sync(connection, + UPOWER_NAME, + DISPLAY_DEVICE, + "org.freedesktop.UPower.Device"); + if (!display_device) + { + return false; + } + + Glib::Variant present; + display_device->get_cached_property(present, SHOULD_DISPLAY); + if (present.get()) + { + display_device->signal_properties_changed().connect( + sigc::mem_fun(*this, &WayfireLockerBatteryPlugin::on_properties_changed)); + + return true; + } + + return false; +} + +void WayfireLockerBatteryPlugin::on_properties_changed( + const Gio::DBus::Proxy::MapChangedProperties& properties, + const std::vector& invalidated) +{ + bool invalid_icon = false, invalid_details = false; + for (auto& prop : properties) + { + if (prop.first == ICON) + { + invalid_icon = true; + } + + if ((prop.first == TYPE) || (prop.first == STATE) || (prop.first == PERCENTAGE) || + (prop.first == TIMETOFULL) || (prop.first == TIMETOEMPTY)) + { + invalid_details = true; + } + + if (prop.first == SHOULD_DISPLAY) + {} + } + + if (invalid_icon) + { + update_images(); + } + + if (invalid_details) + { + update_details(); + } +} + +void WayfireLockerBatteryPlugin::update_details() +{ + Glib::Variant type; + display_device->get_cached_property(type, TYPE); + + Glib::Variant vstate; + display_device->get_cached_property(vstate, STATE); + uint32_t state = vstate.get(); + + Glib::Variant vpercentage; + display_device->get_cached_property(vpercentage, PERCENTAGE); + auto percentage_string = std::to_string((int)vpercentage.get()) + "%"; + + Glib::Variant time_to_full; + display_device->get_cached_property(time_to_full, TIMETOFULL); + + Glib::Variant time_to_empty; + display_device->get_cached_property(time_to_empty, TIMETOEMPTY); + + std::string description = state_descriptions[state]; + if (is_charging(state)) + { + description += ", " + uint_to_time(time_to_full.get()) + " until full"; + } else if (is_discharging(state)) + { + description += ", " + uint_to_time(time_to_empty.get()) + " remaining"; + } + + if (state == 0) /* Unknown */ + { + hide(); + return; + } + + show(); + update_descriptions(get_device_type_description(type.get()) + description); + update_percentages(percentage_string); + update_images(); +} + +void WayfireLockerBatteryPlugin::hide() +{ + show_state = false; + for (auto& it : labels) + { + it.second->hide(); + } + + for (auto& it : images) + { + it.second->hide(); + } + + for (auto & it : subtexts) + { + it.second->hide(); + } +} + +void WayfireLockerBatteryPlugin::show() +{ + show_state = true; + for (auto& it : labels) + { + it.second->show(); + } + + for (auto& it : images) + { + it.second->show(); + } + + for (auto& it : subtexts) + { + it.second->show(); + } +} diff --git a/src/locker/plugin/battery.hpp b/src/locker/plugin/battery.hpp new file mode 100644 index 00000000..25b19142 --- /dev/null +++ b/src/locker/plugin/battery.hpp @@ -0,0 +1,61 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +#include "../plugin.hpp" +#include "../../util/wf-option-wrap.hpp" +#include "lockergrid.hpp" + +using DBusConnection = Glib::RefPtr; +using DBusProxy = Glib::RefPtr; +/* Shamelessly copied in from battery in panel */ + +#define UPOWER_NAME "org.freedesktop.UPower" +#define DISPLAY_DEVICE "/org/freedesktop/UPower/devices/DisplayDevice" + +#define ICON "IconName" +#define TYPE "Type" +#define STATE "State" +#define PERCENTAGE "Percentage" +#define TIMETOFULL "TimeToFull" +#define TIMETOEMPTY "TimeToEmpty" +#define SHOULD_DISPLAY "IsPresent" + +class WayfireLockerBatteryPlugin : public WayfireLockerPlugin +{ + private: + DBusConnection connection; + DBusProxy upower_proxy, display_device; + void on_properties_changed( + const Gio::DBus::Proxy::MapChangedProperties& properties, + const std::vector& invalidated); + bool setup_dbus(); + + public: + WayfireLockerBatteryPlugin() + {} + void add_output(int id, WayfireLockerGrid *grid) override; + void remove_output(int id) override; + bool should_enable() override; + void init() override; + void hide(); + void show(); + bool show_state = true; + + WfOption battery_position{"locker/battery_position"}; + WfOption enable{"locker/battery_enable"}; + + void update_percentages(std::string text); + void update_descriptions(std::string text); + void update_images(); + void update_details(); + + std::unordered_map> images; + std::unordered_map> subtexts; + std::unordered_map> labels; +}; diff --git a/src/locker/plugin/clock.cpp b/src/locker/plugin/clock.cpp new file mode 100644 index 00000000..dbc098b2 --- /dev/null +++ b/src/locker/plugin/clock.cpp @@ -0,0 +1,70 @@ +#include +#include +#include + +#include "lockergrid.hpp" +#include "clock.hpp" + +bool WayfireLockerClockPlugin::should_enable() +{ + return (bool)enable; +} + +void WayfireLockerClockPlugin::update_labels(std::string text) +{ + for (auto& it : labels) + { + it.second->set_markup(text); + } + + label_contents = text; +} + +void WayfireLockerClockPlugin::add_output(int id, WayfireLockerGrid *grid) +{ + labels.emplace(id, std::shared_ptr(new Gtk::Label())); + auto label = labels[id]; + label->add_css_class("clock"); + label->set_label(label_contents); + label->set_justify(Gtk::Justification::CENTER); + + grid->attach(*label, WfOption{"locker/clock_position"}); +} + +void WayfireLockerClockPlugin::remove_output(int id) +{ + labels.erase(id); +} + +void WayfireLockerClockPlugin::update_time() +{ + auto time = Glib::DateTime::create_now_local(); + auto text = time.format((std::string)format); + + /* Sometimes GLib::DateTime will add leading spaces. This results in + * unevenly balanced padding around the text, which looks quite bad. + * + * This could be circumvented with the modifiers the user passes to the + * format string, * but to remove the requirement that the user does + * something fancy, we just remove any leading spaces. */ + int i = 0; + while (i < (int)text.length() && text[i] == ' ') + { + i++; + } + + this->update_labels(text.substr(i)); +} + +WayfireLockerClockPlugin::WayfireLockerClockPlugin() +{} + +void WayfireLockerClockPlugin::init() +{ + timeout = Glib::signal_timeout().connect_seconds( + [this] () + { + this->update_time(); + return G_SOURCE_CONTINUE; + }, 1); +} diff --git a/src/locker/plugin/clock.hpp b/src/locker/plugin/clock.hpp new file mode 100644 index 00000000..d87a87af --- /dev/null +++ b/src/locker/plugin/clock.hpp @@ -0,0 +1,27 @@ +#pragma once +#include +#include + +#include "../plugin.hpp" +#include "../../util/wf-option-wrap.hpp" +#include "lockergrid.hpp" + +class WayfireLockerClockPlugin : public WayfireLockerPlugin +{ + public: + WayfireLockerClockPlugin(); + void add_output(int id, WayfireLockerGrid *grid) override; + void remove_output(int id) override; + bool should_enable() override; + void init() override; + + WfOption enable{"locker/clock_enable"}; + WfOption format{"locker/clock_format"}; + + sigc::connection timeout; + void update_labels(std::string text); + void update_time(); + + std::unordered_map> labels; + std::string label_contents = ""; +}; diff --git a/src/locker/plugin/fingerprint.cpp b/src/locker/plugin/fingerprint.cpp new file mode 100644 index 00000000..0b2ef6c1 --- /dev/null +++ b/src/locker/plugin/fingerprint.cpp @@ -0,0 +1,291 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../util/wf-option-wrap.hpp" +#include "locker.hpp" +#include "lockergrid.hpp" +#include "fingerprint.hpp" + +/** + * Fingerprint Reader Auth + * + * Based on fprintd DBUS Spec: + * https://fprint.freedesktop.org/fprintd-dev/ + */ + +WayfireLockerFingerprintPlugin::WayfireLockerFingerprintPlugin() : + dbus_name_id(Gio::DBus::own_name(Gio::DBus::BusType::SYSTEM, + "net.reactivated.Fprint", + sigc::mem_fun(*this, &WayfireLockerFingerprintPlugin::on_bus_acquired))), + enable(WfOption{"locker/fingerprint_enable"}) +{} + +WayfireLockerFingerprintPlugin::~WayfireLockerFingerprintPlugin() +{ + if (device_proxy) + { + device_proxy->call_sync("Release"); + } +} + +void WayfireLockerFingerprintPlugin::on_bus_acquired(const Glib::RefPtr & connection, + const Glib::ustring & name) +{ + /* Bail here if config has this disabled */ + if (!enable) + { + return; + } + + Gio::DBus::Proxy::create(connection, + "net.reactivated.Fprint", + "/net/reactivated/Fprint/Manager", + "net.reactivated.Fprint.Manager", + [this, connection] (const Glib::RefPtr & result) + { + auto manager_proxy = Gio::DBus::Proxy::create_finish(result); + try { + auto default_device = manager_proxy->call_sync("GetDefaultDevice"); + Glib::Variant item_path; + default_device.get_child(item_path, 0); + Gio::DBus::Proxy::create(connection, + "net.reactivated.Fprint", + item_path.get(), + "net.reactivated.Fprint.Device", + sigc::mem_fun(*this, &WayfireLockerFingerprintPlugin::on_device_acquired)); + } catch (Glib::Error & e) /* TODO : Narrow down? */ + { + enable = false; + hide(); + return; + } + }); +} + +void WayfireLockerFingerprintPlugin::on_device_acquired(const Glib::RefPtr & result) +{ + device_proxy = Gio::DBus::Proxy::create_finish(result); + update_labels("Listing fingers..."); + char *username = getlogin(); + auto reply = device_proxy->call_sync("ListEnrolledFingers", nullptr, + Glib::Variant>::create(username)); + Glib::Variant> array; + reply.get_child(array, 0); + + if (array.get_n_children() > 0) + { + // User has at least one fingerprint on file! + show(); + update_labels("Fingerprint Ready"); + update_image("fingerprint"); + } else + { + // Zero fingers for this user. + show(); + update_labels("No fingerprints enrolled"); + update_image("nofingerprint"); + // Don't hide entirely, allow the user to see this specific fail + return; + } + + Glib::Variant finger; + reply.get_child(finger, 0); + update_labels("Finger print device found"); + + /* Attach a listener now, useful when we start scanning */ + device_proxy->signal_signal().connect([this] (const Glib::ustring & sender_name, + const Glib::ustring & signal_name, + const Glib::VariantContainerBase & params) + { + if (signal_name == "VerifyStatus") + { + Glib::Variant mesg; + Glib::Variant done; + params.get_child(mesg, 0); + params.get_child(done, 1); + bool is_done = done.get(); + + update_labels(mesg.get()); + if (mesg.get() == "verify-match") + { + WayfireLockerApp::get().unlock(); + } + + if (mesg.get() == "verify-no-match") + { + /* Reschedule fingerprint scan */ + Glib::signal_timeout().connect_seconds( + [this] () + { + this->start_fingerprint_scanning(); + return G_SOURCE_REMOVE; + }, 5); + show(); + update_image("nofingerprint"); + update_labels("Invalid fingerprint"); + } else if (mesg.get() == "verify-unknown-error") + { + is_done = true; + /* Reschedule fingerprint scan */ + Glib::signal_timeout().connect_seconds( + [this] () + { + this->start_fingerprint_scanning(); + return G_SOURCE_REMOVE; + }, 5); + } + + if (is_done) + { + is_scanning = false; + device_proxy->call_sync("VerifyStop"); + } + } + }, false); + claim_device(); +} + +void WayfireLockerFingerprintPlugin::claim_device() +{ + try { + char *username = getlogin(); + device_proxy->call_sync("Claim", nullptr, + Glib::Variant>::create({username})); + /* Start scanning in 5 seconds */ + Glib::signal_timeout().connect_seconds( + [this] () + { + this->start_fingerprint_scanning(); + return G_SOURCE_REMOVE; + }, 5); + } catch (Glib::Error & e) /* TODO : Narrow down? */ + { + std::cout << "Fingerprint device already claimed, try in 5s" << std::endl; + update_labels("Fingerprint reader busy..."); + Glib::signal_timeout().connect_seconds( + [this] () + { + this->claim_device(); + return G_SOURCE_REMOVE; + }, 5); + } + + /* Start fingerprint reader 5 seconds after start + * This fixes an issue where the fingerprint reader + * is a button which locks the screen */ +} + +void WayfireLockerFingerprintPlugin::start_fingerprint_scanning() +{ + if (!enable) + { + return; + } + + if (device_proxy && !is_scanning) + { + show(); + update_labels("Use fingerprint to unlock"); + is_scanning = true; + device_proxy->call_sync("VerifyStart", + nullptr, + Glib::Variant>::create({""})); + } else + { + update_labels("Unable to start fingerprint scan"); + } +} + +void WayfireLockerFingerprintPlugin::init() +{} + +void WayfireLockerFingerprintPlugin::add_output(int id, WayfireLockerGrid *grid) +{ + labels.emplace(id, std::shared_ptr(new Gtk::Label())); + images.emplace(id, std::shared_ptr(new Gtk::Image())); + + auto image = images[id]; + auto label = labels[id]; + + if (!show_state) + { + image->hide(); + label->hide(); + } + + image->set_from_icon_name("fingerprint"); + label->set_label("No Fingerprint device found"); + + image->add_css_class("fingerprint-icon"); + label->add_css_class("fingerprint-text"); + + grid->attach(*image, WfOption{"locker/fingerprint_position"}); + grid->attach(*label, WfOption{"locker/fingerprint_position"}); +} + +void WayfireLockerFingerprintPlugin::remove_output(int id) +{ + labels.erase(id); + images.erase(id); +} + +bool WayfireLockerFingerprintPlugin::should_enable() +{ + return enable; +} + +void WayfireLockerFingerprintPlugin::update_image(std::string image) +{ + for (auto& it : images) + { + it.second->set_from_icon_name(image); + } + + icon_contents = image; +} + +void WayfireLockerFingerprintPlugin::update_labels(std::string text) +{ + for (auto& it : labels) + { + it.second->set_label(text); + } + + label_contents = text; +} + +void WayfireLockerFingerprintPlugin::hide() +{ + show_state = false; + for (auto& it : labels) + { + it.second->hide(); + } + + for (auto& it : images) + { + it.second->hide(); + } +} + +void WayfireLockerFingerprintPlugin::show() +{ + show_state = true; + for (auto& it : labels) + { + it.second->show(); + } + + for (auto& it : images) + { + it.second->show(); + } +} diff --git a/src/locker/plugin/fingerprint.hpp b/src/locker/plugin/fingerprint.hpp new file mode 100644 index 00000000..85b7178f --- /dev/null +++ b/src/locker/plugin/fingerprint.hpp @@ -0,0 +1,41 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +#include "lockergrid.hpp" +#include "../plugin.hpp" + +class WayfireLockerFingerprintPlugin : public WayfireLockerPlugin +{ + public: + guint dbus_name_id; + Glib::RefPtr device_proxy; + + WayfireLockerFingerprintPlugin(); + ~WayfireLockerFingerprintPlugin(); + void on_bus_acquired(const Glib::RefPtr & connection, const Glib::ustring & name); + void on_device_acquired(const Glib::RefPtr & result); + void claim_device(); + void start_fingerprint_scanning(); + void add_output(int id, WayfireLockerGrid *grid) override; + void remove_output(int id) override; + bool should_enable() override; + void init() override; + void hide(); + void show(); + + bool enable; + bool is_scanning; + bool show_state = false; + void update_labels(std::string text); + void update_image(std::string image); + + std::unordered_map> labels; + std::unordered_map> images; + std::string icon_contents = ""; + std::string label_contents = ""; +}; diff --git a/src/locker/plugin/instant.cpp b/src/locker/plugin/instant.cpp new file mode 100644 index 00000000..2dda575f --- /dev/null +++ b/src/locker/plugin/instant.cpp @@ -0,0 +1,35 @@ +#include +#include +#include + +#include "../locker.hpp" +#include "lockergrid.hpp" +#include "instant.hpp" + +bool WayfireLockerInstantPlugin::should_enable() +{ + return (bool)enable; +} + +void WayfireLockerInstantPlugin::add_output(int id, WayfireLockerGrid *grid) +{ + buttons.emplace(id, std::shared_ptr(new Gtk::Button())); + auto button = buttons[id]; + button->set_label("Press to unlock"); + button->add_css_class("instant-unlock"); + + grid->attach(*button, WfOption{"locker/instant_unlock_position"}); + + button->signal_clicked().connect([] () + { + WayfireLockerApp::get().unlock(); + }, false); +} + +void WayfireLockerInstantPlugin::remove_output(int id) +{ + buttons.erase(id); +} + +void WayfireLockerInstantPlugin::init() +{} diff --git a/src/locker/plugin/instant.hpp b/src/locker/plugin/instant.hpp new file mode 100644 index 00000000..1ca3159c --- /dev/null +++ b/src/locker/plugin/instant.hpp @@ -0,0 +1,21 @@ +#pragma once +#include + +#include "../plugin.hpp" +#include "../../util/wf-option-wrap.hpp" +#include "lockergrid.hpp" + +class WayfireLockerInstantPlugin : public WayfireLockerPlugin +{ + public: + WayfireLockerInstantPlugin() + {} + void add_output(int id, WayfireLockerGrid *grid) override; + void remove_output(int id) override; + bool should_enable() override; + void init() override; + + WfOption enable{"locker/instant_unlock_enable"}; + + std::unordered_map> buttons; +}; diff --git a/src/locker/plugin/mpris.cpp b/src/locker/plugin/mpris.cpp new file mode 100644 index 00000000..9b40fb36 --- /dev/null +++ b/src/locker/plugin/mpris.cpp @@ -0,0 +1,370 @@ +#include +#include +#include +#include +#include +#include + +#include "lockergrid.hpp" +#include "mpris.hpp" + +/* Widget of controls for one player on one screen + * https://specifications.freedesktop.org/mpris/latest/index.html */ +WayfireLockerMPRISWidget::WayfireLockerMPRISWidget(std::string name, + Glib::RefPtr proxy) : proxy(proxy), name(name) +{ + set_transition_type(Gtk::RevealerTransitionType::SLIDE_UP); + image.add_css_class("albumart"); + add_css_class("mpris"); + + kill.set_halign(Gtk::Align::END); + box.append(image); + box.append(sidebox); + + sidebox.set_orientation(Gtk::Orientation::VERTICAL); + sidebox.append(label); + sidebox.append(controlbox); + + label.set_halign(Gtk::Align::START); + label.set_valign(Gtk::Align::START); + label.set_wrap(true); + + controlbox.set_expand(true); + controlbox.append(prev); + controlbox.append(playpause); + controlbox.append(next); + controlbox.append(kill); + controlbox.set_valign(Gtk::Align::END); + controlbox.set_halign(Gtk::Align::START); + kill.set_hexpand(true); + kill.set_halign(Gtk::Align::END); + + signals.push_back(next.signal_clicked().connect( + [proxy] () + { + proxy->call("Next", [proxy] (Glib::RefPtr res) {proxy->call_finish(res);}, nullptr); + })); + + signals.push_back(prev.signal_clicked().connect( + [proxy] () + { + proxy->call("Previous", + [proxy] (Glib::RefPtr res) {proxy->call_finish(res);}, nullptr); + })); + + signals.push_back(playpause.signal_clicked().connect( + [proxy] () + { + proxy->call("PlayPause", + [proxy] (Glib::RefPtr res) {proxy->call_finish(res);}, nullptr); + })); + + signals.push_back(kill.signal_clicked().connect( + [proxy] () + { + proxy->call("Stop", [proxy] (Glib::RefPtr res) {proxy->call_finish(res);}, nullptr); + })); + + signals.push_back(proxy->signal_properties_changed().connect( + [this] (const Gio::DBus::Proxy::MapChangedProperties& properties, + const std::vector& invalidated) + { + for (auto & it : properties) + { + auto [id, value] = it; + if (id == "PlaybackStatus") + { + auto value_string = Glib::VariantBase::cast_dynamic>(value); + playbackstatus(value_string.get()); + } else if (id == "Metadata") + { + auto value_array = Glib::VariantBase::cast_dynamic>>(value); + metadata(value_array.get()); + } else if (id == "CanGoNext") + { + auto value_bool = Glib::VariantBase::cast_dynamic>(value); + cangonext(value_bool.get()); + } else if (id == "CanGoPrevious") + { + auto value_bool = Glib::VariantBase::cast_dynamic>(value); + cangoprev(value_bool.get()); + } else if (id == "CanControl") + { + auto value_bool = Glib::VariantBase::cast_dynamic>(value); + cancontrol(value_bool.get()); + } + } + })); + + Glib::Variant playbackstatus_value; + proxy->get_cached_property(playbackstatus_value, "PlaybackStatus"); + playbackstatus(playbackstatus_value.get()); + + Glib::Variant> metadata_value; + proxy->get_cached_property(metadata_value, "Metadata"); + metadata(metadata_value.get()); + + Glib::Variant cangonext_value; + proxy->get_cached_property(cangonext_value, "CanGoNext"); + cangonext(cangonext_value.get()); + + Glib::Variant cangoprev_value; + proxy->get_cached_property(cangoprev_value, "CanGoPrevious"); + cangoprev(cangoprev_value.get()); + + Glib::Variant cancontrol_value; + proxy->get_cached_property(cancontrol_value, "CanControl"); + cancontrol(cancontrol_value.get()); + + kill.set_icon_name("close"); + playpause.set_icon_name("media-playback-pause"); + prev.set_icon_name("media-skip-backward"); + next.set_icon_name("media-skip-forward"); + + set_child(box); +} + +WayfireLockerMPRISWidget::~WayfireLockerMPRISWidget() +{ + for (auto signal : signals) + { + signal.disconnect(); + } +} + +void WayfireLockerMPRISWidget::playbackstatus(std::string value) +{ + if (value == "Stopped") + { + set_reveal_child(false); + } else + { + set_reveal_child(true); + } +} + +/* https://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata/ */ +void WayfireLockerMPRISWidget::metadata(std::map value) +{ + std::string title = "", album = "", artist = "", art = ""; + for (auto & it : value) + { + std::string id = it.first; + if (id == "xesam:title") + { + title = Glib::VariantBase::cast_dynamic>(it.second).get(); + } else if (id == "xesam:album") + { + album = Glib::VariantBase::cast_dynamic>(it.second).get(); + } else if (id == "xesam:artist") + { + auto artists = + Glib::VariantBase::cast_dynamic>>(it.second).get(); + if (artists.size() > 0) + { + artist = artists[0]; + } + } else if (id == "mpris:artUrl") + { + art = Glib::VariantBase::cast_dynamic>(it.second).get(); + } + } + + if (art.length() < 8) + { + image_path = ""; + image.hide(); + } else + { + art = art.substr(7); + if (art != image_path) + { + image.show(); + image.set(art); + image_path = art; + } + } + + std::vector> pairs = { + {"%track", title}, + {"%album", album}, + {"%artist", artist}, + {"%n", "\n"} + }; + Glib::ustring output = + substitute_strings(pairs, (std::string)WfOption{"locker/mpris_format"}); + label.set_label(output); +} + +void WayfireLockerMPRISWidget::cangonext(bool value) +{ + if (value) + { + next.show(); + } else + { + next.hide(); + } +} + +void WayfireLockerMPRISWidget::cangoprev(bool value) +{ + if (value) + { + prev.show(); + } else + { + prev.hide(); + } +} + +void WayfireLockerMPRISWidget::cancontrol(bool value) +{ + if (value) + { + controlbox.show(); + } else + { + controlbox.hide(); + } +} + +void WayfireLockerMPRISCollective::add_child(std::string id, Glib::RefPtr proxy) +{ + children.emplace(id, Glib::RefPtr(new WayfireLockerMPRISWidget(id, proxy))); + append(*children[id]); +} + +void WayfireLockerMPRISCollective::rem_child(std::string id) +{ + remove(*children[id]); + children.erase(id); +} + +WayfireLockerMPRISPlugin::WayfireLockerMPRISPlugin() : + enable(WfOption{"locker/mpris_enable"}) +{} + +void WayfireLockerMPRISPlugin::init() +{ + Gio::DBus::Proxy::create_for_bus(Gio::DBus::BusType::SESSION, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + [this] (const Glib::RefPtr & result) + { + // Got a dbus proxy + manager_proxy = Gio::DBus::Proxy::create_finish(result); + auto val = manager_proxy->call_sync("ListNames"); + Glib::Variant> list; + val.get_child(list, 0); + auto l2 = list.get(); + for (auto t : l2) + { + if (t.substr(0, 23) == "org.mpris.MediaPlayer2.") + { + add_client(t); + } + } + + /* https://dbus.freedesktop.org/doc/dbus-java/api/org/freedesktop/DBus.NameOwnerChanged.html */ + manager_proxy->signal_signal().connect( + [this] (const Glib::ustring & sender_name, + const Glib::ustring & signal_name, + const Glib::VariantContainerBase & params) + { + if (signal_name == "NameOwnerChanged") + { + Glib::Variant to, from, name; + params.get_child(name, 0); + params.get_child(to, 1); + params.get_child(from, 2); + if (name.get().substr(0, 23) == "org.mpris.MediaPlayer2.") + { + if (to.get() == "") + { + add_client(name.get()); + } else if (from.get() == "") + { + rem_client(name.get()); + } + } + } + }); + }); +} + +bool WayfireLockerMPRISPlugin::should_enable() +{ + return enable; +} + +void WayfireLockerMPRISPlugin::remove_output(int id) +{ + widgets.erase(id); +} + +void WayfireLockerMPRISPlugin::add_output(int id, WayfireLockerGrid *grid) +{ + widgets.emplace(id, new WayfireLockerMPRISCollective()); + + auto collective = widgets[id]; + for (auto & it : clients) + { + collective->add_child(it.first, it.second); + } + + grid->attach(*collective, WfOption{"locker/mpris_position"}); +} + +std::string substitute_string(const std::string from, const std::string to, const std::string in) +{ + std::string output = in; + std::string::size_type position; + while ((position = output.find(from)) != std::string::npos) + { + output.replace(position, from.length(), to); + } + + return output; +} + +std::string substitute_strings(const std::vector> pairs, + const std::string in) +{ + std::string output = in; + for (auto & it : pairs) + { + const auto [from, to] = it; + output = substitute_string(from, to, output); + } + + return output; +} + +void WayfireLockerMPRISPlugin::add_client(std::string path) +{ + Gio::DBus::Proxy::create_for_bus(Gio::DBus::BusType::SESSION, + path, + "/org/mpris/MediaPlayer2", + "org.mpris.MediaPlayer2.Player", + [this, path] (const Glib::RefPtr & result) + { + auto proxy = Gio::DBus::Proxy::create_finish(result); + clients.emplace(path, proxy); + for (auto & it : widgets) + { + it.second->add_child(path, proxy); + } + }); +} + +void WayfireLockerMPRISPlugin::rem_client(std::string path) +{ + clients.erase(path); + for (auto & it : widgets) + { + it.second->rem_child(path); + } +} diff --git a/src/locker/plugin/mpris.hpp b/src/locker/plugin/mpris.hpp new file mode 100644 index 00000000..ef91bc78 --- /dev/null +++ b/src/locker/plugin/mpris.hpp @@ -0,0 +1,74 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +#include "../plugin.hpp" +#include "lockergrid.hpp" + + +std::string substitute_string(const std::string from, const std::string to, const std::string in); +std::string substitute_strings(const std::vector> pairs, + const std::string in); + +class WayfireLockerMPRISWidget : public Gtk::Revealer +{ + private: + Glib::RefPtr proxy; + std::string name; + void playbackstatus(std::string value); + void metadata(std::map value); + void cangonext(bool value); + void cangoprev(bool value); + void cancontrol(bool value); + std::vector signals; + + public: + Gtk::Label label; + Gtk::Button next, prev, playpause, kill; + Gtk::Box box, controlbox, sidebox; + Gtk::Image image; + std::string image_path = ""; + + WayfireLockerMPRISWidget(std::string name, Glib::RefPtr proxy); + ~WayfireLockerMPRISWidget(); +}; + +class WayfireLockerMPRISCollective : public Gtk::Box +{ + private: + std::map> children; + + public: + void add_child(std::string name, Glib::RefPtr proxy); + void rem_child(std::string name); + WayfireLockerMPRISCollective() + { + set_orientation(Gtk::Orientation::VERTICAL); + } +}; + +class WayfireLockerMPRISPlugin : public WayfireLockerPlugin +{ + private: + Glib::RefPtr manager_proxy; + std::map> clients; + std::map> widgets; + bool enable; + + public: + WayfireLockerMPRISPlugin(); + void add_output(int id, WayfireLockerGrid *grid) override; + void remove_output(int id) override; + bool should_enable() override; + void init() override; + + + void add_client(std::string client); + void rem_client(std::string client); + + gulong hook_play, hook_pause, hook_stop, hook_metadata; +}; diff --git a/src/locker/plugin/password.cpp b/src/locker/plugin/password.cpp new file mode 100644 index 00000000..2fa94528 --- /dev/null +++ b/src/locker/plugin/password.cpp @@ -0,0 +1,154 @@ +#include +#include +#include +#include "gtkmm/entry.h" +#include "gtkmm/label.h" +#include +#include +#include + +#include "locker.hpp" +#include "lockergrid.hpp" +#include "password.hpp" + +bool WayfireLockerPasswordPlugin::should_enable() +{ + return (bool)enable; +} + +void WayfireLockerPasswordPlugin::update_labels(std::string text) +{ + for (auto& it : labels) + { + it.second->set_label(text); + } + + label_contents = text; +} + +void WayfireLockerPasswordPlugin::blank_passwords() +{ + for (auto& it : entries) + { + it.second->set_text(""); + } +} + +void WayfireLockerPasswordPlugin::add_output(int id, WayfireLockerGrid *grid) +{ + labels.emplace(id, std::shared_ptr(new Gtk::Label())); + entries.emplace(id, std::shared_ptr(new Gtk::Entry)); + auto label = labels[id]; + auto entry = entries[id]; + label->add_css_class("password-reply"); + entry->add_css_class("password-entry"); + entry->set_placeholder_text("Password"); + label->set_label(label_contents); + entry->set_visibility(false); + /* Set entry callback for return */ + entry->signal_activate().connect([this, entry] () + { + auto password = entry->get_text(); + if (password.length() > 0) + { + submit_user_password(password); + } + }, true); + /* Add to window */ + grid->attach(*entry, WfOption{"locker/password_position"}); + grid->attach(*label, WfOption{"locker/password_position"}); +} + +void WayfireLockerPasswordPlugin::remove_output(int id) +{ + labels.erase(id); + entries.erase(id); +} + +WayfireLockerPasswordPlugin::WayfireLockerPasswordPlugin() +{} + +/* PAM password C code... */ +int pam_conversation(int num_mesg, const struct pam_message **mesg, struct pam_response **resp, + void *appdata_ptr) +{ + std::cout << "PAM convo step ... " << std::endl; + + WayfireLockerPasswordPlugin *pass_plugin = (WayfireLockerPasswordPlugin*)appdata_ptr; + *resp = (struct pam_response*)calloc(num_mesg, sizeof(struct pam_response)); + if (*resp == NULL) + { + std::cerr << "PAM reply allocation failed" << std::endl; + return PAM_ABORT; + } + + for (int count = 0; count < num_mesg; count++) + { + std::cout << "PAM msg : " << mesg[count]->msg << std::endl; + resp[count]->resp_retcode = 0; + /* Echo OFF prompt should be user password. */ + if (mesg[count]->msg_style == PAM_PROMPT_ECHO_OFF) + { + resp[count]->resp = strdup(pass_plugin->submitted_password.c_str()); + } else if (mesg[count]->msg_style == PAM_ERROR_MSG) + { + pass_plugin->update_labels(mesg[count]->msg); + } else if (mesg[count]->msg_style == PAM_TEXT_INFO) + { + pass_plugin->update_labels(mesg[count]->msg); + } + } + + return PAM_SUCCESS; +} + +void WayfireLockerPasswordPlugin::submit_user_password(std::string password) +{ + submitted_password = password; + blank_passwords(); + std::cout << "Unlocking ... " << std::endl; + /* Get username*/ + char *username = getlogin(); + /* Init PAM conversation */ + const struct pam_conv local_conversation = { + pam_conversation, this + }; + pam_handle_t *local_auth_handle = NULL; // this gets set by pam_start + int retval; + /* Start the password-based conversation */ + std::cout << "PAM start ... " << std::endl; + retval = pam_start("wf-locker-password", username, &local_conversation, &local_auth_handle); + if (retval != PAM_SUCCESS) + { + /* We don't expect to be here. No graceful way out of this. */ + std::cout << "PAM start returned " << retval << std::endl; + update_labels("pam_start failure"); + exit(retval); + } + + std::cout << "PAM auth ... " << std::endl; + /* Request authenticate */ + retval = pam_authenticate(local_auth_handle, 0); + bool unlock = false; + if (retval != PAM_SUCCESS) + { + if (retval == PAM_AUTH_ERR) + { + std::cout << "Authentication failure." << std::endl; + update_labels("Authentication failure."); + } + } else + { + std::cout << "Authenticate success." << std::endl; + unlock = true; + } + + retval = pam_end(local_auth_handle, retval); + if (unlock) + { + WayfireLockerApp::get().unlock(); + } +} + +void WayfireLockerPasswordPlugin::init() +{} diff --git a/src/locker/plugin/password.hpp b/src/locker/plugin/password.hpp new file mode 100644 index 00000000..6d25165e --- /dev/null +++ b/src/locker/plugin/password.hpp @@ -0,0 +1,33 @@ +#pragma once +#include +#include +#include + +#include "../plugin.hpp" +#include "../../util/wf-option-wrap.hpp" +#include "lockergrid.hpp" + +int pam_conversation(int num_msg, const struct pam_message **msg, struct pam_response **resp, + void *appdata_ptr); + +class WayfireLockerPasswordPlugin : public WayfireLockerPlugin +{ + public: + WayfireLockerPasswordPlugin(); + void add_output(int id, WayfireLockerGrid *grid) override; + void remove_output(int id) override; + bool should_enable() override; + void init() override; + void submit_user_password(std::string password); + void blank_passwords(); + + WfOption enable{"locker/password_enable"}; + + sigc::connection timeout; + void update_labels(std::string text); + + std::unordered_map> labels; + std::unordered_map> entries; + std::string label_contents = ""; + std::string submitted_password = ""; +}; diff --git a/src/locker/plugin/pin.cpp b/src/locker/plugin/pin.cpp new file mode 100644 index 00000000..3323e161 --- /dev/null +++ b/src/locker/plugin/pin.cpp @@ -0,0 +1,216 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "../../util/wf-option-wrap.hpp" +#include "locker.hpp" +#include "lockergrid.hpp" +#include "pin.hpp" + + +/* + * To set the PIN Hash required to enable this plugin, try running + * `echo -n "1234" | sha512sum | head -c 128 > ~/.config/wf-locker.hash` + * + * Replace the numbers inside the echo quote. There must be one or more digits, + * and any non-digit will render it impossible to unlock. + */ + +PinPad::PinPad() +{ + for (int count = 0; count < 10; count++) + { + std::string number = std::to_string(count); + numbers[count].add_css_class("pinpad-number"); + numbers[count].add_css_class("pinpad-button"); + numbers[count].set_label(number); + numbers[count].signal_clicked().connect( + [number] () + { + auto plugin = WayfireLockerApp::get().get_plugin("pin"); + auto plugin_cast = std::dynamic_pointer_cast(plugin); + plugin_cast->add_digit(number); + }); + } + + bsub.set_label("✔️"); + bcan.set_label("❌"); + bsub.add_css_class("pinpad-submit"); + bsub.add_css_class("pinpad-button"); + bcan.add_css_class("pinpad-cancel"); + bcan.add_css_class("pinpad-button"); + bcan.signal_clicked().connect( + [] () + { + auto plugin = WayfireLockerApp::get().get_plugin("pin"); + auto plugin_cast = std::dynamic_pointer_cast(plugin); + plugin_cast->reset_pin(); + }); + bsub.signal_clicked().connect( + [] () + { + auto plugin = WayfireLockerApp::get().get_plugin("pin"); + auto plugin_cast = std::dynamic_pointer_cast(plugin); + plugin_cast->submit_pin(); + }); + label.add_css_class("pinpad-current"); +} + +PinPad::~PinPad() +{} + +void PinPad::init() +{ + attach(label, 0, 0, 3); + attach(numbers[1], 0, 1); + attach(numbers[2], 1, 1); + attach(numbers[3], 2, 1); + attach(numbers[4], 0, 2); + attach(numbers[5], 1, 2); + attach(numbers[6], 2, 2); + attach(numbers[7], 0, 3); + attach(numbers[8], 1, 3); + attach(numbers[9], 2, 3); + attach(bsub, 2, 4); + attach(numbers[0], 1, 4); + attach(bcan, 0, 4); + set_vexpand(true); + set_column_homogeneous(true); + set_row_homogeneous(true); +} + +WayfireLockerPinPlugin::WayfireLockerPinPlugin() +{ + WfOption enabled{"locker/pin_enable"}; + enable = enabled; + if (!enable) + { + return; + } + + /* TODO ... */ + // if (cmdline_config.has_value()) + // { + // return cmdline_config.value(); + // } + std::string config_dir; + + char *config_home = getenv("XDG_CONFIG_HOME"); + if (config_home == NULL) + { + config_dir = std::string(getenv("HOME")) + "/.config"; + } else + { + config_dir = std::string(config_home); + } + + std::ifstream f(config_dir + "/wf-locker.hash"); + if (!f.is_open()) + { + std::cerr << "No PIN hash set" << std::endl; + enable = false; + return; + } + + std::string s; + if (!getline(f, s)) + { + std::cerr << "No PIN hash set" << std::endl; + enable = false; + return; + } + + if (s.length() != 128) + { + std::cerr << "Invalid PIN hash" << std::endl; + enable = false; + return; + } + + pinhash = s; +} + +void WayfireLockerPinPlugin::init() +{} + +bool WayfireLockerPinPlugin::should_enable() +{ + return enable; +} + +void WayfireLockerPinPlugin::add_output(int id, WayfireLockerGrid *grid) +{ + pinpads.emplace(id, new PinPad()); + auto pinpad = pinpads[id]; + pinpad->add_css_class("pinpad"); + pinpad->init(); + grid->attach(*pinpad, WfOption{"locker/pin_position"}); + update_labels(); /* Update all to set this one? maybe overkill */ +} + +void WayfireLockerPinPlugin::remove_output(int id) +{ + pinpads.erase(id); +} + +void WayfireLockerPinPlugin::add_digit(std::string digit) +{ + pin = pin + digit; + update_labels(); +} + +void WayfireLockerPinPlugin::reset_pin() +{ + pin = ""; + update_labels(); +} + +void WayfireLockerPinPlugin::update_labels() +{ + std::string asterisks(pin.length(), '*'); + for (auto& it : pinpads) + { + it.second->label.set_label(asterisks); + } +} + +void WayfireLockerPinPlugin::submit_pin() +{ + auto hash = sha512(pin); + if (hash == pinhash) + { + WayfireLockerApp::get().unlock(); + } + + pin = ""; + update_labels(); +} + +std::string WayfireLockerPinPlugin::sha512(const std::string input) +{ + unsigned char hash[EVP_MAX_MD_SIZE]; + unsigned int hash_length = 0; + EVP_MD_CTX *mdctx = EVP_MD_CTX_create(); + EVP_DigestInit(mdctx, EVP_sha512()); + EVP_DigestUpdate(mdctx, input.c_str(), input.size()); + EVP_DigestFinal(mdctx, hash, &hash_length); + EVP_MD_CTX_free(mdctx); + + std::stringstream ss; + + for (int i = 0; i < SHA512_DIGEST_LENGTH; i++) + { + ss << std::hex << std::setw(2) << std::setfill('0') << static_cast(hash[i]); + } + + return ss.str(); +} diff --git a/src/locker/plugin/pin.hpp b/src/locker/plugin/pin.hpp new file mode 100644 index 00000000..1f98003d --- /dev/null +++ b/src/locker/plugin/pin.hpp @@ -0,0 +1,46 @@ +#pragma once +#include +#include +#include +#include +#include + +#include "../plugin.hpp" +#include "lockergrid.hpp" + +/* Rather than keep an unordered list for each widget, put them together */ +class WayfireLockerPinPlugin; +class PinPad : public Gtk::Grid +{ + public: + PinPad(); + ~PinPad(); + Gtk::Button bsub, bcan; + Gtk::Label label; + void init(); + void check(); + Gtk::Button numbers[10]; +}; + +class WayfireLockerPinPlugin : public WayfireLockerPlugin +{ + public: + WayfireLockerPinPlugin(); + void add_output(int id, WayfireLockerGrid *grid) override; + void remove_output(int id) override; + bool should_enable() override; + void init() override; + + void update_labels(); + void submit_pin(); + void reset_pin(); + void add_digit(std::string digit); + std::string sha512(const std::string input); + + bool enable = false; + + std::unordered_map> pinpads; + + std::string pin = ""; + std::string pinhash = "nope"; +}; diff --git a/src/locker/plugin/user.cpp b/src/locker/plugin/user.cpp new file mode 100644 index 00000000..e788ed2f --- /dev/null +++ b/src/locker/plugin/user.cpp @@ -0,0 +1,89 @@ +#include + +#include "lockergrid.hpp" +#include "user.hpp" + +WayfireLockerUserPlugin::WayfireLockerUserPlugin() : + enable(WfOption{"locker/user_enable"}) +{} + +void WayfireLockerUserPlugin::init() +{ + char *home = getenv("HOME"); + if (home == NULL) + { + std::cout << "No user home, skipping finding image" << std::endl; + return; + } + + std::string home_path = home; + + std::vector paths = { + ".face", + ".face.png", + ".face.jpg", + ".face.jpeg", + ".face.svg", + ".face.icon", + }; + + for (auto path : paths) + { + auto home_path_file = home_path + "/" + path; + struct stat sb; + if ((stat(home_path_file.c_str(), &sb) == 0) && !(sb.st_mode & S_IFDIR)) + { + std::cout << home_path_file << " Selected" << std::endl; + image_path = home_path_file; + return; + } + } + + std::cout << "No user image .face... no image in lockscreen" << std::endl; +} + +void WayfireLockerUserPlugin::add_output(int id, WayfireLockerGrid *grid) +{ + labels.emplace(id, Glib::RefPtr(new Gtk::Label())); + images.emplace(id, Glib::RefPtr(new Gtk::Image())); + boxes.emplace(id, Glib::RefPtr(new Gtk::Box)); + + auto label = labels[id]; + auto image = images[id]; + auto box = boxes[id]; + + box->add_css_class("user"); + box->set_orientation(Gtk::Orientation::VERTICAL); + image->set_halign(Gtk::Align::CENTER); + image->set_valign(Gtk::Align::END); + label->set_halign(Gtk::Align::CENTER); + label->set_valign(Gtk::Align::START); + label->set_justify(Gtk::Justification::CENTER); + + std::string username = getlogin(); + + label->set_label(username); + if (image_path == "") + { + image->hide(); + } else + { + image->set(image_path); + } + + box->append(*image); + box->append(*label); + grid->attach(*box, WfOption{"locker/user_position"}); +} + +void WayfireLockerUserPlugin::remove_output(int id) +{ + labels.erase(id); + images.erase(id); + boxes.erase(id); +} + +bool WayfireLockerUserPlugin::should_enable() +{ + return enable; +} diff --git a/src/locker/plugin/user.hpp b/src/locker/plugin/user.hpp new file mode 100644 index 00000000..171ba9b4 --- /dev/null +++ b/src/locker/plugin/user.hpp @@ -0,0 +1,26 @@ +#pragma once +#include +#include +#include + +#include "../plugin.hpp" +#include "../../util/wf-option-wrap.hpp" +#include "lockergrid.hpp" + +class WayfireLockerUserPlugin : public WayfireLockerPlugin +{ + public: + WayfireLockerUserPlugin(); + void add_output(int id, WayfireLockerGrid *grid) override; + void remove_output(int id) override; + bool should_enable() override; + void init() override; + + bool enable; + + std::unordered_map> labels; + std::unordered_map> images; + std::unordered_map> boxes; + + std::string image_path = ""; +}; diff --git a/src/locker/plugin/volume.cpp b/src/locker/plugin/volume.cpp new file mode 100644 index 00000000..f188b19a --- /dev/null +++ b/src/locker/plugin/volume.cpp @@ -0,0 +1,176 @@ +#include +#include +#include + +#include "lockergrid.hpp" +#include "volume.hpp" +#include "../../util/wf-option-wrap.hpp" + +static void default_sink_changed(GvcMixerControl *gvc_control, + guint id, gpointer user_data) +{ + WayfireLockerVolumePlugin *plugin = (WayfireLockerVolumePlugin*)user_data; + plugin->on_default_sink_changed(); +} + +static void default_source_changed(GvcMixerControl *gvc_control, + guint id, gpointer user_data) +{ + WayfireLockerVolumePlugin *plugin = (WayfireLockerVolumePlugin*)user_data; + plugin->on_default_source_changed(); +} + +static void notify_sink_muted(GvcMixerControl *gvc_control, + guint id, gpointer user_data) +{ + WayfireLockerVolumePlugin *plugin = (WayfireLockerVolumePlugin*)user_data; + plugin->update_button_images(); +} + +static void notify_source_muted(GvcMixerControl *gvc_control, + guint id, gpointer user_data) +{ + WayfireLockerVolumePlugin *plugin = (WayfireLockerVolumePlugin*)user_data; + plugin->update_button_images(); +} + +void WayfireLockerVolumePlugin::update_button_images() +{ + if (gvc_sink_stream) + { + for (auto& it : sink_buttons) + { + it.second->set_icon_name(gvc_mixer_stream_get_is_muted( + gvc_sink_stream) ? "audio-volume-muted-symbolic" : "audio-volume-high-symbolic"); + } + } + + if (gvc_source_stream) + { + for (auto& it : source_buttons) + { + it.second->set_icon_name(gvc_mixer_stream_get_is_muted( + gvc_source_stream) ? "microphone-sensitivity-muted-symbolic" : "microphone-sensitivity-high-symbolic"); + } + } +} + +WayfireLockerVolumePlugin::WayfireLockerVolumePlugin() +{ + enable = WfOption{"locker/volume_enable"}; + /* Setup gvc control */ + gvc_control = gvc_mixer_control_new("Wayfire Volume Control"); + g_signal_connect(gvc_control, + "default-sink-changed", G_CALLBACK(default_sink_changed), this); + g_signal_connect(gvc_control, + "default-source-changed", G_CALLBACK(default_source_changed), this); + gvc_mixer_control_open(gvc_control); +} + +bool WayfireLockerVolumePlugin::should_enable() +{ + return (bool)enable; +} + +void WayfireLockerVolumePlugin::add_output(int id, WayfireLockerGrid *grid) +{ + source_buttons.emplace(id, std::shared_ptr(new Gtk::Button())); + sink_buttons.emplace(id, std::shared_ptr(new Gtk::Button())); + auto source_button = source_buttons[id]; + auto sink_button = sink_buttons[id]; + + sink_button->add_css_class("volume-button"); + source_button->add_css_class("mic-button"); + + auto inner_box = Gtk::Box(); + inner_box.append(*source_button); + inner_box.append(*sink_button); + grid->attach(inner_box, WfOption{"locker/volume_position"}); + + sink_button->signal_clicked().connect( + [=] () + { + if (!gvc_sink_stream) + { + return; + } + + bool muted = gvc_mixer_stream_get_is_muted(gvc_sink_stream); + gvc_mixer_stream_change_is_muted(gvc_sink_stream, !muted); + gvc_mixer_stream_push_volume(gvc_sink_stream); + }); + source_button->signal_clicked().connect( + [=] () + { + if (!gvc_source_stream) + { + return; + } + + bool muted = gvc_mixer_stream_get_is_muted(gvc_source_stream); + gvc_mixer_stream_change_is_muted(gvc_source_stream, !muted); + gvc_mixer_stream_push_volume(gvc_source_stream); + }); + update_button_images(); +} + +void WayfireLockerVolumePlugin::remove_output(int id) +{ + source_buttons.erase(id); + sink_buttons.erase(id); +} + +void WayfireLockerVolumePlugin::init() +{} + +void WayfireLockerVolumePlugin::disconnect_gvc_stream_sink_signals() +{ + if (notify_sink_muted_signal) + { + g_signal_handler_disconnect(gvc_sink_stream, notify_sink_muted_signal); + } + + notify_sink_muted_signal = 0; +} + +void WayfireLockerVolumePlugin::disconnect_gvc_stream_source_signals() +{ + if (notify_source_muted_signal) + { + g_signal_handler_disconnect(gvc_source_stream, notify_source_muted_signal); + } + + notify_source_muted_signal = 0; +} + +void WayfireLockerVolumePlugin::on_default_sink_changed() +{ + gvc_sink_stream = gvc_mixer_control_get_default_sink(gvc_control); + if (!gvc_sink_stream) + { + printf("GVC: Failed to get default sink\n"); + return; + } + + /* Reconnect signals to new sink */ + disconnect_gvc_stream_sink_signals(); + notify_sink_muted_signal = g_signal_connect(gvc_sink_stream, "notify::is-muted", + G_CALLBACK(notify_sink_muted), this); + update_button_images(); +} + +void WayfireLockerVolumePlugin::on_default_source_changed() +{ + gvc_source_stream = gvc_mixer_control_get_default_source(gvc_control); + if (!gvc_source_stream) + { + printf("GVC: Failed to get default source\n"); + return; + } + + /* Reconnect signals to new source */ + disconnect_gvc_stream_source_signals(); + notify_source_muted_signal = g_signal_connect(gvc_source_stream, "notify::is-muted", + G_CALLBACK(notify_source_muted), this); + update_button_images(); +} diff --git a/src/locker/plugin/volume.hpp b/src/locker/plugin/volume.hpp new file mode 100644 index 00000000..178c574c --- /dev/null +++ b/src/locker/plugin/volume.hpp @@ -0,0 +1,39 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +#include "gvc-mixer-control.h" +#include "../plugin.hpp" +#include "lockergrid.hpp" + +class WayfireLockerVolumePlugin : public WayfireLockerPlugin +{ + private: + GvcMixerControl *gvc_control; + GvcMixerStream *gvc_sink_stream = NULL; + GvcMixerStream *gvc_source_stream = NULL; + gulong notify_sink_muted_signal = 0; + gulong notify_source_muted_signal = 0; + void disconnect_gvc_stream_sink_signals(); + void disconnect_gvc_stream_source_signals(); + + public: + WayfireLockerVolumePlugin(); + void add_output(int id, WayfireLockerGrid *grid) override; + void remove_output(int id) override; + bool should_enable() override; + void init() override; + bool enable; + void update_button_images(); + + /** Called when the default sink changes */ + void on_default_sink_changed(); + void on_default_source_changed(); + + std::unordered_map> sink_buttons; + std::unordered_map> source_buttons; +}; diff --git a/src/meson.build b/src/meson.build index e8931648..118bdec5 100644 --- a/src/meson.build +++ b/src/meson.build @@ -2,6 +2,7 @@ subdir('util') subdir('panel') subdir('background') subdir('dock') +subdir('locker') pkgconfig = import('pkgconfig') pkgconfig.generate( diff --git a/src/panel/widgets/volume.hpp b/src/panel/widgets/volume.hpp index 39d3dd25..53be598f 100644 --- a/src/panel/widgets/volume.hpp +++ b/src/panel/widgets/volume.hpp @@ -3,6 +3,7 @@ #include "../widget.hpp" #include "wf-popover.hpp" +#include #include #include #include diff --git a/src/util/css-config.cpp b/src/util/css-config.cpp index a2138453..5468cf23 100644 --- a/src/util/css-config.cpp +++ b/src/util/css-config.cpp @@ -1,5 +1,4 @@ #include -#include #include #include #include diff --git a/src/util/css-config.hpp b/src/util/css-config.hpp index bfd83f04..fd3b959a 100644 --- a/src/util/css-config.hpp +++ b/src/util/css-config.hpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include "wf-option-wrap.hpp" class CssFromConfig { diff --git a/src/util/wf-shell-app.cpp b/src/util/wf-shell-app.cpp index b740d864..7883ecbe 100644 --- a/src/util/wf-shell-app.cpp +++ b/src/util/wf-shell-app.cpp @@ -222,17 +222,20 @@ void WayfireShellApp::on_activate() sigc::bind<0>(&handle_css_inotify_event, this), inotify_css_fd, Glib::IOCondition::IO_IN | Glib::IOCondition::IO_HUP); - // Hook up monitor tracking - auto display = Gdk::Display::get_default(); - auto monitors = display->get_monitors(); - monitors->signal_items_changed().connect(sigc::mem_fun(*this, &WayfireShellApp::output_list_updated)); - - // initial monitors - int num_monitors = monitors->get_n_items(); - for (int i = 0; i < num_monitors; i++) + if (!alternative_monitors) { - auto obj = std::dynamic_pointer_cast(monitors->get_object(i)); - add_output(obj); + // Hook up monitor tracking + auto display = Gdk::Display::get_default(); + auto monitors = display->get_monitors(); + monitors->signal_items_changed().connect(sigc::mem_fun(*this, &WayfireShellApp::output_list_updated)); + + // initial monitors + int num_monitors = monitors->get_n_items(); + for (int i = 0; i < num_monitors; i++) + { + auto obj = std::dynamic_pointer_cast(monitors->get_object(i)); + add_output(obj); + } } } diff --git a/src/util/wf-shell-app.hpp b/src/util/wf-shell-app.hpp index c7036ba8..fc43f890 100644 --- a/src/util/wf-shell-app.hpp +++ b/src/util/wf-shell-app.hpp @@ -43,6 +43,7 @@ class WayfireShellApp protected: /** This should be initialized by the subclass in each program which uses * wf-shell-app */ + bool alternative_monitors = false; /* Used to skip monitor management in lockscreen */ static std::unique_ptr instance; std::optional cmdline_config; std::optional cmdline_css; diff --git a/subprojects/.wraplock b/subprojects/.wraplock new file mode 100644 index 00000000..e69de29b