Refactor a bit and add backlight brightness keys handling
This will only work if the video driver supports the xrandr backlight property. It's possible only Intel does this...
This commit is contained in:
parent
6e5dfabcfd
commit
8eb8dfac2e
2
TODO.md
2
TODO.md
@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
* Fully audit the locker source to ensure that it cannot crash based on
|
* Fully audit the locker source to ensure that it cannot crash based on
|
||||||
error handling in its own code.
|
error handling in its own code.
|
||||||
* Add the (optional) ability to allow some key presses through, such as
|
|
||||||
screen brightness keys.
|
|
||||||
* Add support for running screensaver programs ("hacks" in
|
* Add support for running screensaver programs ("hacks" in
|
||||||
`xscreensaver` parlance) that draw interesting things on
|
`xscreensaver` parlance) that draw interesting things on
|
||||||
`bscreensaver`'s blanker windows. Without this, `bscreensaver` is
|
`bscreensaver`'s blanker windows. Without this, `bscreensaver` is
|
||||||
|
@ -18,3 +18,6 @@ dialog-backend = "gtk3"
|
|||||||
# Adds a 'New Login' button to the unlock dialog that will run the
|
# Adds a 'New Login' button to the unlock dialog that will run the
|
||||||
# specified command when clicked
|
# specified command when clicked
|
||||||
new-login-command = "dm-tool switch-to-greeter"
|
new-login-command = "dm-tool switch-to-greeter"
|
||||||
|
# Whether or not to attempt to raise and lower the screen brightness
|
||||||
|
# when the brightness keys are pressed
|
||||||
|
handle-brightness-keys = false
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
mod monitor;
|
||||||
mod pidfd;
|
mod pidfd;
|
||||||
|
|
||||||
use clap::{Arg, Command as ClapCommand};
|
use clap::{Arg, Command as ClapCommand};
|
||||||
@ -27,23 +28,12 @@ use xcb_xembed::embedder::Embedder;
|
|||||||
|
|
||||||
use bscreensaver_command::{BCommand, create_command_window, bscreensaver_command_response};
|
use bscreensaver_command::{BCommand, create_command_window, bscreensaver_command_response};
|
||||||
use bscreensaver_util::{*, settings::Configuration};
|
use bscreensaver_util::{*, settings::Configuration};
|
||||||
|
use monitor::*;
|
||||||
use pidfd::{CreatePidFd, PidFd};
|
use pidfd::{CreatePidFd, PidFd};
|
||||||
|
|
||||||
const BLANKED_ARG: &str = "blanked";
|
const BLANKED_ARG: &str = "blanked";
|
||||||
const LOCKED_ARG: &str = "locked";
|
const LOCKED_ARG: &str = "locked";
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
struct Monitor {
|
|
||||||
pub root: x::Window,
|
|
||||||
pub black_gc: x::Gcontext,
|
|
||||||
pub blanker_window: x::Window,
|
|
||||||
pub unlock_window: x::Window,
|
|
||||||
pub x: i16,
|
|
||||||
pub y: i16,
|
|
||||||
pub width: u16,
|
|
||||||
pub height: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
||||||
enum BlankerState {
|
enum BlankerState {
|
||||||
Idle = 0,
|
Idle = 0,
|
||||||
@ -135,7 +125,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
let mut state = State {
|
let mut state = State {
|
||||||
config,
|
config,
|
||||||
monitors: create_blanker_windows(&conn)?,
|
monitors: Monitor::set_up_all(&conn)?,
|
||||||
dbus_service: None,
|
dbus_service: None,
|
||||||
systemd_service: None,
|
systemd_service: None,
|
||||||
last_user_activity: Instant::now(),
|
last_user_activity: Instant::now(),
|
||||||
@ -322,131 +312,6 @@ fn init_randr(conn: &xcb::Connection) -> anyhow::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_blanker_windows(conn: &xcb::Connection) -> xcb::Result<Vec<Monitor>> {
|
|
||||||
let mut cookies = Vec::new();
|
|
||||||
let mut monitors = Vec::new();
|
|
||||||
for screen in conn.get_setup().roots() {
|
|
||||||
let cookie = conn.send_request(&randr::GetScreenResources {
|
|
||||||
window: screen.root(),
|
|
||||||
});
|
|
||||||
let reply = conn.wait_for_reply(cookie)?;
|
|
||||||
let config_timestamp = reply.config_timestamp();
|
|
||||||
for output in reply.outputs() {
|
|
||||||
let cookie = conn.send_request(&randr::GetOutputInfo {
|
|
||||||
output: *output,
|
|
||||||
config_timestamp,
|
|
||||||
});
|
|
||||||
let reply = conn.wait_for_reply(cookie)?;
|
|
||||||
if !reply.crtc().is_none() {
|
|
||||||
let cookie = conn.send_request(&randr::GetCrtcInfo {
|
|
||||||
crtc: reply.crtc(),
|
|
||||||
config_timestamp,
|
|
||||||
});
|
|
||||||
let reply = conn.wait_for_reply(cookie)?;
|
|
||||||
|
|
||||||
let blanker_window: x::Window = conn.generate_id();
|
|
||||||
let unlock_window: x::Window = conn.generate_id();
|
|
||||||
|
|
||||||
debug!("creating blanker window 0x{:x}, {}x{}+{}+{}; unlock window 0x{:x}", blanker_window.resource_id(), reply.width(), reply.height(), reply.x(), reply.y(), unlock_window.resource_id());
|
|
||||||
cookies.push(conn.send_request_checked(&x::CreateWindow {
|
|
||||||
depth: x::COPY_FROM_PARENT as u8,
|
|
||||||
wid: blanker_window,
|
|
||||||
parent: screen.root(),
|
|
||||||
x: reply.x(),
|
|
||||||
y: reply.y(),
|
|
||||||
width: reply.width(),
|
|
||||||
height: reply.height(),
|
|
||||||
border_width: 0,
|
|
||||||
class: x::WindowClass::InputOutput,
|
|
||||||
visual: x::COPY_FROM_PARENT,
|
|
||||||
value_list: &[
|
|
||||||
x::Cw::BackPixel(screen.black_pixel()),
|
|
||||||
x::Cw::BorderPixel(screen.black_pixel()),
|
|
||||||
x::Cw::OverrideRedirect(true),
|
|
||||||
x::Cw::SaveUnder(true),
|
|
||||||
x::Cw::EventMask(x::EventMask::KEY_PRESS | x::EventMask::KEY_RELEASE | x::EventMask::BUTTON_PRESS | x::EventMask::BUTTON_RELEASE | x::EventMask::POINTER_MOTION | x::EventMask::POINTER_MOTION_HINT | x::EventMask::EXPOSURE),
|
|
||||||
],
|
|
||||||
}));
|
|
||||||
cookies.push(conn.send_request_checked(&x::ChangeProperty {
|
|
||||||
mode: x::PropMode::Replace,
|
|
||||||
window: blanker_window,
|
|
||||||
property: x::ATOM_WM_NAME,
|
|
||||||
r#type: x::ATOM_STRING,
|
|
||||||
data: b"bscreensaver blanker window",
|
|
||||||
}));
|
|
||||||
cookies.push(conn.send_request_checked(&x::ChangeProperty {
|
|
||||||
mode: x::PropMode::Replace,
|
|
||||||
window: blanker_window,
|
|
||||||
property: x::ATOM_WM_CLASS,
|
|
||||||
r#type: x::ATOM_STRING,
|
|
||||||
data: BSCREENSAVER_WM_CLASS,
|
|
||||||
}));
|
|
||||||
|
|
||||||
cookies.push(conn.send_request_checked(&x::CreateWindow {
|
|
||||||
depth: x::COPY_FROM_PARENT as u8,
|
|
||||||
wid: unlock_window,
|
|
||||||
parent: blanker_window,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
border_width: 0,
|
|
||||||
class: x::WindowClass::InputOutput,
|
|
||||||
visual: x::COPY_FROM_PARENT,
|
|
||||||
value_list: &[
|
|
||||||
x::Cw::BackPixel(screen.black_pixel()),
|
|
||||||
x::Cw::BorderPixel(screen.black_pixel()),
|
|
||||||
x::Cw::OverrideRedirect(true),
|
|
||||||
x::Cw::SaveUnder(true),
|
|
||||||
x::Cw::EventMask(x::EventMask::KEY_PRESS | x::EventMask::KEY_RELEASE | x::EventMask::BUTTON_PRESS | x::EventMask::BUTTON_RELEASE | x::EventMask::POINTER_MOTION | x::EventMask::POINTER_MOTION_HINT | x::EventMask::STRUCTURE_NOTIFY | x::EventMask::EXPOSURE),
|
|
||||||
],
|
|
||||||
}));
|
|
||||||
cookies.push(conn.send_request_checked(&x::ChangeProperty {
|
|
||||||
mode: x::PropMode::Replace,
|
|
||||||
window: unlock_window,
|
|
||||||
property: x::ATOM_WM_NAME,
|
|
||||||
r#type: x::ATOM_STRING,
|
|
||||||
data: b"bscreensaver unlock dialog socket window",
|
|
||||||
}));
|
|
||||||
cookies.push(conn.send_request_checked(&x::ChangeProperty {
|
|
||||||
mode: x::PropMode::Replace,
|
|
||||||
window: unlock_window,
|
|
||||||
property: x::ATOM_WM_CLASS,
|
|
||||||
r#type: x::ATOM_STRING,
|
|
||||||
data: BSCREENSAVER_WM_CLASS,
|
|
||||||
}));
|
|
||||||
|
|
||||||
let black_gc: x::Gcontext = conn.generate_id();
|
|
||||||
cookies.push(conn.send_request_checked(&x::CreateGc {
|
|
||||||
cid: black_gc,
|
|
||||||
drawable: x::Drawable::Window(screen.root()),
|
|
||||||
value_list: &[
|
|
||||||
x::Gc::Foreground(screen.black_pixel()),
|
|
||||||
x::Gc::Background(screen.black_pixel()),
|
|
||||||
],
|
|
||||||
}));
|
|
||||||
|
|
||||||
monitors.push(Monitor {
|
|
||||||
root: screen.root(),
|
|
||||||
black_gc,
|
|
||||||
blanker_window,
|
|
||||||
unlock_window,
|
|
||||||
x: reply.x(),
|
|
||||||
y: reply.y(),
|
|
||||||
width: reply.width(),
|
|
||||||
height: reply.height(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for cookie in cookies {
|
|
||||||
conn.check_request(cookie)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(monitors)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_subservice(binary_name: &str) -> anyhow::Result<(Child, PidFd)> {
|
fn start_subservice(binary_name: &str) -> anyhow::Result<(Child, PidFd)> {
|
||||||
let child = Command::new(format!("{}/{}", env!("HELPER_DIR"), binary_name))
|
let child = Command::new(format!("{}/{}", env!("HELPER_DIR"), binary_name))
|
||||||
.spawn()?;
|
.spawn()?;
|
||||||
@ -519,6 +384,13 @@ fn handle_xcb_events<'a>(conn: &'a xcb::Connection, state: &mut State<'a>, comma
|
|||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Allow the code below to handle key presses regardless of whether or not
|
||||||
|
// the embedder handled it.
|
||||||
|
let embedder_handled = match event {
|
||||||
|
xcb::Event::X(x::Event::KeyPress(_)) => false,
|
||||||
|
_ => embedder_handled,
|
||||||
|
};
|
||||||
|
|
||||||
if !embedder_handled {
|
if !embedder_handled {
|
||||||
match event {
|
match event {
|
||||||
xcb::Event::RandR(randr::Event::Notify(ev)) => {
|
xcb::Event::RandR(randr::Event::Notify(ev)) => {
|
||||||
@ -528,7 +400,7 @@ fn handle_xcb_events<'a>(conn: &'a xcb::Connection, state: &mut State<'a>, comma
|
|||||||
destroy_window(&conn, monitor.blanker_window)?;
|
destroy_window(&conn, monitor.blanker_window)?;
|
||||||
destroy_gc(&conn, monitor.black_gc)?;
|
destroy_gc(&conn, monitor.black_gc)?;
|
||||||
}
|
}
|
||||||
state.monitors = create_blanker_windows(&conn)?;
|
state.monitors = Monitor::set_up_all(conn)?;
|
||||||
match state.blanker_state {
|
match state.blanker_state {
|
||||||
BlankerState::Idle => (),
|
BlankerState::Idle => (),
|
||||||
BlankerState::Blanked => {
|
BlankerState::Blanked => {
|
||||||
@ -629,16 +501,42 @@ fn handle_xcb_events<'a>(conn: &'a xcb::Connection, state: &mut State<'a>, comma
|
|||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ev @ xcb::Event::X(x::Event::MotionNotify(_)) | ev @ xcb::Event::X(x::Event::KeyPress(_)) => match state.blanker_state {
|
ev @ xcb::Event::X(x::Event::MotionNotify(_)) => handle_user_activity(conn, state, ev)?,
|
||||||
BlankerState::Idle => (),
|
xcb::Event::X(x::Event::KeyPress(ev)) => {
|
||||||
BlankerState::Blanked => unblank_screen(conn, state)?,
|
if let Err(err) = keysym_for_keypress(conn, &ev).and_then(|keysym| match keysym {
|
||||||
|
Some(keysym) if keysym == xkb::key::XF86MonBrightnessUp && state.config.handle_brightness_keys => state.monitors.iter().map(|monitor| monitor.brightness_up(conn)).collect(),
|
||||||
|
Some(keysym) if keysym == xkb::key::XF86MonBrightnessDown && state.config.handle_brightness_keys => state.monitors.iter().map(|monitor| monitor.brightness_down(conn)).collect(),
|
||||||
|
_ => Ok(()),
|
||||||
|
}) {
|
||||||
|
warn!("Failed to handle key press {}: {}", ev.detail(), err);
|
||||||
|
}
|
||||||
|
handle_user_activity(conn, state, xcb::Event::X(x::Event::KeyPress(ev)))?;
|
||||||
|
},
|
||||||
|
ev => trace!("Got other event: {:#?}", ev),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_user_activity<'a>(conn: &'a xcb::Connection, state: &mut State<'a>, ev: xcb::Event) -> anyhow::Result<()> {
|
||||||
|
match state.blanker_state {
|
||||||
|
BlankerState::Idle => Ok(()),
|
||||||
|
BlankerState::Blanked => unblank_screen(conn, state),
|
||||||
BlankerState::Locked => match &state.unlock_dialog {
|
BlankerState::Locked => match &state.unlock_dialog {
|
||||||
None => state.unlock_dialog = match start_unlock_dialog(&conn, state, Some(ev)) {
|
None => {
|
||||||
|
state.unlock_dialog = match start_unlock_dialog(&conn, state, Some(ev)) {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("Unable to start unlock dialog: {}", err);
|
error!("Unable to start unlock dialog: {}", err);
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
Ok(unlock_dialog) => Some(unlock_dialog),
|
Ok(unlock_dialog) => Some(unlock_dialog),
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
},
|
},
|
||||||
Some(unlock_dialog) => {
|
Some(unlock_dialog) => {
|
||||||
let mut cookies = Vec::new();
|
let mut cookies = Vec::new();
|
||||||
@ -653,18 +551,10 @@ fn handle_xcb_events<'a>(conn: &'a xcb::Connection, state: &mut State<'a>, comma
|
|||||||
for cookie in cookies {
|
for cookie in cookies {
|
||||||
conn.check_request(cookie)?;
|
conn.check_request(cookie)?;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ev => trace!("Got other event: {:#?}", ev),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn do_command_reply(conn: &xcb::Connection, ev: &x::ClientMessageEvent, is_success: bool) {
|
fn do_command_reply(conn: &xcb::Connection, ev: &x::ClientMessageEvent, is_success: bool) {
|
||||||
@ -740,7 +630,9 @@ fn start_unlock_dialog<'a>(conn: &'a xcb::Connection, state: &State<'a>, trigger
|
|||||||
state.monitors.iter().nth(0).unwrap()
|
state.monitors.iter().nth(0).unwrap()
|
||||||
});
|
});
|
||||||
|
|
||||||
show_cursor(conn, state);
|
for monitor in &state.monitors {
|
||||||
|
monitor.show_cursor(conn);
|
||||||
|
}
|
||||||
|
|
||||||
let trigger_event = match trigger_event {
|
let trigger_event = match trigger_event {
|
||||||
Some(xcb::Event::X(x::Event::KeyPress(ev))) => match keysym_for_keypress(conn, &ev) {
|
Some(xcb::Event::X(x::Event::KeyPress(ev))) => match keysym_for_keypress(conn, &ev) {
|
||||||
@ -796,21 +688,9 @@ fn start_unlock_dialog<'a>(conn: &'a xcb::Connection, state: &State<'a>, trigger
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_cursor(conn: &xcb::Connection, state: &State) {
|
|
||||||
for monitor in &state.monitors {
|
|
||||||
conn.send_request(&xfixes::ShowCursor {
|
|
||||||
window: monitor.blanker_window,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hide_cursor(conn: &xcb::Connection, state: &State) {
|
fn hide_cursor(conn: &xcb::Connection, state: &State) {
|
||||||
for monitor in &state.monitors {
|
for monitor in &state.monitors {
|
||||||
if let Err(err) = conn.send_and_check_request(&xfixes::HideCursor {
|
monitor.hide_cursor(conn);
|
||||||
window: monitor.blanker_window,
|
|
||||||
}) {
|
|
||||||
warn!("Failed to hide cursor: {}", err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -822,24 +702,8 @@ fn blank_screen(conn: &xcb::Connection, state: &mut State) -> anyhow::Result<()>
|
|||||||
info!("Blanking");
|
info!("Blanking");
|
||||||
|
|
||||||
for monitor in &state.monitors {
|
for monitor in &state.monitors {
|
||||||
let mut cookies = Vec::new();
|
monitor.blank(conn)?;
|
||||||
|
|
||||||
cookies.push(conn.send_request_checked(&x::ConfigureWindow {
|
|
||||||
window: monitor.blanker_window,
|
|
||||||
value_list: &[
|
|
||||||
x::ConfigWindow::StackMode(x::StackMode::Above),
|
|
||||||
],
|
|
||||||
}));
|
|
||||||
cookies.push(conn.send_request_checked(&x::MapWindow {
|
|
||||||
window: monitor.blanker_window,
|
|
||||||
}));
|
|
||||||
|
|
||||||
for cookie in cookies {
|
|
||||||
conn.check_request(cookie)?;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
hide_cursor(conn, state);
|
|
||||||
|
|
||||||
state.blanker_state = BlankerState::Blanked;
|
state.blanker_state = BlankerState::Blanked;
|
||||||
|
|
||||||
@ -853,16 +717,8 @@ fn unblank_screen(conn: &xcb::Connection, state: &mut State) -> anyhow::Result<(
|
|||||||
|
|
||||||
info!("Unblanking");
|
info!("Unblanking");
|
||||||
|
|
||||||
show_cursor(conn, state);
|
|
||||||
|
|
||||||
let mut cookies = Vec::new();
|
|
||||||
for monitor in &state.monitors {
|
for monitor in &state.monitors {
|
||||||
cookies.push(conn.send_request_checked(&x::UnmapWindow {
|
monitor.unblank(conn)?;
|
||||||
window: monitor.blanker_window,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
for cookie in cookies {
|
|
||||||
conn.check_request(cookie)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state.blanker_state = BlankerState::Idle;
|
state.blanker_state = BlankerState::Idle;
|
||||||
@ -879,50 +735,7 @@ fn lock_screen(conn: &xcb::Connection, state: &mut State) -> anyhow::Result<()>
|
|||||||
info!("Locking");
|
info!("Locking");
|
||||||
|
|
||||||
for monitor in &state.monitors {
|
for monitor in &state.monitors {
|
||||||
let mut cookies = Vec::new();
|
monitor.lock(conn)?;
|
||||||
|
|
||||||
cookies.push(conn.send_request_checked(&x::ConfigureWindow {
|
|
||||||
window: monitor.unlock_window,
|
|
||||||
value_list: &[
|
|
||||||
x::ConfigWindow::StackMode(x::StackMode::Above),
|
|
||||||
],
|
|
||||||
}));
|
|
||||||
cookies.push(conn.send_request_checked(&x::MapWindow {
|
|
||||||
window: monitor.unlock_window,
|
|
||||||
}));
|
|
||||||
|
|
||||||
for cookie in cookies {
|
|
||||||
conn.check_request(cookie)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cookie = conn.send_request(&x::GrabKeyboard {
|
|
||||||
owner_events: true,
|
|
||||||
grab_window: monitor.unlock_window,
|
|
||||||
time: x::CURRENT_TIME,
|
|
||||||
pointer_mode: x::GrabMode::Async,
|
|
||||||
keyboard_mode: x::GrabMode::Async,
|
|
||||||
});
|
|
||||||
let reply = conn.wait_for_reply(cookie)?;
|
|
||||||
if reply.status() != x::GrabStatus::Success {
|
|
||||||
// FIXME: try to grab later?
|
|
||||||
warn!("Failed to grab keyboard on window {:?}: {:?}", monitor.blanker_window, reply.status());
|
|
||||||
}
|
|
||||||
|
|
||||||
let cookie = conn.send_request(&x::GrabPointer {
|
|
||||||
owner_events: true,
|
|
||||||
grab_window: monitor.unlock_window,
|
|
||||||
event_mask: x::EventMask::BUTTON_PRESS | x::EventMask::BUTTON_RELEASE | x::EventMask::POINTER_MOTION | x::EventMask::POINTER_MOTION_HINT,
|
|
||||||
pointer_mode: x::GrabMode::Async,
|
|
||||||
keyboard_mode: x::GrabMode::Async,
|
|
||||||
confine_to: monitor.blanker_window,
|
|
||||||
cursor: x::CURSOR_NONE,
|
|
||||||
time: x::CURRENT_TIME,
|
|
||||||
});
|
|
||||||
let reply = conn.wait_for_reply(cookie)?;
|
|
||||||
if reply.status() != x::GrabStatus::Success {
|
|
||||||
// FIXME: try to grab later?
|
|
||||||
warn!("Failed to grab pointer on window {:?}: {:?}", monitor.blanker_window, reply.status());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state.blanker_state = BlankerState::Locked;
|
state.blanker_state = BlankerState::Locked;
|
||||||
@ -937,20 +750,8 @@ fn unlock_screen(conn: &xcb::Connection, state: &mut State) -> anyhow::Result<()
|
|||||||
|
|
||||||
info!("Unlocking");
|
info!("Unlocking");
|
||||||
|
|
||||||
let mut cookies = Vec::new();
|
|
||||||
for monitor in &state.monitors {
|
for monitor in &state.monitors {
|
||||||
cookies.push(conn.send_request_checked(&x::UngrabKeyboard {
|
monitor.unlock(conn)?;
|
||||||
time: x::CURRENT_TIME,
|
|
||||||
}));
|
|
||||||
cookies.push(conn.send_request_checked(&x::UngrabPointer {
|
|
||||||
time: x::CURRENT_TIME,
|
|
||||||
}));
|
|
||||||
cookies.push(conn.send_request_checked(&x::UnmapWindow {
|
|
||||||
window: monitor.unlock_window,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
for cookie in cookies {
|
|
||||||
conn.check_request(cookie)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state.blanker_state = BlankerState::Blanked;
|
state.blanker_state = BlankerState::Blanked;
|
||||||
|
363
locker/src/monitor.rs
Normal file
363
locker/src/monitor.rs
Normal file
@ -0,0 +1,363 @@
|
|||||||
|
use log::{debug, warn};
|
||||||
|
use std::cmp;
|
||||||
|
use xcb::{x, randr, xfixes, Xid};
|
||||||
|
|
||||||
|
use bscreensaver_util::{BSCREENSAVER_WM_CLASS, create_atom};
|
||||||
|
|
||||||
|
const BACKLIGHT_ATOM_NAME: &[u8] = b"Backlight";
|
||||||
|
const BACKLIGHT_FALLBACK_ATOM_NAME: &[u8] = b"BACKLIGHT";
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct BacklightControl {
|
||||||
|
property: x::Atom,
|
||||||
|
min_level: i32,
|
||||||
|
max_level: i32,
|
||||||
|
step: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct Monitor {
|
||||||
|
pub root: x::Window,
|
||||||
|
pub black_gc: x::Gcontext,
|
||||||
|
pub output: randr::Output,
|
||||||
|
pub blanker_window: x::Window,
|
||||||
|
pub unlock_window: x::Window,
|
||||||
|
pub x: i16,
|
||||||
|
pub y: i16,
|
||||||
|
pub width: u16,
|
||||||
|
pub height: u16,
|
||||||
|
backlight_control: Option<BacklightControl>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Monitor {
|
||||||
|
pub fn set_up_all(conn: &xcb::Connection) -> xcb::Result<Vec<Monitor>> {
|
||||||
|
let mut monitors = Vec::new();
|
||||||
|
for screen in conn.get_setup().roots() {
|
||||||
|
let cookie = conn.send_request(&randr::GetScreenResources {
|
||||||
|
window: screen.root(),
|
||||||
|
});
|
||||||
|
let reply = conn.wait_for_reply(cookie)?;
|
||||||
|
let config_timestamp = reply.config_timestamp();
|
||||||
|
for output in reply.outputs() {
|
||||||
|
let cookie = conn.send_request(&randr::GetOutputInfo {
|
||||||
|
output: *output,
|
||||||
|
config_timestamp,
|
||||||
|
});
|
||||||
|
let output_info = conn.wait_for_reply(cookie)?;
|
||||||
|
if !output_info.crtc().is_none() {
|
||||||
|
let cookie = conn.send_request(&randr::GetCrtcInfo {
|
||||||
|
crtc: output_info.crtc(),
|
||||||
|
config_timestamp,
|
||||||
|
});
|
||||||
|
let reply = conn.wait_for_reply(cookie)?;
|
||||||
|
|
||||||
|
let (blanker_window, unlock_window) = create_windows(conn, &screen, &reply)?;
|
||||||
|
|
||||||
|
let black_gc: x::Gcontext = conn.generate_id();
|
||||||
|
conn.send_and_check_request(&x::CreateGc {
|
||||||
|
cid: black_gc,
|
||||||
|
drawable: x::Drawable::Window(screen.root()),
|
||||||
|
value_list: &[
|
||||||
|
x::Gc::Foreground(screen.black_pixel()),
|
||||||
|
x::Gc::Background(screen.black_pixel()),
|
||||||
|
],
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let backlight_control = find_backlight_control(conn, *output);
|
||||||
|
if let Err(err) = &backlight_control {
|
||||||
|
warn!("Failed to find backlight control: {}", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
monitors.push(Monitor {
|
||||||
|
root: screen.root(),
|
||||||
|
black_gc,
|
||||||
|
output: *output,
|
||||||
|
blanker_window,
|
||||||
|
unlock_window,
|
||||||
|
x: reply.x(),
|
||||||
|
y: reply.y(),
|
||||||
|
width: reply.width(),
|
||||||
|
height: reply.height(),
|
||||||
|
backlight_control: backlight_control.ok().flatten(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(monitors)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn blank(&self, conn: &xcb::Connection) -> anyhow::Result<()> {
|
||||||
|
let mut cookies = Vec::new();
|
||||||
|
cookies.push(conn.send_request_checked(&x::ConfigureWindow {
|
||||||
|
window: self.blanker_window,
|
||||||
|
value_list: &[
|
||||||
|
x::ConfigWindow::StackMode(x::StackMode::Above),
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
cookies.push(conn.send_request_checked(&x::MapWindow {
|
||||||
|
window: self.blanker_window,
|
||||||
|
}));
|
||||||
|
|
||||||
|
for cookie in cookies {
|
||||||
|
conn.check_request(cookie)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.hide_cursor(conn);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unblank(&self, conn: &xcb::Connection) -> anyhow::Result<()> {
|
||||||
|
self.show_cursor(conn);
|
||||||
|
|
||||||
|
conn.send_and_check_request(&x::UnmapWindow {
|
||||||
|
window: self.blanker_window,
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lock(&self, conn: &xcb::Connection) -> anyhow::Result<()> {
|
||||||
|
let mut cookies = Vec::new();
|
||||||
|
|
||||||
|
cookies.push(conn.send_request_checked(&x::ConfigureWindow {
|
||||||
|
window: self.unlock_window,
|
||||||
|
value_list: &[
|
||||||
|
x::ConfigWindow::StackMode(x::StackMode::Above),
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
cookies.push(conn.send_request_checked(&x::MapWindow {
|
||||||
|
window: self.unlock_window,
|
||||||
|
}));
|
||||||
|
|
||||||
|
for cookie in cookies {
|
||||||
|
conn.check_request(cookie)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cookie = conn.send_request(&x::GrabKeyboard {
|
||||||
|
owner_events: true,
|
||||||
|
grab_window: self.unlock_window,
|
||||||
|
time: x::CURRENT_TIME,
|
||||||
|
pointer_mode: x::GrabMode::Async,
|
||||||
|
keyboard_mode: x::GrabMode::Async,
|
||||||
|
});
|
||||||
|
let reply = conn.wait_for_reply(cookie)?;
|
||||||
|
if reply.status() != x::GrabStatus::Success {
|
||||||
|
// FIXME: try to grab later?
|
||||||
|
warn!("Failed to grab keyboard on window {:?}: {:?}", self.blanker_window, reply.status());
|
||||||
|
}
|
||||||
|
|
||||||
|
let cookie = conn.send_request(&x::GrabPointer {
|
||||||
|
owner_events: true,
|
||||||
|
grab_window: self.unlock_window,
|
||||||
|
event_mask: x::EventMask::BUTTON_PRESS | x::EventMask::BUTTON_RELEASE | x::EventMask::POINTER_MOTION | x::EventMask::POINTER_MOTION_HINT,
|
||||||
|
pointer_mode: x::GrabMode::Async,
|
||||||
|
keyboard_mode: x::GrabMode::Async,
|
||||||
|
confine_to: self.blanker_window,
|
||||||
|
cursor: x::CURSOR_NONE,
|
||||||
|
time: x::CURRENT_TIME,
|
||||||
|
});
|
||||||
|
let reply = conn.wait_for_reply(cookie)?;
|
||||||
|
if reply.status() != x::GrabStatus::Success {
|
||||||
|
// FIXME: try to grab later?
|
||||||
|
warn!("Failed to grab pointer on window {:?}: {:?}", self.blanker_window, reply.status());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unlock(&self, conn: &xcb::Connection) -> anyhow::Result<()> {
|
||||||
|
let mut cookies = Vec::new();
|
||||||
|
|
||||||
|
cookies.push(conn.send_request_checked(&x::UngrabKeyboard {
|
||||||
|
time: x::CURRENT_TIME,
|
||||||
|
}));
|
||||||
|
cookies.push(conn.send_request_checked(&x::UngrabPointer {
|
||||||
|
time: x::CURRENT_TIME,
|
||||||
|
}));
|
||||||
|
cookies.push(conn.send_request_checked(&x::UnmapWindow {
|
||||||
|
window: self.unlock_window,
|
||||||
|
}));
|
||||||
|
|
||||||
|
for cookie in cookies {
|
||||||
|
conn.check_request(cookie)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show_cursor(&self, conn: &xcb::Connection) {
|
||||||
|
if let Err(err) = conn.send_and_check_request(&xfixes::ShowCursor {
|
||||||
|
window: self.blanker_window,
|
||||||
|
}) {
|
||||||
|
warn!("Failed to show cursor: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hide_cursor(&self, conn: &xcb::Connection) {
|
||||||
|
if let Err(err) = conn.send_and_check_request(&xfixes::HideCursor {
|
||||||
|
window: self.blanker_window,
|
||||||
|
}) {
|
||||||
|
warn!("Failed to hide cursor: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn brightness_up(&self, conn: &xcb::Connection) -> anyhow::Result<()> {
|
||||||
|
self.brightness_change(conn, |backlight_control, cur_brightness| cur_brightness as i32 + backlight_control.step as i32)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn brightness_down(&self, conn: &xcb::Connection) -> anyhow::Result<()> {
|
||||||
|
self.brightness_change(conn, |backlight_control, cur_brightness| cur_brightness as i32 - backlight_control.step as i32)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn brightness_change<F: FnOnce(&BacklightControl, u32) -> i32>(&self, conn: &xcb::Connection, updater: F) -> anyhow::Result<()> {
|
||||||
|
if let Some(backlight_control) = self.backlight_control {
|
||||||
|
if let Ok(Some(cur_brightness)) = self.get_current_brightness(conn, &backlight_control) {
|
||||||
|
let new_level = updater(&backlight_control, cur_brightness);
|
||||||
|
let new_level = cmp::min(backlight_control.max_level, new_level);
|
||||||
|
let new_level = cmp::max(backlight_control.min_level, new_level);
|
||||||
|
if new_level != cur_brightness as i32 {
|
||||||
|
self.set_brightness(conn, &backlight_control, new_level)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_current_brightness(&self, conn: &xcb::Connection, backlight_control: &BacklightControl) -> anyhow::Result<Option<u32>> {
|
||||||
|
let cookie = conn.send_request(&randr::GetOutputProperty {
|
||||||
|
output: self.output,
|
||||||
|
property: backlight_control.property,
|
||||||
|
r#type: x::ATOM_INTEGER,
|
||||||
|
long_offset: 0,
|
||||||
|
long_length: 4,
|
||||||
|
delete: false,
|
||||||
|
pending: false,
|
||||||
|
});
|
||||||
|
let reply = conn.wait_for_reply(cookie)?;
|
||||||
|
let data = reply.data::<u32>();
|
||||||
|
if data.len() == 1 {
|
||||||
|
Ok(Some(data[0]))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_brightness(&self, conn: &xcb::Connection, backlight_control: &BacklightControl, level: i32) -> anyhow::Result<()> {
|
||||||
|
conn.send_and_check_request(&randr::ChangeOutputProperty {
|
||||||
|
output: self.output,
|
||||||
|
property: backlight_control.property,
|
||||||
|
r#type: x::ATOM_INTEGER,
|
||||||
|
mode: x::PropMode::Replace,
|
||||||
|
data: &[level as u32],
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_windows(conn: &xcb::Connection, screen: &x::Screen, crtc_info: &randr::GetCrtcInfoReply) -> xcb::Result<(x::Window, x::Window)> {
|
||||||
|
let blanker_window: x::Window = conn.generate_id();
|
||||||
|
let unlock_window: x::Window = conn.generate_id();
|
||||||
|
|
||||||
|
let mut cookies = Vec::new();
|
||||||
|
|
||||||
|
debug!("creating blanker window 0x{:x}, {}x{}+{}+{}; unlock window 0x{:x}", blanker_window.resource_id(), crtc_info.width(), crtc_info.height(), crtc_info.x(), crtc_info.y(), unlock_window.resource_id());
|
||||||
|
cookies.push(conn.send_request_checked(&x::CreateWindow {
|
||||||
|
depth: x::COPY_FROM_PARENT as u8,
|
||||||
|
wid: blanker_window,
|
||||||
|
parent: screen.root(),
|
||||||
|
x: crtc_info.x(),
|
||||||
|
y: crtc_info.y(),
|
||||||
|
width: crtc_info.width(),
|
||||||
|
height: crtc_info.height(),
|
||||||
|
border_width: 0,
|
||||||
|
class: x::WindowClass::InputOutput,
|
||||||
|
visual: x::COPY_FROM_PARENT,
|
||||||
|
value_list: &[
|
||||||
|
x::Cw::BackPixel(screen.black_pixel()),
|
||||||
|
x::Cw::BorderPixel(screen.black_pixel()),
|
||||||
|
x::Cw::OverrideRedirect(true),
|
||||||
|
x::Cw::SaveUnder(true),
|
||||||
|
x::Cw::EventMask(x::EventMask::KEY_PRESS | x::EventMask::KEY_RELEASE | x::EventMask::BUTTON_PRESS | x::EventMask::BUTTON_RELEASE | x::EventMask::POINTER_MOTION | x::EventMask::POINTER_MOTION_HINT | x::EventMask::EXPOSURE),
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
cookies.push(conn.send_request_checked(&x::ChangeProperty {
|
||||||
|
mode: x::PropMode::Replace,
|
||||||
|
window: blanker_window,
|
||||||
|
property: x::ATOM_WM_NAME,
|
||||||
|
r#type: x::ATOM_STRING,
|
||||||
|
data: b"bscreensaver blanker window",
|
||||||
|
}));
|
||||||
|
cookies.push(conn.send_request_checked(&x::ChangeProperty {
|
||||||
|
mode: x::PropMode::Replace,
|
||||||
|
window: blanker_window,
|
||||||
|
property: x::ATOM_WM_CLASS,
|
||||||
|
r#type: x::ATOM_STRING,
|
||||||
|
data: BSCREENSAVER_WM_CLASS,
|
||||||
|
}));
|
||||||
|
|
||||||
|
cookies.push(conn.send_request_checked(&x::CreateWindow {
|
||||||
|
depth: x::COPY_FROM_PARENT as u8,
|
||||||
|
wid: unlock_window,
|
||||||
|
parent: blanker_window,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
border_width: 0,
|
||||||
|
class: x::WindowClass::InputOutput,
|
||||||
|
visual: x::COPY_FROM_PARENT,
|
||||||
|
value_list: &[
|
||||||
|
x::Cw::BackPixel(screen.black_pixel()),
|
||||||
|
x::Cw::BorderPixel(screen.black_pixel()),
|
||||||
|
x::Cw::OverrideRedirect(true),
|
||||||
|
x::Cw::SaveUnder(true),
|
||||||
|
x::Cw::EventMask(x::EventMask::KEY_PRESS | x::EventMask::KEY_RELEASE | x::EventMask::BUTTON_PRESS | x::EventMask::BUTTON_RELEASE | x::EventMask::POINTER_MOTION | x::EventMask::POINTER_MOTION_HINT | x::EventMask::STRUCTURE_NOTIFY | x::EventMask::EXPOSURE),
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
cookies.push(conn.send_request_checked(&x::ChangeProperty {
|
||||||
|
mode: x::PropMode::Replace,
|
||||||
|
window: unlock_window,
|
||||||
|
property: x::ATOM_WM_NAME,
|
||||||
|
r#type: x::ATOM_STRING,
|
||||||
|
data: b"bscreensaver unlock dialog socket window",
|
||||||
|
}));
|
||||||
|
cookies.push(conn.send_request_checked(&x::ChangeProperty {
|
||||||
|
mode: x::PropMode::Replace,
|
||||||
|
window: unlock_window,
|
||||||
|
property: x::ATOM_WM_CLASS,
|
||||||
|
r#type: x::ATOM_STRING,
|
||||||
|
data: BSCREENSAVER_WM_CLASS,
|
||||||
|
}));
|
||||||
|
|
||||||
|
for cookie in cookies {
|
||||||
|
conn.check_request(cookie)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((blanker_window, unlock_window))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_backlight_control(conn: &xcb::Connection, output: randr::Output) -> xcb::Result<Option<BacklightControl>> {
|
||||||
|
for prop_name in [BACKLIGHT_ATOM_NAME, BACKLIGHT_FALLBACK_ATOM_NAME] {
|
||||||
|
let property = create_atom(conn, prop_name)?;
|
||||||
|
let cookie = conn.send_request(&randr::QueryOutputProperty {
|
||||||
|
output,
|
||||||
|
property,
|
||||||
|
});
|
||||||
|
let reply = conn.wait_for_reply(cookie)?;
|
||||||
|
let values = reply.valid_values();
|
||||||
|
if reply.range() && values.len() == 2 {
|
||||||
|
let min_level = values[0];
|
||||||
|
let max_level = values[1];
|
||||||
|
let range = max_level - min_level;
|
||||||
|
if range > 0 {
|
||||||
|
return Ok(Some(BacklightControl {
|
||||||
|
property,
|
||||||
|
min_level,
|
||||||
|
max_level,
|
||||||
|
step: cmp::min(range as u32, cmp::max(10, (max_level - min_level) / 10) as u32),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
@ -11,6 +11,7 @@ struct Widgets {
|
|||||||
blank_before_locking: gtk::SpinButton,
|
blank_before_locking: gtk::SpinButton,
|
||||||
new_login_command_combo: gtk::ComboBoxText,
|
new_login_command_combo: gtk::ComboBoxText,
|
||||||
custom_new_login_command_entry: gtk::Entry,
|
custom_new_login_command_entry: gtk::Entry,
|
||||||
|
handle_brightness_keys_checkbox: gtk::CheckButton,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
@ -190,11 +191,31 @@ fn show_ui(app: >k::Application, config: &Configuration) {
|
|||||||
custom_new_login_command_hbox.set_sensitive(sensitive);
|
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 {
|
let widgets = Widgets {
|
||||||
lock_timeout: lock_timeout_spinbutton.clone(),
|
lock_timeout: lock_timeout_spinbutton.clone(),
|
||||||
blank_before_locking: blank_before_locking_spinbutton.clone(),
|
blank_before_locking: blank_before_locking_spinbutton.clone(),
|
||||||
new_login_command_combo: new_login_command_combo.clone(),
|
new_login_command_combo: new_login_command_combo.clone(),
|
||||||
custom_new_login_command_entry: custom_new_login_command_entry.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 |_,_| {
|
mainwin.connect_delete_event(clone!(@strong config, @strong widgets, @strong app, @strong mainwin => move |_,_| {
|
||||||
Inhibit(!confirm_cancel(&config, &widgets, &mainwin))
|
Inhibit(!confirm_cancel(&config, &widgets, &mainwin))
|
||||||
@ -315,6 +336,8 @@ fn build_new_configuration(old_config: &Configuration, widgets: &Widgets) -> (Co
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
new_config.handle_brightness_keys = widgets.handle_brightness_keys_checkbox.is_active();
|
||||||
|
|
||||||
let changed = old_config != &new_config;
|
let changed = old_config != &new_config;
|
||||||
(new_config, changed)
|
(new_config, changed)
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ const LOCK_TIMEOUT: &str = "lock-timeout";
|
|||||||
const BLANK_BEFORE_LOCKING: &str = "blank-before-locking";
|
const BLANK_BEFORE_LOCKING: &str = "blank-before-locking";
|
||||||
const DIALOG_BACKEND: &str = "dialog-backend";
|
const DIALOG_BACKEND: &str = "dialog-backend";
|
||||||
const NEW_LOGIN_COMMAND: &str = "new-login-command";
|
const NEW_LOGIN_COMMAND: &str = "new-login-command";
|
||||||
|
const HANDLE_BRIGHTNESS_KEYS: &str = "handle-brightness-keys";
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum DialogBackend {
|
pub enum DialogBackend {
|
||||||
@ -54,6 +55,7 @@ pub struct Configuration {
|
|||||||
pub blank_before_locking: Duration,
|
pub blank_before_locking: Duration,
|
||||||
pub dialog_backend: DialogBackend,
|
pub dialog_backend: DialogBackend,
|
||||||
pub new_login_command: NewLoginCommand,
|
pub new_login_command: NewLoginCommand,
|
||||||
|
pub handle_brightness_keys: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Configuration {
|
impl Configuration {
|
||||||
@ -84,6 +86,10 @@ impl Configuration {
|
|||||||
None => config.new_login_command,
|
None => config.new_login_command,
|
||||||
Some(val) => val.try_into().unwrap_or(NewLoginCommand::Auto),
|
Some(val) => val.try_into().unwrap_or(NewLoginCommand::Auto),
|
||||||
};
|
};
|
||||||
|
config.handle_brightness_keys = match config_toml.get(HANDLE_BRIGHTNESS_KEYS) {
|
||||||
|
None => config.handle_brightness_keys,
|
||||||
|
Some(val) => val.as_bool().ok_or(anyhow!("'handle-brightness-keys' must be a boolean"))?,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.blank_before_locking >= config.lock_timeout {
|
if config.blank_before_locking >= config.lock_timeout {
|
||||||
@ -102,6 +108,7 @@ impl Configuration {
|
|||||||
config_map.insert(BLANK_BEFORE_LOCKING.to_string(), Value::String(format_duration(self.blank_before_locking).to_string()));
|
config_map.insert(BLANK_BEFORE_LOCKING.to_string(), Value::String(format_duration(self.blank_before_locking).to_string()));
|
||||||
config_map.insert(DIALOG_BACKEND.to_string(), Value::String(self.dialog_backend.to_string()));
|
config_map.insert(DIALOG_BACKEND.to_string(), Value::String(self.dialog_backend.to_string()));
|
||||||
config_map.insert(NEW_LOGIN_COMMAND.to_string(), self.new_login_command.clone().try_into()?);
|
config_map.insert(NEW_LOGIN_COMMAND.to_string(), self.new_login_command.clone().try_into()?);
|
||||||
|
config_map.insert(HANDLE_BRIGHTNESS_KEYS.to_string(), Value::Boolean(self.handle_brightness_keys));
|
||||||
|
|
||||||
let config_path = xdg::BaseDirectories::new()?.place_config_file(CONFIG_FILE_RELATIVE_PATH)?;
|
let config_path = xdg::BaseDirectories::new()?.place_config_file(CONFIG_FILE_RELATIVE_PATH)?;
|
||||||
let mut tmp_filename = config_path.file_name().unwrap().to_os_string();
|
let mut tmp_filename = config_path.file_name().unwrap().to_os_string();
|
||||||
@ -123,6 +130,7 @@ impl Default for Configuration {
|
|||||||
blank_before_locking: Duration::ZERO,
|
blank_before_locking: Duration::ZERO,
|
||||||
dialog_backend: DialogBackend::Gtk3,
|
dialog_backend: DialogBackend::Gtk3,
|
||||||
new_login_command: NewLoginCommand::Auto,
|
new_login_command: NewLoginCommand::Auto,
|
||||||
|
handle_brightness_keys: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user