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