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:
Brian Tarricone 2022-05-23 20:27:59 -07:00
parent 6e5dfabcfd
commit 8eb8dfac2e
6 changed files with 459 additions and 263 deletions

View File

@ -2,8 +2,6 @@
* Fully audit the locker source to ensure that it cannot crash based on
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
`xscreensaver` parlance) that draw interesting things on
`bscreensaver`'s blanker windows. Without this, `bscreensaver` is

View File

@ -18,3 +18,6 @@ dialog-backend = "gtk3"
# Adds a 'New Login' button to the unlock dialog that will run the
# specified command when clicked
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

View File

@ -1,3 +1,4 @@
mod monitor;
mod pidfd;
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_util::{*, settings::Configuration};
use monitor::*;
use pidfd::{CreatePidFd, PidFd};
const BLANKED_ARG: &str = "blanked";
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)]
enum BlankerState {
Idle = 0,
@ -135,7 +125,7 @@ fn main() -> anyhow::Result<()> {
let mut state = State {
config,
monitors: create_blanker_windows(&conn)?,
monitors: Monitor::set_up_all(&conn)?,
dbus_service: None,
systemd_service: None,
last_user_activity: Instant::now(),
@ -322,131 +312,6 @@ fn init_randr(conn: &xcb::Connection) -> anyhow::Result<()> {
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)> {
let child = Command::new(format!("{}/{}", env!("HELPER_DIR"), binary_name))
.spawn()?;
@ -519,6 +384,13 @@ fn handle_xcb_events<'a>(conn: &'a xcb::Connection, state: &mut State<'a>, comma
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 {
match event {
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_gc(&conn, monitor.black_gc)?;
}
state.monitors = create_blanker_windows(&conn)?;
state.monitors = Monitor::set_up_all(conn)?;
match state.blanker_state {
BlankerState::Idle => (),
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 {
BlankerState::Idle => (),
BlankerState::Blanked => unblank_screen(conn, state)?,
ev @ xcb::Event::X(x::Event::MotionNotify(_)) => handle_user_activity(conn, state, ev)?,
xcb::Event::X(x::Event::KeyPress(ev)) => {
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 {
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) => {
error!("Unable to start unlock dialog: {}", err);
None
},
Ok(unlock_dialog) => Some(unlock_dialog),
};
Ok(())
},
Some(unlock_dialog) => {
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 {
conn.check_request(cookie)?;
}
},
}
},
ev => trace!("Got other event: {:#?}", ev),
}
}
} else {
break;
}
}
Ok(())
},
}
}
}
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()
});
show_cursor(conn, state);
for monitor in &state.monitors {
monitor.show_cursor(conn);
}
let trigger_event = match trigger_event {
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) {
for monitor in &state.monitors {
if let Err(err) = conn.send_and_check_request(&xfixes::HideCursor {
window: monitor.blanker_window,
}) {
warn!("Failed to hide cursor: {}", err);
}
monitor.hide_cursor(conn);
}
}
@ -822,24 +702,8 @@ fn blank_screen(conn: &xcb::Connection, state: &mut State) -> anyhow::Result<()>
info!("Blanking");
for monitor in &state.monitors {
let mut cookies = Vec::new();
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)?;
monitor.blank(conn)?;
}
}
hide_cursor(conn, state);
state.blanker_state = BlankerState::Blanked;
@ -853,16 +717,8 @@ fn unblank_screen(conn: &xcb::Connection, state: &mut State) -> anyhow::Result<(
info!("Unblanking");
show_cursor(conn, state);
let mut cookies = Vec::new();
for monitor in &state.monitors {
cookies.push(conn.send_request_checked(&x::UnmapWindow {
window: monitor.blanker_window,
}));
}
for cookie in cookies {
conn.check_request(cookie)?;
monitor.unblank(conn)?;
}
state.blanker_state = BlankerState::Idle;
@ -879,50 +735,7 @@ fn lock_screen(conn: &xcb::Connection, state: &mut State) -> anyhow::Result<()>
info!("Locking");
for monitor in &state.monitors {
let mut cookies = Vec::new();
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());
}
monitor.lock(conn)?;
}
state.blanker_state = BlankerState::Locked;
@ -937,20 +750,8 @@ fn unlock_screen(conn: &xcb::Connection, state: &mut State) -> anyhow::Result<()
info!("Unlocking");
let mut cookies = Vec::new();
for monitor in &state.monitors {
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: monitor.unlock_window,
}));
}
for cookie in cookies {
conn.check_request(cookie)?;
monitor.unlock(conn)?;
}
state.blanker_state = BlankerState::Blanked;

363
locker/src/monitor.rs Normal file
View 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)
}

View File

@ -11,6 +11,7 @@ struct Widgets {
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<()> {
@ -190,11 +191,31 @@ fn show_ui(app: &gtk::Application, config: &Configuration) {
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 |_,_| {
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;
(new_config, changed)
}

View File

@ -10,6 +10,7 @@ const LOCK_TIMEOUT: &str = "lock-timeout";
const BLANK_BEFORE_LOCKING: &str = "blank-before-locking";
const DIALOG_BACKEND: &str = "dialog-backend";
const NEW_LOGIN_COMMAND: &str = "new-login-command";
const HANDLE_BRIGHTNESS_KEYS: &str = "handle-brightness-keys";
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DialogBackend {
@ -54,6 +55,7 @@ pub struct Configuration {
pub blank_before_locking: Duration,
pub dialog_backend: DialogBackend,
pub new_login_command: NewLoginCommand,
pub handle_brightness_keys: bool,
}
impl Configuration {
@ -84,6 +86,10 @@ impl Configuration {
None => config.new_login_command,
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 {
@ -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(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(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 mut tmp_filename = config_path.file_name().unwrap().to_os_string();
@ -123,6 +130,7 @@ impl Default for Configuration {
blank_before_locking: Duration::ZERO,
dialog_backend: DialogBackend::Gtk3,
new_login_command: NewLoginCommand::Auto,
handle_brightness_keys: false,
}
}
}