mirror of
https://github.com/morgan9e/adw-askpass
synced 2026-04-13 15:54:04 +09:00
Initial commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
target/
|
||||
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "adw-askpass"
|
||||
version = "0.0.1"
|
||||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
name = "adw-askpass"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
gtk4 = "0.11"
|
||||
libadwaita = { version = "0.9", features = ["v1_2"] }
|
||||
138
src/main.rs
Normal file
138
src/main.rs
Normal file
@@ -0,0 +1,138 @@
|
||||
mod notification;
|
||||
mod password;
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::env;
|
||||
use std::process::ExitCode;
|
||||
use std::rc::Rc;
|
||||
|
||||
use gtk4::prelude::*;
|
||||
use libadwaita as adw;
|
||||
|
||||
enum Mode {
|
||||
Password,
|
||||
Notification,
|
||||
}
|
||||
|
||||
pub struct Args {
|
||||
mode: Mode,
|
||||
title: String,
|
||||
message: String,
|
||||
icon: String,
|
||||
ok_label: String,
|
||||
cancel_label: String,
|
||||
timeout: u32,
|
||||
}
|
||||
|
||||
impl Default for Args {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mode: Mode::Password,
|
||||
title: "Authentication Required".into(),
|
||||
message: "Enter your password to continue".into(),
|
||||
icon: "dialog-password-symbolic".into(),
|
||||
ok_label: "Unlock".into(),
|
||||
cancel_label: "Cancel".into(),
|
||||
timeout: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Args {
|
||||
fn parse() -> Self {
|
||||
let mut args = Args::default();
|
||||
let raw: Vec<String> = env::args().skip(1).collect();
|
||||
let mut i = 0;
|
||||
|
||||
while i < raw.len() {
|
||||
let a = &raw[i];
|
||||
|
||||
if let Some((key, val)) = a.split_once('=') {
|
||||
match key {
|
||||
"--title" => args.title = val.to_string(),
|
||||
"--text" | "--message" => args.message = val.to_string(),
|
||||
"--icon" | "--window-icon" => args.icon = val.to_string(),
|
||||
"--ok-label" => args.ok_label = val.to_string(),
|
||||
"--cancel-label" => args.cancel_label = val.to_string(),
|
||||
"--timeout" => args.timeout = val.parse().unwrap_or(0),
|
||||
_ => {}
|
||||
}
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
match a.as_str() {
|
||||
"--password" | "--modal" => {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
"--notification" => {
|
||||
args.mode = Mode::Notification;
|
||||
if args.icon == "dialog-password-symbolic" {
|
||||
args.icon = "dialog-information-symbolic".into();
|
||||
}
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let next = raw.get(i + 1).map(|s| s.as_str());
|
||||
let consumed = match a.as_str() {
|
||||
"--title" => {
|
||||
if let Some(v) = next { args.title = v.to_string(); true } else { false }
|
||||
}
|
||||
"--text" | "--message" => {
|
||||
if let Some(v) = next { args.message = v.to_string(); true } else { false }
|
||||
}
|
||||
"--icon" | "--window-icon" => {
|
||||
if let Some(v) = next { args.icon = v.to_string(); true } else { false }
|
||||
}
|
||||
"--ok-label" => {
|
||||
if let Some(v) = next { args.ok_label = v.to_string(); true } else { false }
|
||||
}
|
||||
"--cancel-label" => {
|
||||
if let Some(v) = next { args.cancel_label = v.to_string(); true } else { false }
|
||||
}
|
||||
"--timeout" => {
|
||||
if let Some(v) = next { args.timeout = v.parse().unwrap_or(0); true } else { false }
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if !consumed && !a.starts_with('-') {
|
||||
args.message = a.to_string();
|
||||
}
|
||||
|
||||
i += if consumed { 2 } else { 1 };
|
||||
}
|
||||
|
||||
args
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let args = Args::parse();
|
||||
let submitted = Rc::new(Cell::new(false));
|
||||
|
||||
let app = adw::Application::new(None, gtk4::gio::ApplicationFlags::default());
|
||||
|
||||
match args.mode {
|
||||
Mode::Notification => {
|
||||
notification::send(&args);
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
Mode::Password => {
|
||||
let submitted_clone = submitted.clone();
|
||||
app.connect_activate(move |app| {
|
||||
password::build(app, &args, &submitted_clone);
|
||||
});
|
||||
app.run_with_args::<&str>(&[]);
|
||||
if submitted.get() {
|
||||
ExitCode::SUCCESS
|
||||
} else {
|
||||
ExitCode::from(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/notification.rs
Normal file
13
src/notification.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use std::process::Command;
|
||||
|
||||
use crate::Args;
|
||||
|
||||
pub fn send(args: &Args) {
|
||||
let _ = Command::new("notify-send")
|
||||
.arg("--icon")
|
||||
.arg(&args.icon)
|
||||
.arg("--app-name")
|
||||
.arg(&args.title)
|
||||
.arg(&args.message)
|
||||
.spawn();
|
||||
}
|
||||
128
src/password.rs
Normal file
128
src/password.rs
Normal file
@@ -0,0 +1,128 @@
|
||||
use std::cell::Cell;
|
||||
use std::io::Write;
|
||||
use std::rc::Rc;
|
||||
|
||||
use gtk4 as gtk;
|
||||
use gtk4::glib;
|
||||
use gtk4::prelude::*;
|
||||
use libadwaita as adw;
|
||||
use libadwaita::prelude::*;
|
||||
|
||||
use crate::Args;
|
||||
|
||||
pub fn build(app: &adw::Application, args: &Args, submitted: &Rc<Cell<bool>>) {
|
||||
let window = adw::ApplicationWindow::builder()
|
||||
.application(app)
|
||||
.default_width(340)
|
||||
.resizable(false)
|
||||
.build();
|
||||
|
||||
// Close on Escape
|
||||
let esc = gtk::ShortcutController::new();
|
||||
let win_ref = window.clone();
|
||||
esc.add_shortcut(gtk::Shortcut::new(
|
||||
gtk::ShortcutTrigger::parse_string("Escape"),
|
||||
Some(gtk::CallbackAction::new(move |_, _| {
|
||||
win_ref.close();
|
||||
glib::Propagation::Stop
|
||||
})),
|
||||
));
|
||||
window.add_controller(esc);
|
||||
|
||||
let vbox = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Vertical)
|
||||
.spacing(14)
|
||||
.margin_top(32)
|
||||
.margin_bottom(24)
|
||||
.margin_start(28)
|
||||
.margin_end(28)
|
||||
.build();
|
||||
window.set_content(Some(&vbox));
|
||||
|
||||
let icon = gtk::Image::builder()
|
||||
.icon_name(&args.icon)
|
||||
.pixel_size(48)
|
||||
.margin_bottom(4)
|
||||
.css_classes(["dim-label"])
|
||||
.build();
|
||||
vbox.append(&icon);
|
||||
|
||||
let title_label = gtk::Label::builder()
|
||||
.label(&args.title)
|
||||
.css_classes(["title-4"])
|
||||
.build();
|
||||
vbox.append(&title_label);
|
||||
|
||||
let msg_label = gtk::Label::builder()
|
||||
.label(&args.message)
|
||||
.css_classes(["dim-label", "body"])
|
||||
.wrap(true)
|
||||
.margin_bottom(4)
|
||||
.justify(gtk::Justification::Center)
|
||||
.build();
|
||||
vbox.append(&msg_label);
|
||||
|
||||
let list_box = gtk::ListBox::builder()
|
||||
.selection_mode(gtk::SelectionMode::None)
|
||||
.css_classes(["boxed-list"])
|
||||
.margin_bottom(4)
|
||||
.build();
|
||||
vbox.append(&list_box);
|
||||
|
||||
let entry = adw::PasswordEntryRow::builder().title("Password").build();
|
||||
list_box.append(&entry);
|
||||
|
||||
let btn_box = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Horizontal)
|
||||
.spacing(12)
|
||||
.homogeneous(true)
|
||||
.build();
|
||||
vbox.append(&btn_box);
|
||||
|
||||
let cancel_btn = gtk::Button::builder()
|
||||
.label(&args.cancel_label)
|
||||
.css_classes(["pill"])
|
||||
.build();
|
||||
let win_ref = window.clone();
|
||||
cancel_btn.connect_clicked(move |_| win_ref.close());
|
||||
btn_box.append(&cancel_btn);
|
||||
|
||||
let ok_btn = gtk::Button::builder()
|
||||
.label(&args.ok_label)
|
||||
.css_classes(["pill", "suggested-action"])
|
||||
.build();
|
||||
btn_box.append(&ok_btn);
|
||||
|
||||
let submit = {
|
||||
let entry = entry.clone();
|
||||
let window = window.clone();
|
||||
let submitted = submitted.clone();
|
||||
move || {
|
||||
let pw: String = entry.text().into();
|
||||
if !pw.is_empty() {
|
||||
let _ = std::io::stdout().write_all(pw.as_bytes());
|
||||
let _ = std::io::stdout().flush();
|
||||
submitted.set(true);
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let submit_clone = submit.clone();
|
||||
ok_btn.connect_clicked(move |_| submit_clone());
|
||||
entry.connect_entry_activated(move |_| submit());
|
||||
|
||||
if args.timeout > 0 {
|
||||
let win_ref = window.clone();
|
||||
glib::timeout_add_seconds_local_once(args.timeout, move || {
|
||||
win_ref.close();
|
||||
});
|
||||
}
|
||||
|
||||
window.present();
|
||||
|
||||
let entry_ref = entry.clone();
|
||||
glib::idle_add_local_once(move || {
|
||||
entry_ref.grab_focus();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user