Skip to content

Commit

Permalink
Merge pull request #64 from wmww/session-lock-api
Browse files Browse the repository at this point in the history
Refactor Session Lock API and add Python example
  • Loading branch information
wmww authored Jan 26, 2025
2 parents e6c9581 + c06f5d2 commit 46ba98c
Show file tree
Hide file tree
Showing 8 changed files with 261 additions and 129 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
- name: Apt update
run: sudo apt update
- name: Install depends
run: sudo apt install meson libwayland-dev libgtk-4-dev gobject-introspection libgirepository1.0-dev valac luajit luarocks gtk-doc-tools
run: sudo apt install meson libwayland-dev libgtk-4-dev gobject-introspection libgirepository1.0-dev valac gtk-doc-tools
- name: Meson
run: meson setup -Dexamples=true -Ddocs=true -Dtests=true build
env:
Expand Down
58 changes: 36 additions & 22 deletions examples/session-lock.c
Original file line number Diff line number Diff line change
@@ -1,53 +1,63 @@
#include "gtk4-session-lock.h"
#include <gtk/gtk.h>

static gboolean has_locked = FALSE;
static GtkApplication* app = NULL;
static GtkSessionLockInstance* lock = NULL;

static void unlock(GtkButton *button, void *data) {
(void)button;
GtkApplication* app = data;
(void)data;

gtk_session_lock_unlock();
gtk_session_lock_instance_unlock(lock);
g_application_quit(G_APPLICATION(app));
}

