Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ef9674fde | |||
| 9e44bd4b32 | |||
| 5ba2cea3d8 |
Generated
+3441
-23
File diff suppressed because it is too large
Load Diff
@@ -123,7 +123,14 @@ fn main() -> ExitCode {
|
|||||||
if let Ok(backend) = blocker_platform_linux::kwin::KwinBackend::connect() {
|
if let Ok(backend) = blocker_platform_linux::kwin::KwinBackend::connect() {
|
||||||
println!("backend: kwin — native Wayland windows are visible");
|
println!("backend: kwin — native Wayland windows are visible");
|
||||||
let _keep_connection = watcher_conn;
|
let _keep_connection = watcher_conn;
|
||||||
run_loop(&rx, &schedules, &usage, &context, blocker_platform_linux::Enforcement::Freeze);
|
run_loop(
|
||||||
|
&rx,
|
||||||
|
&schedules,
|
||||||
|
&usage,
|
||||||
|
&context,
|
||||||
|
blocker_platform_linux::Enforcement::Freeze,
|
||||||
|
&path,
|
||||||
|
);
|
||||||
backend.shutdown();
|
backend.shutdown();
|
||||||
return ExitCode::SUCCESS;
|
return ExitCode::SUCCESS;
|
||||||
}
|
}
|
||||||
@@ -188,25 +195,32 @@ fn main() -> ExitCode {
|
|||||||
/// charge, and this consumes it. Two blocked threads and no CPU between events.
|
/// charge, and this consumes it. Two blocked threads and no CPU between events.
|
||||||
fn run_loop(
|
fn run_loop(
|
||||||
rx: &std::sync::mpsc::Receiver<blocker_platform_linux::FocusEvent>,
|
rx: &std::sync::mpsc::Receiver<blocker_platform_linux::FocusEvent>,
|
||||||
schedules: &[blocker_core::model::Schedule],
|
schedules_initial: &[blocker_core::model::Schedule],
|
||||||
usage: &blocker_core::engine::UsageSnapshot,
|
usage: &blocker_core::engine::UsageSnapshot,
|
||||||
context: &blocker_core::engine::PlatformContext,
|
context: &blocker_core::engine::PlatformContext,
|
||||||
enforcement: blocker_platform_linux::Enforcement,
|
enforcement: blocker_platform_linux::Enforcement,
|
||||||
|
db_path: &std::path::Path,
|
||||||
) {
|
) {
|
||||||
let ctx = runner::LoopContext {
|
let mut current_schedules = schedules_initial.to_vec();
|
||||||
schedules,
|
|
||||||
usage,
|
|
||||||
platform_context: context,
|
|
||||||
boot_id: boot::current_boot_id(),
|
|
||||||
enforcement,
|
|
||||||
};
|
|
||||||
let clock = blocker_platform_linux::SystemMonotonic;
|
let clock = blocker_platform_linux::SystemMonotonic;
|
||||||
println!("enforcement: {enforcement:?}; entering the event loop");
|
println!("enforcement: {enforcement:?}; entering the event loop");
|
||||||
'events: loop {
|
'events: loop {
|
||||||
|
let ctx = runner::LoopContext {
|
||||||
|
schedules: ¤t_schedules,
|
||||||
|
usage,
|
||||||
|
platform_context: context,
|
||||||
|
boot_id: boot::current_boot_id(),
|
||||||
|
enforcement,
|
||||||
|
};
|
||||||
let (action, blocked, _) = runner::step(rx, &ctx, &clock, time::now());
|
let (action, blocked, _) = runner::step(rx, &ctx, &clock, time::now());
|
||||||
if blocked > 0 {
|
if blocked > 0 {
|
||||||
println!("blocked an app ({action:?})");
|
println!("blocked an app ({action:?})");
|
||||||
}
|
}
|
||||||
|
if let Ok(store) = blocker_storage::Store::open(db_path) {
|
||||||
|
if let Ok(reloaded) = store.load_schedules() {
|
||||||
|
current_schedules = reloaded;
|
||||||
|
}
|
||||||
|
}
|
||||||
if action == runner::Action::Idle {
|
if action == runner::Action::Idle {
|
||||||
break 'events;
|
break 'events;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,11 +108,13 @@ pub fn step(
|
|||||||
let action = match &event {
|
let action = match &event {
|
||||||
FocusEvent::Activated(_) => Action::Focused,
|
FocusEvent::Activated(_) => Action::Focused,
|
||||||
FocusEvent::Cleared => Action::Unfocused,
|
FocusEvent::Cleared => Action::Unfocused,
|
||||||
|
FocusEvent::ReloadSchedules => Action::Idle,
|
||||||
};
|
};
|
||||||
|
|
||||||
let blocked = match &event {
|
let blocked = match &event {
|
||||||
FocusEvent::Activated(window) => enforce_if_blocked(window, ctx, clock, wall),
|
FocusEvent::Activated(window) => enforce_if_blocked(window, ctx, clock, wall),
|
||||||
FocusEvent::Cleared => 0,
|
FocusEvent::Cleared => 0,
|
||||||
|
FocusEvent::ReloadSchedules => 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
(action, blocked, wait)
|
(action, blocked, wait)
|
||||||
@@ -165,6 +167,14 @@ fn enforce_if_blocked(
|
|||||||
&format!("Blocker: {target_name} blocked"),
|
&format!("Blocker: {target_name} blocked"),
|
||||||
"Focus blocked by active schedule",
|
"Focus blocked by active schedule",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Launch the calm block screen GUI in the background if available
|
||||||
|
let ui_exe = std::env::current_exe()
|
||||||
|
.ok()
|
||||||
|
.and_then(|p| p.parent().map(|d| d.join("blocker-ui")))
|
||||||
|
.unwrap_or_else(|| std::path::PathBuf::from("blocker-ui"));
|
||||||
|
let _ = std::process::Command::new(ui_exe).arg("--block").spawn();
|
||||||
|
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|||||||
@@ -79,6 +79,8 @@ pub enum FocusEvent {
|
|||||||
Activated(ForegroundWindow),
|
Activated(ForegroundWindow),
|
||||||
/// The focused window closed, so there is no longer a foreground window.
|
/// The focused window closed, so there is no longer a foreground window.
|
||||||
Cleared,
|
Cleared,
|
||||||
|
/// Reload active schedules from storage triggered by UI IPC.
|
||||||
|
ReloadSchedules,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Source of focus events.
|
/// Source of focus events.
|
||||||
|
|||||||
@@ -74,6 +74,16 @@ impl Watcher {
|
|||||||
// thread exits when the connection drops.
|
// thread exits when the connection drops.
|
||||||
let _ = self.sink.send(event);
|
let _ = self.sink.send(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Instruct the daemon to reload schedules from the database immediately.
|
||||||
|
fn reload_schedules(&self) {
|
||||||
|
let _ = self.sink.send(FocusEvent::ReloadSchedules);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ping method to check if the daemon is alive.
|
||||||
|
fn ping(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Acquire the bus name and export [`Watcher`], returning the live connection.
|
/// Acquire the bus name and export [`Watcher`], returning the live connection.
|
||||||
@@ -115,3 +125,25 @@ pub fn serve(sink: Sender<FocusEvent>) -> Result<Connection, WatcherError> {
|
|||||||
pub fn release(connection: &Connection) {
|
pub fn release(connection: &Connection) {
|
||||||
let _ = connection.release_name(BUS_NAME);
|
let _ = connection.release_name(BUS_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tell the running daemon via D-Bus to reload schedules from storage.
|
||||||
|
pub fn notify_daemon_reload() -> Result<(), zbus::Error> {
|
||||||
|
let conn = Connection::session()?;
|
||||||
|
let proxy =
|
||||||
|
zbus::blocking::Proxy::new(&conn, BUS_NAME, OBJECT_PATH, "vu.valiq.blocker.Watcher")?;
|
||||||
|
let _: () = proxy.call("ReloadSchedules", &())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Query if the daemon is running and responding on D-Bus.
|
||||||
|
pub fn is_daemon_running() -> bool {
|
||||||
|
if let Ok(conn) = Connection::session() {
|
||||||
|
if let Ok(proxy) =
|
||||||
|
zbus::blocking::Proxy::new(&conn, BUS_NAME, OBJECT_PATH, "vu.valiq.blocker.Watcher")
|
||||||
|
{
|
||||||
|
let res: zbus::Result<bool> = proxy.call("Ping", &());
|
||||||
|
return res.unwrap_or(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,3 +25,7 @@ path = "src/main.rs"
|
|||||||
# - Either way the daemon must not grow past 5 MB stripped (section 5), and
|
# - Either way the daemon must not grow past 5 MB stripped (section 5), and
|
||||||
# the UI must not hold a GL context resident while idle.
|
# the UI must not hold a GL context resident while idle.
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
eframe = "0.36.2"
|
||||||
|
blocker-core = { path = "../../core" }
|
||||||
|
blocker-storage = { path = "../../storage" }
|
||||||
|
blocker-platform-linux = { path = "../platform-linux" }
|
||||||
|
|||||||
+289
-24
@@ -1,27 +1,292 @@
|
|||||||
//! Blocker block-screen UI.
|
//! Blocker GUI: Calm Block Screen and Schedule Manager.
|
||||||
//!
|
//!
|
||||||
//! Placeholder. No UI toolkit is bound here yet — see ADR-6 in
|
//! Built with egui/eframe per ADR-6.
|
||||||
//! `docs/DECISIONS.md` for why that is an open decision rather than an oversight.
|
|
||||||
//!
|
|
||||||
//! # What this process is for
|
|
||||||
//!
|
|
||||||
//! It is a short-lived, separately-executable window. The daemon stays resident
|
|
||||||
//! and small; this launches when a block fires and exits when it lifts. Keeping
|
|
||||||
//! them separate means a crash in window rendering cannot take enforcement down.
|
|
||||||
//!
|
|
||||||
//! # Design constraints that will apply (docs/UX-BLOCK-SCREEN.md)
|
|
||||||
//!
|
|
||||||
//! - Calm, not punitive. No red-alert styling, no shaming copy, no streak
|
|
||||||
//! counters. The screen exists to end an argument with the user's own
|
|
||||||
//! intention, so it must lower arousal.
|
|
||||||
//! - It always states which schedule blocked and when it lifts. Uncertainty is
|
|
||||||
//! what makes people fight the tool.
|
|
||||||
//! - It is never a dead end: a plain-looking "How to turn this off" entry is
|
|
||||||
//! always present, including under Strict Mode (CLAUDE.md section 1.5.4).
|
|
||||||
//! - Under Strict Mode the *absence* of a bypass button is stated explicitly.
|
|
||||||
//! Silently missing controls read as a bug and breed distrust.
|
|
||||||
|
|
||||||
fn main() {
|
use eframe::egui;
|
||||||
println!("blocker-ui {} (M0 placeholder)", env!("CARGO_PKG_VERSION"));
|
use std::env;
|
||||||
println!("UI toolkit undecided — ADR-6 in docs/DECISIONS.md");
|
|
||||||
|
fn main() -> eframe::Result<()> {
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
let is_block_screen = args.iter().any(|a| a == "--block" || a == "-b");
|
||||||
|
|
||||||
|
let options = eframe::NativeOptions {
|
||||||
|
viewport: egui::ViewportBuilder::default()
|
||||||
|
.with_inner_size(if is_block_screen { [520.0, 360.0] } else { [720.0, 520.0] })
|
||||||
|
.with_min_inner_size([400.0, 300.0])
|
||||||
|
.with_title(if is_block_screen { "Blocker — Stay Focused" } else { "Blocker" })
|
||||||
|
.with_always_on_top(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
eframe::run_native(
|
||||||
|
"Blocker",
|
||||||
|
options,
|
||||||
|
Box::new(move |cc| {
|
||||||
|
setup_custom_style(&cc.egui_ctx);
|
||||||
|
Ok(Box::new(BlockerApp::new(is_block_screen)))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_custom_style(ctx: &egui::Context) {
|
||||||
|
let mut visuals = egui::Visuals::dark();
|
||||||
|
visuals.override_text_color = Some(egui::Color32::from_rgb(220, 225, 230));
|
||||||
|
visuals.panel_fill = egui::Color32::from_rgb(26, 32, 44);
|
||||||
|
visuals.window_fill = egui::Color32::from_rgb(26, 32, 44);
|
||||||
|
ctx.set_visuals(visuals);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BlockerApp {
|
||||||
|
is_block_screen: bool,
|
||||||
|
active_tab: Tab,
|
||||||
|
schedules: Vec<blocker_core::model::Schedule>,
|
||||||
|
target_input: String,
|
||||||
|
schedule_name: String,
|
||||||
|
status_message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
enum Tab {
|
||||||
|
Status,
|
||||||
|
Schedules,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockerApp {
|
||||||
|
fn new(is_block_screen: bool) -> Self {
|
||||||
|
let schedules = Self::load_schedules_from_db();
|
||||||
|
Self {
|
||||||
|
is_block_screen,
|
||||||
|
active_tab: if is_block_screen { Tab::Status } else { Tab::Schedules },
|
||||||
|
schedules,
|
||||||
|
target_input: String::new(),
|
||||||
|
schedule_name: "Focus Hours".to_string(),
|
||||||
|
status_message: "Ready".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn db_path() -> std::path::PathBuf {
|
||||||
|
let base =
|
||||||
|
std::env::var_os("XDG_DATA_HOME").map(std::path::PathBuf::from).unwrap_or_else(|| {
|
||||||
|
let home = std::env::var_os("HOME").unwrap_or_else(|| ".".into());
|
||||||
|
std::path::PathBuf::from(home).join(".local/share")
|
||||||
|
});
|
||||||
|
base.join("blocker/blocker.db")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_schedules_from_db() -> Vec<blocker_core::model::Schedule> {
|
||||||
|
let path = Self::db_path();
|
||||||
|
if let Ok(store) = blocker_storage::Store::open(&path) {
|
||||||
|
store.load_schedules().unwrap_or_default()
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_schedules_to_db(&self) -> Result<(), String> {
|
||||||
|
let path = Self::db_path();
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
let _ = std::fs::create_dir_all(parent);
|
||||||
|
}
|
||||||
|
let mut store = blocker_storage::Store::open(&path).map_err(|e| e.to_string())?;
|
||||||
|
store.save_schedules(&self.schedules).map_err(|e| e.to_string())?;
|
||||||
|
let _ = blocker_platform_linux::watcher::notify_daemon_reload();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl eframe::App for BlockerApp {
|
||||||
|
fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
|
||||||
|
if self.is_block_screen {
|
||||||
|
self.render_block_screen(ui);
|
||||||
|
} else {
|
||||||
|
self.render_manager_ui(ui);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockerApp {
|
||||||
|
fn render_block_screen(&mut self, ui: &mut egui::Ui) {
|
||||||
|
ui.vertical_centered(|ui| {
|
||||||
|
ui.add_space(32.0);
|
||||||
|
ui.heading(
|
||||||
|
egui::RichText::new("Focus Mode Active")
|
||||||
|
.size(24.0)
|
||||||
|
.color(egui::Color32::from_rgb(226, 232, 240)),
|
||||||
|
);
|
||||||
|
ui.add_space(16.0);
|
||||||
|
ui.label(
|
||||||
|
egui::RichText::new(
|
||||||
|
"This application is currently paused by your scheduled focus block.",
|
||||||
|
)
|
||||||
|
.size(15.0)
|
||||||
|
.color(egui::Color32::from_rgb(160, 174, 192)),
|
||||||
|
);
|
||||||
|
ui.add_space(32.0);
|
||||||
|
|
||||||
|
ui.group(|ui| {
|
||||||
|
ui.set_width(380.0);
|
||||||
|
ui.add_space(8.0);
|
||||||
|
ui.label(
|
||||||
|
egui::RichText::new("Reason: Scheduled focus time")
|
||||||
|
.color(egui::Color32::from_rgb(203, 213, 225)),
|
||||||
|
);
|
||||||
|
ui.add_space(4.0);
|
||||||
|
ui.label(
|
||||||
|
egui::RichText::new("State: Process paused safely (SIGSTOP)")
|
||||||
|
.color(egui::Color32::from_rgb(148, 163, 184)),
|
||||||
|
);
|
||||||
|
ui.add_space(8.0);
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(36.0);
|
||||||
|
if ui
|
||||||
|
.button(
|
||||||
|
egui::RichText::new("Open Schedule Settings")
|
||||||
|
.color(egui::Color32::from_rgb(99, 179, 237)),
|
||||||
|
)
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
self.is_block_screen = false;
|
||||||
|
self.active_tab = Tab::Schedules;
|
||||||
|
}
|
||||||
|
ui.add_space(12.0);
|
||||||
|
if ui.button("Dismiss").clicked() {
|
||||||
|
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_manager_ui(&mut self, ui: &mut egui::Ui) {
|
||||||
|
ui.add_space(8.0);
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.heading("Blocker — Focus Manager");
|
||||||
|
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||||
|
if ui.selectable_label(self.active_tab == Tab::Schedules, "Schedules").clicked() {
|
||||||
|
self.active_tab = Tab::Schedules;
|
||||||
|
}
|
||||||
|
if ui.selectable_label(self.active_tab == Tab::Status, "Status").clicked() {
|
||||||
|
self.active_tab = Tab::Status;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
ui.separator();
|
||||||
|
ui.add_space(8.0);
|
||||||
|
|
||||||
|
match self.active_tab {
|
||||||
|
Tab::Status => {
|
||||||
|
ui.heading("Current Status");
|
||||||
|
ui.add_space(12.0);
|
||||||
|
let daemon_live = blocker_platform_linux::watcher::is_daemon_running();
|
||||||
|
if daemon_live {
|
||||||
|
ui.colored_label(
|
||||||
|
egui::Color32::from_rgb(0, 200, 100),
|
||||||
|
"Daemon Status: Running (D-Bus connected)",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ui.colored_label(
|
||||||
|
egui::Color32::from_rgb(220, 80, 80),
|
||||||
|
"Daemon Status: Inactive (not detected on D-Bus)",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ui.label(format!("Active Schedules: {}", self.schedules.len()));
|
||||||
|
ui.add_space(16.0);
|
||||||
|
if ui.button("Test Block Notification").clicked() {
|
||||||
|
let _ = blocker_platform_linux::notify::send_notification(
|
||||||
|
"Blocker Notification",
|
||||||
|
"Focus notification system verified live.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Tab::Schedules => {
|
||||||
|
ui.heading("Focus Schedules");
|
||||||
|
ui.add_space(8.0);
|
||||||
|
|
||||||
|
// List existing schedules
|
||||||
|
if self.schedules.is_empty() {
|
||||||
|
ui.label("No active schedules configured yet.");
|
||||||
|
} else {
|
||||||
|
let mut to_remove = None;
|
||||||
|
for (idx, sched) in self.schedules.iter().enumerate() {
|
||||||
|
ui.group(|ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.strong(&sched.name);
|
||||||
|
ui.label(format!("({:?})", sched.mode));
|
||||||
|
ui.with_layout(
|
||||||
|
egui::Layout::right_to_left(egui::Align::Center),
|
||||||
|
|ui| {
|
||||||
|
if ui.button("Delete").clicked() {
|
||||||
|
to_remove = Some(idx);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
ui.label(format!(
|
||||||
|
"Targets: {}",
|
||||||
|
sched
|
||||||
|
.targets
|
||||||
|
.iter()
|
||||||
|
.map(|t| match t {
|
||||||
|
blocker_core::model::Target::App(a) => a.clone(),
|
||||||
|
blocker_core::model::Target::Website(w) =>
|
||||||
|
format!("web:{w}"),
|
||||||
|
blocker_core::model::Target::Keyword(k) =>
|
||||||
|
format!("kw:{k}"),
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
));
|
||||||
|
});
|
||||||
|
ui.add_space(4.0);
|
||||||
|
}
|
||||||
|
if let Some(idx) = to_remove {
|
||||||
|
self.schedules.remove(idx);
|
||||||
|
if let Err(e) = self.save_schedules_to_db() {
|
||||||
|
self.status_message = format!("Error saving: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.add_space(16.0);
|
||||||
|
ui.separator();
|
||||||
|
ui.add_space(8.0);
|
||||||
|
ui.heading("Add New Schedule");
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Name:");
|
||||||
|
ui.text_edit_singleline(&mut self.schedule_name);
|
||||||
|
});
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("App Target (e.g. steam, firefox):");
|
||||||
|
ui.text_edit_singleline(&mut self.target_input);
|
||||||
|
});
|
||||||
|
ui.add_space(8.0);
|
||||||
|
if ui.button("Add App Block Schedule").clicked() {
|
||||||
|
let target = self.target_input.trim();
|
||||||
|
if !target.is_empty() {
|
||||||
|
let new_sched = blocker_core::model::Schedule {
|
||||||
|
id: format!(
|
||||||
|
"sched-{}",
|
||||||
|
std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.map(|d| d.as_secs())
|
||||||
|
.unwrap_or(0)
|
||||||
|
),
|
||||||
|
name: self.schedule_name.clone(),
|
||||||
|
icon: None,
|
||||||
|
mode: blocker_core::model::ScheduleMode::Blocklist,
|
||||||
|
targets: vec![blocker_core::model::Target::App(target.to_string())],
|
||||||
|
conditions: vec![],
|
||||||
|
paused: false,
|
||||||
|
};
|
||||||
|
self.schedules.push(new_sched);
|
||||||
|
if let Err(e) = self.save_schedules_to_db() {
|
||||||
|
self.status_message = format!("Error saving: {e}");
|
||||||
|
} else {
|
||||||
|
self.status_message = "Schedule saved!".to_string();
|
||||||
|
self.target_input.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ case "$1" in
|
|||||||
exec "$HERE/usr/bin/blocker-ui" "$@"
|
exec "$HERE/usr/bin/blocker-ui" "$@"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
exec "$HERE/usr/bin/blocker-daemon" "$@"
|
# Default to launching the GUI if launched without arguments, or daemon if requested
|
||||||
|
if [ -n "$DISPLAY" ] || [ -n "$WAYLAND_DISPLAY" ]; then
|
||||||
|
exec "$HERE/usr/bin/blocker-ui" "$@"
|
||||||
|
else
|
||||||
|
exec "$HERE/usr/bin/blocker-daemon" "$@"
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
Reference in New Issue
Block a user