356 lines
13 KiB
Rust
356 lines
13 KiB
Rust
use gtk::{glib, prelude::*};
|
|
use glib::{clone, Propagation::{Proceed, Stop}};
|
|
use log::warn;
|
|
use std::{env, process::exit, time::Duration, ffi::CString};
|
|
|
|
use bscreensaver_util::{init_logging, settings::Configuration, desktop::NewLoginCommand};
|
|
|
|
#[derive(Clone)]
|
|
struct Widgets {
|
|
lock_timeout: gtk::SpinButton,
|
|
blank_before_locking: gtk::SpinButton,
|
|
new_login_command_combo: gtk::ComboBoxText,
|
|
custom_new_login_command_entry: gtk::Entry,
|
|
handle_brightness_keys_checkbox: gtk::CheckButton,
|
|
}
|
|
|
|
fn main() -> anyhow::Result<()> {
|
|
init_logging("BSCREENSAVER_SETTINGS");
|
|
let config = Configuration::load()?;
|
|
|
|
// Can't use the rust version as it requires gtk_init() to be
|
|
// called first, but the underlying C function requires that
|
|
// it hasn't.
|
|
let backends = CString::new("x11").unwrap();
|
|
unsafe {
|
|
gtk::gdk::ffi::gdk_set_allowed_backends(backends.as_ptr());
|
|
};
|
|
|
|
let app = gtk::Application::builder()
|
|
.application_id("org.spurint.bscreensaver-settings")
|
|
.build();
|
|
app.connect_activate(move |app| show_ui(&app, &config));
|
|
|
|
exit(app.run_with_args(&env::args().into_iter().collect::<Vec<String>>()).into());
|
|
}
|
|
|
|
fn show_ui(app: >k::Application, config: &Configuration) {
|
|
let mainwin = gtk::ApplicationWindow::builder()
|
|
.application(app)
|
|
.name("BScreensaver Settings")
|
|
.title("Screensaver Settings")
|
|
.type_(gtk::WindowType::Toplevel)
|
|
.resizable(false)
|
|
.build();
|
|
|
|
let label_sg = gtk::SizeGroup::builder()
|
|
.mode(gtk::SizeGroupMode::Horizontal)
|
|
.build();
|
|
|
|
let topvbox = gtk::Box::builder()
|
|
.orientation(gtk::Orientation::Vertical)
|
|
.spacing(8)
|
|
.margin(4)
|
|
.build();
|
|
mainwin.add(&topvbox);
|
|
|
|
let hbox = gtk::Box::builder()
|
|
.orientation(gtk::Orientation::Horizontal)
|
|
.spacing(8)
|
|
.build();
|
|
topvbox.pack_start(&hbox, false, false, 0);
|
|
|
|
let label = gtk::Label::builder()
|
|
.label("Lock screen after")
|
|
.xalign(0.0)
|
|
.build();
|
|
label_sg.add_widget(&label);
|
|
hbox.pack_start(&label, false, false, 0);
|
|
|
|
let lock_timeout_spinbutton = gtk::SpinButton::builder()
|
|
.adjustment(>k::Adjustment::builder()
|
|
.lower(1.0)
|
|
.upper(f64::MAX as u32 as f64)
|
|
.step_increment(1.0)
|
|
.page_increment(5.0)
|
|
.value((config.lock_timeout.as_secs() / 60) as f64)
|
|
.build()
|
|
)
|
|
.tooltip_text("Minutes before the screen locks")
|
|
.activates_default(true)
|
|
.build();
|
|
hbox.pack_start(&lock_timeout_spinbutton, false, false, 0);
|
|
|
|
let label = gtk::Label::builder()
|
|
.label("minutes")
|
|
.xalign(0.0)
|
|
.build();
|
|
hbox.pack_start(&label, false, false, 0);
|
|
|
|
let hbox = gtk::Box::builder()
|
|
.orientation(gtk::Orientation::Horizontal)
|
|
.spacing(8)
|
|
.build();
|
|
topvbox.pack_start(&hbox, false, false, 0);
|
|
|
|
let label = gtk::Label::builder()
|
|
.label("Blank screen")
|
|
.xalign(0.0)
|
|
.build();
|
|
label_sg.add_widget(&label);
|
|
hbox.pack_start(&label, false, false, 0);
|
|
|
|
let blank_before_locking_spinbutton = gtk::SpinButton::builder()
|
|
.adjustment(>k::Adjustment::builder()
|
|
.lower(0.0)
|
|
.upper(f64::MAX as u32 as f64)
|
|
.step_increment(1.0)
|
|
.page_increment(5.0)
|
|
.value((config.blank_before_locking.as_secs() / 60) as f64)
|
|
.build()
|
|
)
|
|
.tooltip_text("Minutes before screen locks to blank the screen")
|
|
.activates_default(true)
|
|
.build();
|
|
hbox.pack_start(&blank_before_locking_spinbutton, false, false, 0);
|
|
|
|
let label = gtk::Label::builder()
|
|
.label("minutes before locking")
|
|
.xalign(0.0)
|
|
.build();
|
|
hbox.pack_start(&label, false, false, 0);
|
|
|
|
let hbox = gtk::Box::builder()
|
|
.orientation(gtk::Orientation::Horizontal)
|
|
.spacing(8)
|
|
.build();
|
|
topvbox.pack_start(&hbox, false, false, 0);
|
|
|
|
let label = gtk::Label::builder()
|
|
.label("New Login Command:")
|
|
.tooltip_text("Command to run when the 'New Login' button is clicked in the unlock dialog")
|
|
.xalign(0.0)
|
|
.build();
|
|
label_sg.add_widget(&label);
|
|
hbox.pack_start(&label, false, false, 0);
|
|
|
|
let vbox = gtk::Box::builder()
|
|
.orientation(gtk::Orientation::Vertical)
|
|
.spacing(8)
|
|
.build();
|
|
hbox.pack_start(&vbox, true, true, 0);
|
|
|
|
let new_login_command_combo = gtk::ComboBoxText::builder()
|
|
.build();
|
|
new_login_command_combo.append_text(NewLoginCommand::Auto.as_str());
|
|
new_login_command_combo.append_text(NewLoginCommand::DisplayManagerDBus.as_str());
|
|
new_login_command_combo.append_text(NewLoginCommand::Gdm.as_str());
|
|
new_login_command_combo.append_text(NewLoginCommand::Kdm.as_str());
|
|
new_login_command_combo.append_text(NewLoginCommand::LightDm.as_str());
|
|
new_login_command_combo.append_text(NewLoginCommand::Lxdm.as_str());
|
|
new_login_command_combo.append_text(NewLoginCommand::Custom("".to_string()).as_str());
|
|
new_login_command_combo.append_text(NewLoginCommand::Disabled.as_str());
|
|
vbox.pack_start(&new_login_command_combo, false, false, 0);
|
|
|
|
let (custom_new_login_command_hbox, custom_new_login_command_entry, custom_new_login_command_button) = {
|
|
let hbox = gtk::Box::builder()
|
|
.orientation(gtk::Orientation::Horizontal)
|
|
.spacing(8)
|
|
.sensitive(false)
|
|
.build();
|
|
topvbox.pack_start(&hbox, false, false, 0);
|
|
|
|
let spacer = gtk::Box::builder()
|
|
.orientation(gtk::Orientation::Horizontal)
|
|
.spacing(0)
|
|
.build();
|
|
label_sg.add_widget(&spacer);
|
|
hbox.pack_start(&spacer, false, false, 0);
|
|
|
|
let label = gtk::Label::builder()
|
|
.label("Custom Command:")
|
|
.tooltip_text("Custom command to run when the 'New Login' button is clicked in the unlock dialog")
|
|
.xalign(0.0)
|
|
.build();
|
|
hbox.pack_start(&label, false, false, 0);
|
|
|
|
let entry = gtk::Entry::builder()
|
|
.width_chars(30)
|
|
.activates_default(true)
|
|
.build();
|
|
hbox.pack_start(&entry, false, false, 0);
|
|
|
|
let button = gtk::Button::from_icon_name(Some("folder-open"), gtk::IconSize::Button);
|
|
hbox.pack_start(&button, false, false, 0);
|
|
|
|
(hbox, entry, button)
|
|
};
|
|
|
|
match &config.new_login_command {
|
|
NewLoginCommand::Custom(cmd) => {
|
|
custom_new_login_command_entry.set_text(cmd.as_str());
|
|
custom_new_login_command_hbox.set_sensitive(true);
|
|
},
|
|
_ => (),
|
|
};
|
|
new_login_command_combo.set_active(Some(config.new_login_command.ord()));
|
|
new_login_command_combo.connect_changed(clone!(@strong custom_new_login_command_hbox => move |combo| {
|
|
let sensitive = combo.active().unwrap_or(0) == NewLoginCommand::Custom("".to_string()).ord();
|
|
custom_new_login_command_hbox.set_sensitive(sensitive);
|
|
}));
|
|
|
|
let hbox = gtk::Box::builder()
|
|
.orientation(gtk::Orientation::Horizontal)
|
|
.spacing(8)
|
|
.build();
|
|
topvbox.pack_start(&hbox, false, false, 0);
|
|
|
|
let spacer = gtk::Box::builder()
|
|
.orientation(gtk::Orientation::Horizontal)
|
|
.spacing(0)
|
|
.build();
|
|
label_sg.add_widget(&spacer);
|
|
hbox.pack_start(&spacer, false, false, 0);
|
|
|
|
let handle_brightness_keys_checkbox = gtk::CheckButton::builder()
|
|
.label("Handle brightness keys")
|
|
.active(config.handle_brightness_keys)
|
|
.build();
|
|
hbox.pack_start(&handle_brightness_keys_checkbox, false, false, 0);
|
|
|
|
let widgets = Widgets {
|
|
lock_timeout: lock_timeout_spinbutton.clone(),
|
|
blank_before_locking: blank_before_locking_spinbutton.clone(),
|
|
new_login_command_combo: new_login_command_combo.clone(),
|
|
custom_new_login_command_entry: custom_new_login_command_entry.clone(),
|
|
handle_brightness_keys_checkbox: handle_brightness_keys_checkbox.clone(),
|
|
};
|
|
mainwin.connect_delete_event(clone!(@strong config, @strong widgets, @strong app, @strong mainwin => move |_,_| {
|
|
if !confirm_cancel(&config, &widgets, &mainwin) {
|
|
Stop
|
|
} else {
|
|
Proceed
|
|
}
|
|
}));
|
|
custom_new_login_command_button.connect_clicked(clone!(@strong mainwin, @strong widgets => move |_| {
|
|
run_file_chooser(&mainwin, &widgets);
|
|
}));
|
|
|
|
let button_box = gtk::ButtonBox::builder()
|
|
.spacing(8)
|
|
.layout_style(gtk::ButtonBoxStyle::End)
|
|
.build();
|
|
topvbox.pack_end(&button_box, false, false, 0);
|
|
|
|
let close_button = gtk::Button::builder()
|
|
.label("Close")
|
|
.build();
|
|
close_button.connect_clicked(clone!(@strong config, @strong widgets, @strong app, @strong mainwin => move |_| {
|
|
if confirm_cancel(&config, &widgets, &mainwin) {
|
|
app.quit();
|
|
}
|
|
}));
|
|
button_box.pack_end(&close_button, false, false, 0);
|
|
|
|
let save_button = gtk::Button::builder()
|
|
.label("Save")
|
|
.build();
|
|
save_button.connect_clicked(clone!(@strong config, @strong widgets, @strong app, @strong mainwin => move |_| {
|
|
if save_config(&config, &widgets, &mainwin) {
|
|
app.quit();
|
|
}
|
|
}));
|
|
button_box.pack_end(&save_button, false, false, 0);
|
|
save_button.set_can_default(true);
|
|
save_button.set_has_default(true);
|
|
|
|
mainwin.show_all();
|
|
}
|
|
|
|
fn run_file_chooser(mainwin: >k::ApplicationWindow, widgets: &Widgets) {
|
|
let file_chooser = gtk::FileChooserNative::new(
|
|
Some("Custom New Login Command"),
|
|
Some(mainwin),
|
|
gtk::FileChooserAction::Open,
|
|
Some("Select"),
|
|
Some("Cancel"),
|
|
);
|
|
let response = file_chooser.run();
|
|
file_chooser.hide();
|
|
if response == gtk::ResponseType::Accept {
|
|
if let Some(filename) = file_chooser.filename() {
|
|
widgets.custom_new_login_command_entry.set_text(&filename.to_string_lossy());
|
|
}
|
|
}
|
|
}
|
|
|
|
fn save_config(config: &Configuration, widgets: &Widgets, mainwin: >k::ApplicationWindow) -> bool{
|
|
let (new_config, changed) = build_new_configuration(&config, &widgets);
|
|
if changed {
|
|
if let Err(err) = new_config.save() {
|
|
let error_dialog = gtk::MessageDialog::new(
|
|
Some(mainwin),
|
|
gtk::DialogFlags::MODAL | gtk::DialogFlags::DESTROY_WITH_PARENT,
|
|
gtk::MessageType::Error,
|
|
gtk::ButtonsType::Close,
|
|
&format!("Failed to save configuration: {}", err),
|
|
);
|
|
error_dialog.set_title("Screensaver Settings");
|
|
error_dialog.run();
|
|
error_dialog.hide();
|
|
return false;
|
|
}
|
|
}
|
|
true
|
|
}
|
|
|
|
fn confirm_cancel(config: &Configuration, widgets: &Widgets, mainwin: >k::ApplicationWindow) -> bool {
|
|
let (_, changed) = build_new_configuration(&config, &widgets);
|
|
if changed {
|
|
let dialog = gtk::MessageDialog::new(
|
|
Some(mainwin),
|
|
gtk::DialogFlags::MODAL | gtk::DialogFlags::DESTROY_WITH_PARENT,
|
|
gtk::MessageType::Warning,
|
|
gtk::ButtonsType::None,
|
|
"You have unsaved changes. Are you sure you want to close?",
|
|
);
|
|
dialog.set_title("Screensaver Settings");
|
|
dialog.add_buttons(&[
|
|
("Close Window", gtk::ResponseType::Close),
|
|
("Cancel", gtk::ResponseType::Cancel),
|
|
]);
|
|
dialog.set_default_response(gtk::ResponseType::Cancel);
|
|
let response = dialog.run();
|
|
dialog.hide();
|
|
match response {
|
|
gtk::ResponseType::Close => true,
|
|
_ => false,
|
|
}
|
|
} else {
|
|
true
|
|
}
|
|
}
|
|
|
|
fn build_new_configuration(old_config: &Configuration, widgets: &Widgets) -> (Configuration, bool) {
|
|
let mut new_config = old_config.clone();
|
|
new_config.lock_timeout = Duration::from_secs(widgets.lock_timeout.adjustment().value() as u64 * 60);
|
|
new_config.blank_before_locking = Duration::from_secs(widgets.blank_before_locking.adjustment().value() as u64 * 60);
|
|
|
|
new_config.new_login_command = match NewLoginCommand::try_from(widgets.new_login_command_combo.active().unwrap_or(0) as u32) {
|
|
Ok(NewLoginCommand::Custom(_)) => Some(widgets.custom_new_login_command_entry.text())
|
|
.filter(|s| !s.is_empty())
|
|
.map(|s| NewLoginCommand::Custom(s.to_string()))
|
|
.unwrap_or(NewLoginCommand::Auto),
|
|
Ok(nlc) => nlc,
|
|
Err(_) => {
|
|
warn!("BUG: couldn't figure out new login command type from combo box");
|
|
NewLoginCommand::Auto
|
|
},
|
|
};
|
|
|
|
new_config.handle_brightness_keys = widgets.handle_brightness_keys_checkbox.is_active();
|
|
|
|
let changed = old_config != &new_config;
|
|
(new_config, changed)
|
|
}
|