feat: LockScreen

This commit is contained in:
tr1x_em committed 2025-08-09 14:44:55 +05:30
1 parent e89fdb8163
commit afd42745d6
7 files changed
+410 -139

No files matched your search

+218
View File
@@ -0,0 +1,218 @@
import gi, pam, fabric
gi.require_version("GtkSessionLock", "0.1")
from gi.repository import Gdk, GtkSessionLock
from fabric.widgets.window import Window
# from widgets.wayland import WaylandWindow as Window
import os
from fabric.widgets.entry import Entry
from fabric.widgets.datetime import DateTime
from modules.panel.components.indicators import (
BatteryIndicator,
BluetoothIndicator,
NetworkIndicator,
)
from fabric.widgets.box import Box
from fabric.widgets.label import Label
from fabric.widgets.centerbox import CenterBox
# from fabric.widgets.image import Image
import getpass
from fabric import Application
from gi.repository import GLib
from widgets.circle_image import CircleImage as Image
from fabric.utils import get_relative_path
class IndicatorBox(Box):
def __init__(self, *args, **kwargs):
super().__init__(
h_align="end",
name="indicatorbox",
spacing=5,
h_expand=True,
children=[
BatteryIndicator(),
BluetoothIndicator(),
NetworkIndicator(),
],
)
class ContentBox(CenterBox):
def __init__(self, on_activate, *args, **kwargs):
self.password_entry = Entry(
placeholder="Enter Password",
name="password-entry",
h_align="center",
v_align="center",
visible=False,
password=True,
# on_activate=on_activate,
)
self.password_entry.set_property("xalign", 0.5)
self.username_label = Label(
label=f"{getpass.getuser().title()}",
name="username",
visible=True,
h_align="center",
v_align="center",
)
self.unlock_text = Label(
label="Touch ID or Enter Password",
name="unlock-text",
)
super().__init__(
name="content-box",
h_expand=True,
orientation="vertical",
v_expand=True,
start_children=[
IndicatorBox(),
DateTime(
formatters=["%A,%B %-d"],
interval=10000,
h_expand=False,
v_align="start",
v_expand=False,
name="lock-date",
),
DateTime(formatters=["%I:%M"], name="lock-clock"),
],
end_children=[
Box(
name="profile-box",
h_align="center",
h_expand=True,
v_expand=True,
v_align="end",
children=[
Image(
name="face-icon",
image_file="/home/saumya/.face.icon",
size=64,
),
],
),
Box(
name="container-box",
orientation="v",
v_align="end",
h_align="center",
h_expand=True,
v_expand=True,
children=[
self.username_label,
self.password_entry,
],
),
Box(
name="unlock-box",
v_align="end",
h_align="center",
h_expand=True,
v_expand=True,
children=[
self.unlock_text,
],
),
],
**kwargs,
)
class LockScreen(Window):
def __init__(self, lock: GtkSessionLock.Lock):
self._hide_timeout_id = None # prevent AttributeError
self.lock = lock
self.content = ContentBox(self.on_activate)
super().__init__(
title="lock",
visible=True,
all_visible=False,
name="lockscreen-bg",
anchor="center",
child=self.content,
)
self.content.password_entry.set_visible(False)
self.connect("key-press-event", self._on_keypress)
bg = os.path.expanduser("~/.current.wall")
self.set_style(f"background-image: url('{bg}');")
def _on_keypress(self, widget, event):
keyval = event.keyval
# ESC pressed → hide entry immediately
if keyval == Gdk.KEY_Escape and self.content.password_entry.get_visible():
self._hide_entry()
return
# Show entry if hidden
if not self.content.password_entry.get_visible():
self.content.username_label.set_visible(False)
self.content.password_entry.set_visible(True)
self.content.password_entry.grab_focus()
self._start_hide_timer()
else:
# Reset timer if already visible
self._restart_hide_timer()
def _start_hide_timer(self):
self._stop_hide_timer() # just in case
self._hide_timeout_id = GLib.timeout_add_seconds(500, self._hide_entry)
# 10 seconds of inactivity before hiding
def _restart_hide_timer(self):
self._start_hide_timer()
def _stop_hide_timer(self):
if self._hide_timeout_id:
GLib.source_remove(self._hide_timeout_id)
self._hide_timeout_id = None
def _hide_entry(self):
self._stop_hide_timer()
self.content.password_entry.set_visible(False)
self.content.username_label.set_visible(True)
return False # stop timeout
def on_activate(self, entry: Entry, *args):
if not pam.authenticate(getpass.getuser(), (entry.get_text() or "").strip()):
return
self.lock.unlock_and_destroy()
self.destroy()
def initialize():
lock = GtkSessionLock.prepare_lock()
lock.lock_lock()
lockscreen = LockScreen(lock)
lock.new_surface(
lockscreen,
Gdk.Display.get_default().get_monitor(
0
), # pyright: ignore[reportAttributeAccessIssue, reportOptionalMemberAccess]
)
lockscreen.show_all()
if __name__ == "__main__":
# initialize()
lockscreen = LockScreen(GtkSessionLock.Lock())
app = Application("lock", lockscreen)
def set_css():
app.set_stylesheet_from_file(
get_relative_path("main.css"),
)
app.set_css = set_css # pyright: ignore[reportAttributeAccessIssue]
app.set_css() # pyright: ignore[reportAttributeAccessIssue]
app.run()
-8
View File
@@ -1,8 +0,0 @@
#!/bin/bash
while true; do
inotifywait -e close_write main.css
fabric-cli exec lock 'app.set_css()'
done
-92
View File
@@ -1,92 +0,0 @@
import gi, pam, fabric
gi.require_version("GtkSessionLock", "0.1")
from gi.repository import Gdk, GtkSessionLock
from fabric.widgets.window import Window
# from widgets.wayland import WaylandWindow as Window
from fabric.widgets.entry import Entry
from fabric.widgets.datetime import DateTime
from fabric.widgets.box import Box
from fabric.widgets.centerbox import CenterBox
from fabric import Application
from fabric.utils import get_relative_path
class ContentBox(CenterBox):
def __init__(self, *args, **kwargs):
super().__init__(
name="content-box",
h_expand=True,
orientation="vertical",
v_expand=True,
start_children=[
DateTime(
formatters=["%A. %d %B"],
interval=10000,
h_expand=False,
v_align="start",
v_expand=False,
name="date",
),
DateTime(formatters=["%I:%M"], name="clock"),
],
end_children=[
Entry(
placeholder="gib password",
name="password-entry",
h_align="center",
v_align="end",
password=True,
on_activate=on_activate(self),
),
],
**kwargs
)
class LockScreen(Window):
def __init__(self, lock: GtkSessionLock.Lock):
self.lock = lock
super().__init__(
title="lock",
visible=True,
all_visible=False,
name="lockscreen-bg",
anchor="center",
child=ContentBox(),
)
def on_activate(self, entry: Entry, *args):
if entry.get_text() == "help":
pass
elif not pam.authenticate("saumya", (entry.get_text() or "").strip()):
return
self.lock.unlock_and_destroy()
self.destroy()
def initialize():
lock = GtkSessionLock.prepare_lock()
lock.lock_lock()
lockscreen = LockScreen(lock)
lock.new_surface(lockscreen, Gdk.Display.get_default().get_monitor(0))
lockscreen.show_all()
if __name__ == "__main__":
initialize()
lock = LockScreen(GtkSessionLock.Lock())
app = Application("lock", lock)
def set_css():
app.set_stylesheet_from_file(
get_relative_path("main.css"),
)
app.set_css = set_css
app.set_css()
app.run()
-37
View File
@@ -1,37 +0,0 @@
/* album_cover.set_style(f"background-image:url('{fallback_cover_path}')") */
#lockscreen-bg {
/* background-color: rgba(225, 225, 225, 0.9); */
background-image: url("/home/saumya/.current.wall");
background-size: cover;
}
* {
all: unset;
font-family: "SF Pro Rounded";
}
#password-entry {
background-color: white;
border-radius: 20px;
min-width: 300px;
color: black;
min-height: 50px;
font-size: 20px;
margin-bottom: 80px;
font-weight: bold;
}
#date {
background-color: transparent;
color: alpha(#fff, 0.5);
border: none;
font-size: 50px;
margin-top: 80px;
font-weight: bold;
}
#clock {
background-color: transparent;
color: alpha(#fff, 0.5);
border: none;
font-size: 150px;
margin-top: -20px;
font-weight: bold;
}
+2 -2
View File
@@ -13,12 +13,12 @@
@import url("./styles/widgets.css");
@import url("./styles/tray.css");
@import url("./styles/battery-widget.css");
@import url("./styles/lock.css");
* {
all: unset;
color: var(--foreground);
font-size: 13px;
font-family: "SF Pro Rounded";
font-size: unset;
}
#corner {
+65
View File
@@ -0,0 +1,65 @@
#lockscreen-bg {
background-size: cover;
}
#container-box {
min-height: 50px;
min-width: 250px;
transition: all 0.7s ease-in-out;
}
#password-entry {
background-color: alpha(#fff, 0.4);
border-radius: 20px;
color: white;
min-width: 220px;
min-height: 40px;
font-family: "SF Pro Rounded";
font-size: 14px;
font-weight: 500;
transition: all 0.7s ease-in-out;
}
#profile-box {
margin-bottom: 10px;
}
#username {
font-size: 21px;
font-weight: bold;
margin-top: 5px;
transition: all 0.7s ease-in-out;
}
#lock-date label {
background-color: transparent;
font-family: "SF Pro Rounded";
color: alpha(#fff, 0.6);
border: none;
font-size: 30px;
margin-top: 80px;
font-weight: bold;
}
#lock-clock label {
background-color: transparent;
color: alpha(#fff, 0.6);
font-family: "SF Pro Rounded ";
border: none;
/* font-family: "SF Pro Rounded"; */
font-size: 130px;
margin-top: -20px;
font-weight: bold;
}
#face-icon image {
margin-bottom: 10px;
}
#face-icon {
padding-bottom: 10px;
}
#unlock-box {
margin-bottom: 40px;
}
#unlock-text {
font-family: "SF Pro Rounded";
font-size: 15px;
color: alpha(#fff, 0.6);
font-weight: bold;
}
+125
View File
@@ -0,0 +1,125 @@
import math
from typing import Literal
import cairo
import gi
from fabric.core.service import Property
from fabric.widgets.widget import Widget
gi.require_version("Gtk", "3.0")
from gi.repository import Gdk, GdkPixbuf, Gtk # noqa: E402
class CircleImage(Gtk.DrawingArea, Widget):
"""A widget that displays an image in a circular shape with a 1:1 aspect ratio."""
@Property(int, "read-write")
def angle(self) -> int:
return self._angle
@angle.setter
def angle(self, value: int):
self._angle = value % 360
self.queue_draw()
def __init__(
self,
image_file: str | None = None,
pixbuf: GdkPixbuf.Pixbuf | None = None,
name: str | None = None,
visible: bool = True,
all_visible: bool = False,
style: str | None = None,
tooltip_text: str | None = None,
tooltip_markup: str | None = None,
h_align: (
Literal["fill", "start", "end", "center", "baseline"] | Gtk.Align | None
) = None,
v_align: (
Literal["fill", "start", "end", "center", "baseline"] | Gtk.Align | None
) = None,
h_expand: bool = False,
v_expand: bool = False,
size: int | None = None,
**kwargs,
):
Gtk.DrawingArea.__init__(self)
Widget.__init__(
self,
name=name,
visible=visible,
all_visible=all_visible,
style=style,
tooltip_text=tooltip_text,
tooltip_markup=tooltip_markup,
h_align=h_align,
v_align=v_align,
h_expand=h_expand,
v_expand=v_expand,
size=size,
**kwargs,
)
self.size = size if size is not None else 100 # Default size if not provided
self._angle = 0
# Original image for reprocessing
self._orig_image: GdkPixbuf.Pixbuf | None = None
self._image: GdkPixbuf.Pixbuf | None = None
if image_file:
pix = GdkPixbuf.Pixbuf.new_from_file(image_file)
self._orig_image = pix
self._image = self._process_image(pix)
elif pixbuf:
self._orig_image = pixbuf
self._image = self._process_image(pixbuf)
self.connect("draw", self.on_draw)
def _process_image(self, pixbuf: GdkPixbuf.Pixbuf) -> GdkPixbuf.Pixbuf:
"""Crop the image to a centered square and scale it to the widget’s size."""
width, height = pixbuf.get_width(), pixbuf.get_height()
if width != height:
square_size = min(width, height)
x_offset = (width - square_size) // 2
y_offset = (height - square_size) // 2
pixbuf = pixbuf.new_subpixbuf(x_offset, y_offset, square_size, square_size)
else:
square_size = width
if square_size != self.size:
pixbuf = pixbuf.scale_simple(
self.size, self.size, GdkPixbuf.InterpType.BILINEAR
)
return pixbuf
def on_draw(self, widget: "CircleImage", ctx: cairo.Context):
if self._image:
ctx.save()
# Create a circular clipping path
ctx.arc(self.size / 2, self.size / 2, self.size / 2, 0, 2 * math.pi)
ctx.clip()
# Rotate around the center of the square image
ctx.translate(self.size / 2, self.size / 2)
ctx.rotate(self._angle * math.pi / 180.0)
ctx.translate(-self.size / 2, -self.size / 2)
Gdk.cairo_set_source_pixbuf(ctx, self._image, 0, 0)
ctx.paint()
ctx.restore()
def set_image_from_file(self, new_image_file: str):
if not new_image_file:
return
pixbuf = GdkPixbuf.Pixbuf.new_from_file(new_image_file)
self._orig_image = pixbuf
self._image = self._process_image(pixbuf)
self.queue_draw()
def set_image_from_pixbuf(self, pixbuf: GdkPixbuf.Pixbuf):
if not pixbuf:
return
self._orig_image = pixbuf
self._image = self._process_image(pixbuf)
self.queue_draw()
def set_image_size(self, size: int):
self.size = size
if self._orig_image:
self._image = self._process_image(self._orig_image)
self.queue_draw()