Initial import. Most things seem working.
This includes an abortive attempt to do a gtk4 dialog (which I don't think is possible, as gtk4 doesn't allow embedding toplevels anymore), and an iced dialog, which I just never started writing.
This commit is contained in:
18
dialog-gtk3/Cargo.toml
Normal file
18
dialog-gtk3/Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "bscreensaver-dialog-gtk3"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
bscreensaver-util = { path = "../util" }
|
||||
chrono = "0.4"
|
||||
gethostname = "0.2"
|
||||
glib = { version = "0.15", features = ["v2_68"] }
|
||||
gtk = { version = "0.15", features = ["v3_24"] }
|
||||
gtk-sys = "0.15"
|
||||
gdk-sys = "0.15"
|
||||
gdkx11 = "0.15"
|
||||
log = "0.4"
|
||||
pam = "0.7"
|
||||
#x11 = "2.19"
|
261
dialog-gtk3/src/main.rs
Normal file
261
dialog-gtk3/src/main.rs
Normal file
@ -0,0 +1,261 @@
|
||||
use chrono::prelude::*;
|
||||
use gdkx11::X11Window;
|
||||
use gethostname::gethostname;
|
||||
use glib::GString;
|
||||
use gtk::{prelude::*, Button, Entry, Label, Plug, Window};
|
||||
use log::{debug, error};
|
||||
use std::{io::{self, Write}, process::exit, thread};
|
||||
|
||||
use bscreensaver_util::init_logging;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
init_logging("BSCREENSAVER_DIALOG_GTK3_LOG");
|
||||
|
||||
let standalone = std::env::var("BSCREENSAVER_DIALOG_STANDALONE").is_ok();
|
||||
|
||||
unsafe { glib::log_writer_default_set_use_stderr(true) };
|
||||
gtk::init()?;
|
||||
|
||||
let top_sg = gtk::SizeGroup::builder()
|
||||
.mode(gtk::SizeGroupMode::Horizontal)
|
||||
.build();
|
||||
let label_sg = gtk::SizeGroup::builder()
|
||||
.mode(gtk::SizeGroupMode::Horizontal)
|
||||
.build();
|
||||
let entry_sg = gtk::SizeGroup::builder()
|
||||
.mode(gtk::SizeGroupMode::Horizontal)
|
||||
.build();
|
||||
|
||||
let header = gtk::HeaderBar::builder()
|
||||
.title("Unlock Screen")
|
||||
.show_close_button(true)
|
||||
.build();
|
||||
|
||||
let top_vbox = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Vertical)
|
||||
.spacing(0)
|
||||
.build();
|
||||
|
||||
let dialog =
|
||||
if standalone {
|
||||
let win = Window::builder()
|
||||
.type_(gtk::WindowType::Toplevel)
|
||||
.title("Unlock Screen")
|
||||
.modal(true)
|
||||
.build();
|
||||
win.set_titlebar(Some(&header));
|
||||
win.upcast::<gtk::Window>()
|
||||
} else {
|
||||
let plug = Plug::builder()
|
||||
.type_(gtk::WindowType::Toplevel)
|
||||
.title("Unlock Screen")
|
||||
.modal(true)
|
||||
.build();
|
||||
plug.connect_embedded(|_| debug!("DIALOG EMBEDDED"));
|
||||
plug.connect_embedded_notify(|_| debug!("DIALOG EMBEDDED (notify)"));
|
||||
plug.connect_realize(|plug| {
|
||||
let plug_window = match plug.window().unwrap().downcast::<X11Window>() {
|
||||
Err(err) => {
|
||||
error!("Failed to find XID of unlock dialog window: {}", err);
|
||||
exit(2);
|
||||
},
|
||||
Ok(w) => w,
|
||||
};
|
||||
let xid = plug_window.xid() as u32;
|
||||
let xid_buf: [u8; 4] = [
|
||||
((xid >> 24) & 0xff) as u8,
|
||||
((xid >> 16) & 0xff) as u8,
|
||||
((xid >> 8) & 0xff) as u8,
|
||||
(xid & 0xff) as u8,
|
||||
];
|
||||
let out = io::stdout();
|
||||
let mut out_locked = out.lock();
|
||||
if let Err(err) = out_locked.write_all(&xid_buf).and_then(|_| out_locked.flush()) {
|
||||
error!("Failed to write XID to stdout: {}", err);
|
||||
exit(2);
|
||||
};
|
||||
});
|
||||
|
||||
// Walking the header's widget tree, finding the close button, and connecting
|
||||
// to the 'clicked' signal strangely doesn't work either, so let's just
|
||||
// disable it for now.
|
||||
header.set_show_close_button(false);
|
||||
top_vbox.pack_start(&header, true, false, 0);
|
||||
|
||||
plug.upcast::<gtk::Window>()
|
||||
};
|
||||
|
||||
// I don't know why, but this doesn't work when we're a GktPlug, despite
|
||||
// an examination of the gtk source suggesting that the header should send
|
||||
// a delete-event to the toplevel (which should be the plug) when the close
|
||||
// button is clicked. For some reason, though, we never get the delete-event.
|
||||
dialog.connect_delete_event(|_, _| exit(1));
|
||||
dialog.connect_realize(|_| debug!("DIALOG REALIZED"));
|
||||
dialog.connect_map(|_| debug!("DIALOG MAPPED"));
|
||||
dialog.connect_unmap(|_| debug!("DIALOG UNMAPPED"));
|
||||
dialog.connect_unrealize(|_| debug!("DIALOG_UNREALIZED"));
|
||||
dialog.add(&top_vbox);
|
||||
|
||||
let top_hbox = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Horizontal)
|
||||
.border_width(48)
|
||||
.spacing(8)
|
||||
.build();
|
||||
top_vbox.pack_start(&top_hbox, true, true, 0);
|
||||
|
||||
let vbox = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Vertical)
|
||||
.spacing(4)
|
||||
.build();
|
||||
top_sg.add_widget(&vbox);
|
||||
top_hbox.pack_start(&vbox, true, true, 0);
|
||||
|
||||
let attrs = gtk::pango::AttrList::new();
|
||||
attrs.insert(gtk::pango::AttrFloat::new_scale(gtk::pango::SCALE_XX_LARGE));
|
||||
let mut bold_desc = gtk::pango::FontDescription::new();
|
||||
bold_desc.set_weight(gtk::pango::Weight::Bold);
|
||||
attrs.insert(gtk::pango::AttrFontDesc::new(&bold_desc));
|
||||
let label = gtk::Label::builder()
|
||||
.label(gethostname().to_str().unwrap_or("(unknown hostname)"))
|
||||
.xalign(0.5)
|
||||
.yalign(0.5)
|
||||
.attributes(&attrs)
|
||||
.build();
|
||||
vbox.pack_start(&label, false, false, 0);
|
||||
|
||||
let attrs = gtk::pango::AttrList::new();
|
||||
attrs.insert(gtk::pango::AttrFloat::new_scale(gtk::pango::SCALE_LARGE));
|
||||
let label = gtk::Label::builder()
|
||||
.xalign(0.5)
|
||||
.yalign(0.5)
|
||||
.attributes(&attrs)
|
||||
.build();
|
||||
set_time_label(&label);
|
||||
vbox.pack_start(&label, false, false, 0);
|
||||
glib::timeout_add_seconds_local(1, move || {
|
||||
set_time_label(&label);
|
||||
glib::source::Continue(true)
|
||||
});
|
||||
|
||||
let sep = gtk::Separator::builder()
|
||||
.orientation(gtk::Orientation::Vertical)
|
||||
.build();
|
||||
top_hbox.pack_start(&sep, true, false, 0);
|
||||
|
||||
let vbox = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Vertical)
|
||||
.spacing(4)
|
||||
.build();
|
||||
top_sg.add_widget(&vbox);
|
||||
top_hbox.pack_start(&vbox, true, true, 0);
|
||||
|
||||
let hbox = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Horizontal)
|
||||
.spacing(8)
|
||||
.build();
|
||||
vbox.pack_start(&hbox, true, true, 2);
|
||||
|
||||
let label = Label::builder()
|
||||
.label("Username:")
|
||||
.xalign(0.0)
|
||||
.build();
|
||||
label_sg.add_widget(&label);
|
||||
hbox.pack_start(&label, false, true, 8);
|
||||
|
||||
let username = bscreensaver_util::get_username()?;
|
||||
let username_box = Entry::builder()
|
||||
.text(&username)
|
||||
.sensitive(false)
|
||||
.build();
|
||||
entry_sg.add_widget(&username_box);
|
||||
hbox.pack_start(&username_box, true, true, 8);
|
||||
|
||||
let hbox = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Horizontal)
|
||||
.spacing(8)
|
||||
.build();
|
||||
vbox.pack_start(&hbox, true, true, 0);
|
||||
|
||||
let label = Label::builder()
|
||||
.label("Password:")
|
||||
.xalign(0.0)
|
||||
.build();
|
||||
label_sg.add_widget(&label);
|
||||
hbox.pack_start(&label, false, true, 8);
|
||||
|
||||
let password_box = Entry::builder()
|
||||
.visibility(false)
|
||||
.input_purpose(gtk::InputPurpose::Password)
|
||||
.activates_default(true)
|
||||
.width_chars(25)
|
||||
.build();
|
||||
entry_sg.add_widget(&password_box);
|
||||
hbox.pack_start(&password_box, true, true, 8);
|
||||
password_box.connect_key_press_event(|_, ev| {
|
||||
if ev.keyval().name() == Some(GString::from("Escape")) {
|
||||
exit(1);
|
||||
}
|
||||
gtk::Inhibit(false)
|
||||
});
|
||||
password_box.grab_focus();
|
||||
|
||||
let hbox = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Horizontal)
|
||||
.spacing(8)
|
||||
.build();
|
||||
vbox.pack_start(&hbox, true, true, 2);
|
||||
|
||||
let button = Button::builder()
|
||||
.label("Unlock")
|
||||
.build();
|
||||
button.connect_clicked(move |button| {
|
||||
button.set_sensitive(false);
|
||||
password_box.set_sensitive(false);
|
||||
|
||||
let username = username.clone();
|
||||
let password = password_box.text().to_string();
|
||||
|
||||
thread::spawn(move || {
|
||||
if authenticate(&username, &password) {
|
||||
exit(0);
|
||||
} else {
|
||||
exit(-1);
|
||||
}
|
||||
});
|
||||
});
|
||||
hbox.pack_end(&button, false, true, 8);
|
||||
button.set_can_default(true);
|
||||
button.set_has_default(true);
|
||||
|
||||
dialog.show_all();
|
||||
|
||||
gtk::main();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_time_label(label: >k::Label) {
|
||||
let now = Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
||||
label.set_label(&now);
|
||||
}
|
||||
|
||||
fn authenticate(username: &String, password: &String) -> bool {
|
||||
let mut authenticator = match pam::Authenticator::with_password("xscreensaver") {
|
||||
Err(err) => {
|
||||
error!("[PAM] {}", err);
|
||||
return false;
|
||||
},
|
||||
Ok(authenticator) => authenticator,
|
||||
};
|
||||
authenticator.get_handler().set_credentials(username, password);
|
||||
if let Err(err) = authenticator.authenticate() {
|
||||
error!("[PAM] {}", err);
|
||||
return false;
|
||||
}
|
||||
if let Err(err) = authenticator.open_session() {
|
||||
error!("[PAM] {}", err);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user