static void locked(GtkSessionLockSingleton *session_lock, void *data) {
static void locked(GtkSessionLockInstance *lock, void *data) {
(void)lock;
(void)data;
(void)session_lock;

g_message("Session locked successfully");
has_locked = TRUE;
}

static void finished(GtkSessionLockSingleton *session_lock, void *data) {
(void)session_lock;
GtkApplication* app = data;
static void failed(GtkSessionLockInstance *lock, void *data) {
(void)lock;
(void)data;

if (has_locked) {
g_message("The compositor unlocked us");
g_application_quit(G_APPLICATION(app));
} else {
g_critical("The session could not be locked");
}
g_critical("The session could not be locked");
g_application_quit(G_APPLICATION(app));
}

static void unlocked(GtkSessionLockInstance *lock, void *data) {
(void)lock;
(void)data;

g_message("Session unlocked");
g_application_quit(G_APPLICATION(app));
}

static void activate(GtkApplication* app, void *data) {
(void)data;
g_application_hold(G_APPLICATION(app));

lock = gtk_session_lock_instance_new();
g_signal_connect(lock, "locked", G_CALLBACK(locked), NULL);
g_signal_connect(lock, "failed", G_CALLBACK(failed), NULL);
g_signal_connect(lock, "unlocked", G_CALLBACK(unlocked), NULL);

if (!gtk_session_lock_instance_lock(lock)) {
// Error message already shown when handling the ::failed signal
return;
}

GdkDisplay *display = gdk_display_get_default();
GListModel *monitors = gdk_display_get_monitors(display);
guint n_monitors = g_list_model_get_n_items(monitors);

g_signal_connect(gtk_session_lock_get_singleton(), "locked", G_CALLBACK(locked), app);
g_signal_connect(gtk_session_lock_get_singleton(), "finished", G_CALLBACK(finished), app);

gtk_session_lock_lock();

for (guint i = 0; i < n_monitors; ++i) {
GdkMonitor *monitor = g_list_model_get_item(monitors, i);

GtkWindow *gtk_window = GTK_WINDOW(gtk_application_window_new(app));
gtk_session_lock_assign_window_to_monitor(gtk_window, monitor);
gtk_session_lock_instance_assign_window_to_monitor(lock, gtk_window, monitor);

GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_set_halign(box, GTK_ALIGN_CENTER);
Expand All @@ -67,7 +77,11 @@ static void activate(GtkApplication* app, void *data) {
}

int main(int argc, char **argv) {
GtkApplication *app = gtk_application_new(
if (!gtk_session_lock_is_supported()) {
g_message("Session lock not supported");
return 1;
}
app = gtk_application_new(
"com.github.wmww.gtk4-layer-shell.session-lock-example",
G_APPLICATION_DEFAULT_FLAGS
);
Expand Down
72 changes: 72 additions & 0 deletions examples/session-lock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# To run this script without installing the library, set GI_TYPELIB_PATH and LD_LIBRARY_PATH to the build/src directory
# GI_TYPELIB_PATH=build/src LD_LIBRARY_PATH=build/src python3 examples/session-lock.py

# For GTK4 Layer Shell to get linked before libwayland-client we must explicitly load it before importing with gi
from ctypes import CDLL
CDLL('libgtk4-layer-shell.so')

import gi
gi.require_version('Gtk', '4.0')
gi.require_version('Gtk4SessionLock', '1.0')

from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import Gtk4SessionLock as SessionLock

class ScreenLock:
def __init__(self):
self.lock_instance = SessionLock.Instance.new()
self.lock_instance.connect('locked', self._on_locked)
self.lock_instance.connect('unlocked', self._on_unlocked)
self.lock_instance.connect('failed', self._on_failed)

def _on_locked(self, lock_instance):
print('Locked!')

def _on_unlocked(self, lock_instance):
print('Unlocked!')
app.quit()

def _on_failed(self, lock_instance):
print('Failed to lock :(')
app.quit()

def _on_unlock_clicked(self, button):
self.lock_instance.unlock()

def _create_lock_window(self, monitor):
window = Gtk.Window(application=app)

box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
box.set_halign(Gtk.Align.CENTER)
box.set_valign(Gtk.Align.CENTER)
window.set_child(box)

label = Gtk.Label(label="GTK Session Lock with Python")
box.append(label)

button = Gtk.Button(label='Unlock')
button.connect('clicked', self._on_unlock_clicked)
box.append(button)

self.lock_instance.assign_window_to_monitor(window, monitor)
window.present()

def lock(self):
if not self.lock_instance.lock():
# Failure has already been handled in on_failed()
return

display = Gdk.Display.get_default()

for monitor in display.get_monitors():
self._create_lock_window(monitor)

app = Gtk.Application(application_id='com.github.wmww.gtk4-layer-shell.py-session-lock')
lock = ScreenLock()

def on_activate(app):
lock.lock()

app.connect('activate', on_activate)
app.run(None)
4 changes: 2 additions & 2 deletions examples/simple-example.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
CDLL('libgtk4-layer-shell.so')

import gi
gi.require_version("Gtk", "4.0")
gi.require_version('Gtk', '4.0')
gi.require_version('Gtk4LayerShell', '1.0')

from gi.repository import Gtk
Expand All @@ -23,7 +23,7 @@ def on_activate(app):
LayerShell.set_margin(window, LayerShell.Edge.TOP, 20)
LayerShell.auto_exclusive_zone_enable(window)

button = Gtk.Button(label="GTK4 Layer Shell with Python")
button = Gtk.Button(label='GTK4 Layer Shell with Python')
button.connect('clicked', lambda x: window.close())
window.set_child(button)
window.present()
Expand Down
74 changes: 52 additions & 22 deletions include/gtk4-session-lock.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,63 +21,93 @@
G_BEGIN_DECLS

/**
* GtkSessionLockSingleton:
* gtk_session_lock_is_supported:
*
* The singleton object used to register signals relating to the lock screen's state.
* May block for a Wayland roundtrip the first time it's called.
*
* Returns: %TRUE if the platform is Wayland and Wayland compositor supports the
* Session Lock protocol.
*/
gboolean gtk_session_lock_is_supported();

/**
* GtkSessionLockInstance:
*
* An instance of the object used to control locking the screen.
* Multiple instances can exist at once, but only one can be locked at a time.
*/
G_DECLARE_FINAL_TYPE(GtkSessionLockSingleton, gtk_session_lock_singleton, GTK_SESSION_LOCK, SESSION_LOCK, GObject)
G_DECLARE_FINAL_TYPE(GtkSessionLockInstance, gtk_session_lock_instance, GTK_SESSION_LOCK, INSTANCE, GObject)

/**
* GtkSessionLockSingleton::locked:
* GtkSessionLockInstance::locked:
*
* The ::locked signal is fired when the screen is successfully locked.
*/

/**
* GtkSessionLockSingleton::finished:
* GtkSessionLockInstance::failed:
*
* The ::finished signal is fired when the session is not locked (either it failed to lock or has been unlocked by the
* compositor). `finished` is not fired when gtk_session_lock_unlock() is called.
* The ::failed signal is fired when the lock could not be acquired.
*/

/**
* gtk_session_lock_get_singleton:
* GtkSessionLockInstance::unlocked:
*
* Returns: (transfer none): The singleton instance (created on first call).
* The ::unlocked signal is fired when the session is unlocked, which may have been caused by a call to
* gtk_session_lock_instance_unlock() or by the compositor.
*/
GtkSessionLockSingleton* gtk_session_lock_get_singleton();

/**
* gtk_session_lock_lock:
* gtk_session_lock_instance_new:
*
* Returns: new session lock instance
*/
GtkSessionLockInstance* gtk_session_lock_instance_new();

/**
* gtk_session_lock_instance_lock:
* @self: the instance to lock
*
* Lock the screen. This should be called before assigning any windows to monitors. If this function fails the ::failed
* signal is emitted, if it succeeds the ::locked signal is emitted. The ::failed signal may be emitted before the
* function returns (for example, if another #GtkSessionLockInstance holds a lock) or later (if another process holds a
* lock)
*
* Lock the screen. This should be called before assigning any windows to monitors.
* Returns: false on immediate fail, true if lock acquisition was successfully started
*/
void gtk_session_lock_lock();
gboolean gtk_session_lock_instance_lock(GtkSessionLockInstance* self);

/**
* gtk_session_lock_unlock:
* gtk_session_lock_instance_unlock:
* @self: the instance to unlock
*
* Unlock the screen. Has no effect if called when the screen is not locked.
* If the screen is locked by this instance unlocks it and fires ::unlocked. Otherwise has no effect
*/
void gtk_session_lock_unlock();
void gtk_session_lock_instance_unlock(GtkSessionLockInstance* self);

/**
* gtk_session_lock_is_locked:
* gtk_session_lock_instance_is_locked:
* @self: the instance
*
* Returns if the screen is currently locked by this library in this process.
* Returns if this instance currently holds a lock.
*/
gboolean gtk_session_lock_is_locked();
gboolean gtk_session_lock_instance_is_locked(GtkSessionLockInstance* self);

/**
* gtk_session_lock_assign_window_to_monitor:
* gtk_session_lock_instance_assign_window_to_monitor:
* @self: the instance to use
* @window: The GTK Window to use as a lock surface
* @monitor: The monitor to show it on
*
* This should be called with a different window once for each monitor immediately after calling
* gtk_session_lock_lock(). Hiding a window that is active on a monitor or not letting a window be resized by the
* library may result in a protocol error.
* library may result in a Wayland protocol error.
*/
void gtk_session_lock_assign_window_to_monitor(GtkWindow *window, GdkMonitor *monitor);
void gtk_session_lock_instance_assign_window_to_monitor(
GtkSessionLockInstance* self,
GtkWindow *window,
GdkMonitor *monitor
);

G_END_DECLS

Expand Down
Loading

0 comments on commit 46ba98c

Please sign in to comment.