mirror of
https://github.com/morgan9e/adw-askpass
synced 2026-04-14 00:04: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