pub mod dbus;
pub mod virtual_keyboard;
pub mod virtual_pointer;
use crate::common::virtual_keyboard::VirtualKeyboard;
use async_channel::Receiver;
use glib::{g_critical, spawn_future_local, JoinHandle, Object};
use greetd_ipc::codec::SyncCodec;
use greetd_ipc::AuthMessageType::Secret;
use greetd_ipc::{Request, Response};
use gtk::gio::{ListStore, Settings};
use gtk::glib::{clone, timeout_add_once};
use gtk::prelude::*;
use gtk::{Button, Grid, Revealer};
use libhandy::Carousel;
use libphosh::prelude::ShellExt;
use libphosh::prelude::WallClockExt;
use libphosh::WallClock;
use phrog::lockscreen::Lockscreen;
use phrog::session_object::SessionObject;
use phrog::shell::Shell;
use phrog::supervised_child::SupervisedChild;
use std::env::temp_dir;
use std::os::unix::net::UnixListener;
use std::path::PathBuf;
use std::process::Stdio;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use tempfile::TempDir;
pub use virtual_pointer::VirtualPointer;
#[allow(dead_code)]
pub struct Test {
pub session_dbus_conn: zbus::Connection,
system_dbus_conn: zbus::Connection,
pub if_settings: Settings,
pub logged_in: Arc<AtomicBool>,
pub ready_called: Arc<AtomicBool>,
pub ready_rx: Receiver<(VirtualPointer, VirtualKeyboard)>,
recording: Option<SupervisedChild>,
pub shell: Shell,
system_dbus: SupervisedChild,
session_dbus: SupervisedChild,
tmp: TempDir,
wall_clock: WallClock,
}
impl Test {
pub fn start(&mut self, name: &str, jh: JoinHandle<()>) {
if let Ok(base_path) = std::env::var("RECORD_TESTS") {
if let Ok(child) = std::process::Command::new("wf-recorder")
.arg("-f")
.arg(PathBuf::from(base_path).join(format!("{}.mp4", name)))
.stdout(Stdio::null())
.stdin(Stdio::null())
.stderr(Stdio::null())
.spawn()
{
self.recording = Some(SupervisedChild::new("wf-recorder", child));
}
}
let timed_out = Arc::new(AtomicBool::new(false));
timeout_add_once(
Duration::from_secs(60),
clone!(@strong timed_out => move || {
timed_out.store(true, Ordering::SeqCst);
g_critical!("phrog", "Test timed out!");
gtk::main_quit();
}),
);
let failed = Arc::new(AtomicBool::new(false));
spawn_future_local(clone!(@strong failed => async move {
if jh.await.is_err() {
g_critical!("phrog", "Test failed!");
gtk::main_quit();
failed.store(true, Ordering::SeqCst);
}
}));
gtk::main();
assert!(!timed_out.load(Ordering::SeqCst));
assert!(!failed.load(Ordering::SeqCst));
assert!(self.ready_called.load(Ordering::Relaxed));
}
}
#[derive(Default)]
pub struct TestOptions {
pub num_users: Option<u32>,
pub sessions: Option<Vec<SessionObject>>,
pub last_user: Option<String>,
pub last_session: Option<String>,
pub first_run: Option<String>,
pub fake_greetd: Option<bool>,
pub locale_dir: Option<PathBuf>,
pub locale: Option<String>,
pub language: Option<String>,
}
pub fn test_init(options: Option<TestOptions>) -> Test {
std::env::set_var("GSETTINGS_BACKEND", "memory");
let tmp = tempfile::tempdir().unwrap();
let system_dbus = dbus::dbus_daemon("system", tmp.path());
let session_dbus = dbus::dbus_daemon("session", tmp.path());
phrog::init().unwrap();
if let Some(ref options) = options {
let phrog_settings = Settings::new("mobi.phosh.phrog");
phrog_settings
.set_string(
"last-user",
&options.last_user.clone().unwrap_or(String::new()),
)
.unwrap();
phrog_settings
.set_string(
"last-session",
&options.last_session.clone().unwrap_or(String::new()),
)
.unwrap();
phrog_settings
.set_string(
"first-run",
&options.first_run.clone().unwrap_or(String::new()),
)
.unwrap();
}
let if_settings = Settings::new("org.gnome.desktop.interface");
if_settings.set_string("accent-color", "green").unwrap();
let num_users = options.as_ref().and_then(|opts| opts.num_users);
let (system_dbus_conn, session_dbus_conn) = async_global_executor::block_on(async move {
let system = zbus::Connection::system()
.await
.expect("failed to connect to system bus");
dbus::run_accounts_fixture(system.clone(), num_users)
.await
.unwrap();
let session = zbus::Connection::session()
.await
.expect("failed to connect to session bus");
(system, session)
});
let logged_in = Arc::new(AtomicBool::new(false));
fake_greetd(&logged_in);
let locale_dir = options
.as_ref()
.and_then(|opts| opts.locale_dir.as_deref())
.unwrap_or_else(|| std::path::Path::new(phrog::LOCALEDIR))
.display()
.to_string();
let mut shell_builder = Object::builder().property("locale-dir", locale_dir);
let sessions_store = ListStore::new::<SessionObject>();
sessions_store.extend_from_slice(
&options
.as_ref()
.and_then(|opts| opts.sessions.clone())
.unwrap_or(vec![
SessionObject::new("gnome", "GNOME", "", "", ""),
SessionObject::new("phosh", "Phosh", "", "", ""),
]),
);
shell_builder = shell_builder.property("sessions", sessions_store);
if let Some(fake_greetd) = options.as_ref().and_then(|opts| opts.fake_greetd) {
shell_builder = shell_builder.property("fake-greetd", fake_greetd);
}
if let Some(locale) = options.as_ref().and_then(|opts| opts.locale.as_ref()) {
shell_builder = shell_builder.property("locale", locale);
}
if let Some(language) = options.as_ref().and_then(|opts| opts.language.as_ref()) {
shell_builder = shell_builder.property("language", language);
}
let shell: Shell = shell_builder.build();
shell.set_default();
let wall_clock = WallClock::new();
wall_clock.set_default();
let ready_called = Arc::new(AtomicBool::new(false));
let ready_called2 = ready_called.clone();
let (ready_tx, ready_rx) = async_channel::bounded(1);
shell.connect_ready(clone!(@strong ready_called2 => move |shell| {
ready_called2.store(true, Ordering::Relaxed);
let (_, _, width, height) = shell.usable_area();
let vp = VirtualPointer::new(wayland_client::Connection::connect_to_env().unwrap(), width as _, height as _);
let kb = VirtualKeyboard::new(wayland_client::Connection::connect_to_env().unwrap());
ready_tx.send_blocking((vp, kb)).expect("notify ready failed");
}));
Test {
system_dbus_conn,
session_dbus_conn,
if_settings,
logged_in,
ready_called,
ready_rx,
recording: None,
shell,
system_dbus,
session_dbus,
tmp,
wall_clock,
}
}
pub fn fake_greetd(logged_in: &Arc<AtomicBool>) {
let path = temp_dir().join(format!(
".phrog-test-greetd-{}.sock",
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
));
std::env::set_var("GREETD_SOCK", &path);
std::thread::spawn(clone!(@strong logged_in => move || {
let listener = UnixListener::bind(&path).unwrap();
loop {
let (mut stream, _addr) = listener
.accept()
.expect("failed to accept greetd connection");
match Request::read_from(&mut stream).unwrap() {
Request::CreateSession { .. } => Response::AuthMessage {
auth_message_type: Secret,
auth_message: "Password:".to_string(),
}
.write_to(&mut stream)
.unwrap(),
req => panic!("wrong request: {:?}", req),
}
match Request::read_from(&mut stream).unwrap() {
Request::PostAuthMessageResponse {
response: Some(password),
} => {
assert_eq!(password, "0451");
Response::Success.write_to(&mut stream).unwrap();
}
req => panic!("wrong request: {:?}", req),
}
match Request::read_from(&mut stream).unwrap() {
Request::StartSession { .. } => {
Response::Success.write_to(&mut stream).unwrap();
logged_in.store(true, Ordering::Relaxed);
}
req => panic!("wrong request: {:?}", req),
}
}
}));
}
pub fn get_lockscreen_bits(lockscreen: &mut Lockscreen) -> (Grid, Button) {
let carousel = lockscreen.child().unwrap().downcast::<Carousel>().unwrap();
let keypad_page = carousel
.children()
.get(2)
.unwrap()
.clone()
.downcast::<gtk::Box>()
.unwrap();
let keypad_revealer = keypad_page
.children()
.get(2)
.unwrap()
.clone()
.downcast::<Revealer>()
.unwrap();
let keypad = keypad_revealer.child().unwrap().downcast::<Grid>().unwrap();
let submit_box = keypad_page
.children()
.get(3)
.unwrap()
.clone()
.downcast::<gtk::Box>()
.unwrap();
let submit_btn = submit_box
.children()
.first()
.unwrap()
.clone()
.downcast::<Button>()
.unwrap();
(keypad, submit_btn)
}
pub fn keypad_digit(grid: >k::Grid, digit: i32) -> gtk::Widget {
if digit == 0 {
return grid.child_at(1, 3).unwrap();
}
let digit = digit - 1;
grid.child_at(digit % 3, digit / 3).unwrap()
}
pub fn fade_quit() {
Shell::default().fade_out(0);
timeout_add_once(Duration::from_millis(500), || {
gtk::main_quit();
});
}