Compare commits

..

1 Commits

Author SHA1 Message Date
2f736e2138
WIP 2025-03-01 12:20:05 +01:00
12 changed files with 134 additions and 433 deletions

16
Cargo.lock generated
View File

@ -103,6 +103,14 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "dndbuster"
version = "0.1.0"
dependencies = [
"chrono",
"gtk4",
]
[[package]]
name = "equivalent"
version = "1.0.1"
@ -551,14 +559,6 @@ dependencies = [
"system-deps",
]
[[package]]
name = "pdt"
version = "0.1.0"
dependencies = [
"chrono",
"gtk4",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"

View File

@ -1,5 +1,5 @@
[package]
name = "pdt"
name = "dndbuster"
version = "0.1.0"
edition = "2021"

View File

@ -1,7 +1,9 @@
# pdt - PleaseDisturbTimer
# DnDBuster - Do not Disturb Buster
A timer that notifies you - even if "do not disturb" is enabled.
Note: notification doesn't work as .desktop entry is missing. See dndbuster-python.
## Alternatives
https://man.archlinux.org/man/notify-send.1.en

View File

@ -1,172 +0,0 @@
// Copied from https://github.com/gtk-rs/gtk4-rs/blob/main/examples/clipboard/main.rs
use gtk4 as gtk;
use gtk::{
gdk, gio,
glib::{self, clone},
prelude::*,
};
fn main() -> glib::ExitCode {
let application = gtk::Application::builder()
.application_id("com.github.gtk-rs.examples.clipboard")
.build();
application.connect_activate(build_ui);
application.run()
}
fn build_ui(application: &gtk::Application) {
let window = gtk::ApplicationWindow::builder()
.application(application)
.title("Clipboard")
.default_width(660)
.default_height(420)
.build();
let display = gdk::Display::default().unwrap();
let clipboard = display.clipboard();
let container = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.margin_top(24)
.margin_bottom(24)
.margin_start(24)
.margin_end(24)
.halign(gtk::Align::Center)
.valign(gtk::Align::Center)
.spacing(24)
.build();
// The text copy/paste part
let title = gtk::Label::builder()
.label("Text")
.halign(gtk::Align::Start)
.build();
title.add_css_class("title-2");
container.append(&title);
let text_container = gtk::Box::builder()
.halign(gtk::Align::Center)
.orientation(gtk::Orientation::Horizontal)
.spacing(24)
.build();
let from_entry = gtk::Entry::builder()
.placeholder_text("Type text to copy")
.build();
text_container.append(&from_entry);
let copy_btn = gtk::Button::with_label("Copy");
copy_btn.connect_clicked(clone!(
#[weak]
clipboard,
#[weak]
from_entry,
move |_btn| {
let text = from_entry.text();
clipboard.set_text(&text);
}
));
text_container.append(&copy_btn);
let into_entry = gtk::Entry::new();
text_container.append(&into_entry);
let paste_btn = gtk::Button::with_label("Paste");
paste_btn.connect_clicked(clone!(
#[weak]
clipboard,
#[weak]
into_entry,
move |_btn| {
clipboard.read_text_async(
gio::Cancellable::NONE,
clone!(
#[weak]
into_entry,
move |res| {
if let Ok(Some(text)) = res {
into_entry.set_text(&text);
}
}
),
);
}
));
text_container.append(&paste_btn);
container.append(&text_container);
// The texture copy/paste part
let title = gtk::Label::builder()
.label("Texture")
.halign(gtk::Align::Start)
.build();
title.add_css_class("title-2");
container.append(&title);
let texture_container = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal)
.halign(gtk::Align::Center)
.spacing(24)
.build();
let file = gio::File::for_path("./examples/clipboard/asset.png");
let asset_paintable = gdk::Texture::from_file(&file).unwrap();
let image_from = gtk::Image::builder()
.pixel_size(96)
.paintable(&asset_paintable)
.build();
texture_container.append(&image_from);
let copy_texture_btn = gtk::Button::builder()
.label("Copy")
.valign(gtk::Align::Center)
.build();
copy_texture_btn.connect_clicked(clone!(
#[weak]
clipboard,
#[weak]
image_from,
move |_btn| {
let texture = image_from
.paintable()
.and_downcast::<gdk::Texture>()
.unwrap();
clipboard.set_texture(&texture);
}
));
texture_container.append(&copy_texture_btn);
let image_into = gtk::Image::builder()
.pixel_size(96)
.icon_name("image-missing")
.build();
texture_container.append(&image_into);
let paste_texture_btn = gtk::Button::builder()
.label("Paste")
.valign(gtk::Align::Center)
.build();
paste_texture_btn.connect_clicked(clone!(
#[weak]
clipboard,
move |_btn| {
clipboard.read_texture_async(
gio::Cancellable::NONE,
clone!(
#[weak]
image_into,
move |res| {
if let Ok(Some(texture)) = res {
image_into.set_paintable(Some(&texture));
}
}
),
);
}
));
texture_container.append(&paste_texture_btn);
container.append(&texture_container);
window.set_child(Some(&container));
window.present();
}

View File

@ -1,45 +0,0 @@
// Example copied from https://github.com/gtk-rs/gtk4-rs/blob/main/examples/clock/main.rs
use chrono::Local;
use gtk4 as gtk;
use gtk::{glib, prelude::*};
fn main() -> glib::ExitCode {
let application = gtk::Application::builder()
.application_id("com.github.gtk-rs.examples.clock")
.build();
application.connect_activate(build_ui);
application.run()
}
fn build_ui(application: &gtk::Application) {
let window = gtk::ApplicationWindow::new(application);
window.set_title(Some("Clock Example"));
window.set_default_size(260, 40);
let time = current_time();
let label = gtk::Label::default();
label.set_text(&time);
window.set_child(Some(&label));
window.present();
// we are using a closure to capture the label (else we could also use a normal
// function)
let tick = move || {
let time = current_time();
label.set_text(&time);
// we could return glib::ControlFlow::Break to stop our clock after this tick
glib::ControlFlow::Continue
};
// executes the closure once every second
glib::timeout_add_seconds_local(1, tick);
}
fn current_time() -> String {
format!("{}", Local::now().format("%Y-%m-%d %H:%M:%S"))
}

View File

@ -1,78 +0,0 @@
// Example copied from https://github.com/kashifsoofi/blog-code-samples/blob/gtk4-c-counter-app/gtk4-rust-counter-app/src/main.rs
use std::cell::Cell;
use std::rc::Rc;
use gtk4 as gtk;
use gtk::glib::clone;
use gtk::prelude::*;
use gtk::{glib, Application, ApplicationWindow, Label, Box, Button, Orientation};
const APP_ID: &str = "org.gtk_rs.GTK4Counter";
fn main() -> glib::ExitCode {
// Create a new application
let app = Application::builder().application_id(APP_ID).build();
// Connect to "activate" signal of `app`
app.connect_activate(build_ui);
// Run the application
app.run()
}
fn build_ui(app: &Application) {
let counter = Rc::new(Cell::new(0));
let label_counter = Label::builder()
.label(&counter.get().to_string())
.margin_top(12)
.margin_bottom(12)
.margin_start(12)
.margin_end(12)
.build();
let button_increase = Button::builder()
.label("Increase")
.margin_top(12)
.margin_bottom(12)
.margin_start(12)
.margin_end(12)
.build();
let button_decrease = Button::builder()
.label("Decrease")
.margin_top(12)
.margin_bottom(12)
.margin_start(12)
.margin_end(12)
.build();
button_increase.connect_clicked(clone!(@weak counter, @weak label_counter =>
move |_| {
counter.set(counter.get() + 1);
label_counter.set_label(&counter.get().to_string());
}));
button_decrease.connect_clicked(clone!(@weak label_counter =>
move |_| {
counter.set(counter.get() - 1);
label_counter.set_label(&counter.get().to_string());
}));
let gtk_box = Box::builder()
.orientation(Orientation::Vertical)
.build();
gtk_box.append(&label_counter);
gtk_box.append(&button_increase);
gtk_box.append(&button_decrease);
// Create a window and set the title
let window = ApplicationWindow::builder()
.application(app)
.title("GTK Counter App")
.child(&gtk_box)
.build();
// Present window
window.present();
}

22
src/custom_app/imp.rs Normal file
View File

@ -0,0 +1,22 @@
use gtk::glib;
use gtk::subclass::prelude::*;
use gtk4 as gtk;
// Object holding the state
#[derive(Default)]
pub struct CustomApp;
// The central trait for subclassing a GObject
#[glib::object_subclass]
impl ObjectSubclass for CustomApp {
const NAME: &'static str = "MyGtkAppCustomApp";
type Type = super::CustomApp;
type ParentType = gtk::Application;
}
impl ObjectImpl for CustomApp {}
impl ApplicationImpl for CustomApp {}
impl GtkApplicationImpl for CustomApp {}

44
src/custom_app/mod.rs Normal file
View File

@ -0,0 +1,44 @@
mod imp;
use gtk::gio;
use gtk::glib;
use gtk::prelude::ApplicationExtManual;
use gtk4 as gtk;
use gtk4::prelude::ApplicationExt;
const APP_ID: &str = "de.privacy1st.dndbuster";
glib::wrapper! {
pub struct CustomApp(ObjectSubclass<imp::CustomApp>)
@extends gio::Application, gtk::Application,
@implements gio::ActionMap, gio::ActionGroup;
}
impl CustomApp {
pub fn new() -> Self {
glib::Object::builder()
.property("application-id", APP_ID)
.property("resource-base-path", "/io/github/seadve/Kooha/")
.build()
}
pub fn run(&self) -> glib::ExitCode {
eprintln!("Kooha ({})", APP_ID);
eprintln!("Version: {} ({})", 0.1, "dev");
eprintln!("Datadir: {}", ".");
ApplicationExtManual::run(self)
}
pub async fn send_record_success_notification(&self) {
let notification = gio::Notification::new("Screencast recorded");
notification.set_body(Some("Yeeeeha!"));
self.send_notification(Some("record-success"), &notification);
}
}
impl Default for CustomApp {
fn default() -> Self {
Self::new()
}
}

24
src/custom_button/imp.rs Normal file
View File

@ -0,0 +1,24 @@
use gtk::glib;
use gtk::subclass::prelude::*;
use gtk4 as gtk;
// Object holding the state
#[derive(Default)]
pub struct CustomButton;
// The central trait for subclassing a GObject
#[glib::object_subclass]
impl ObjectSubclass for CustomButton {
const NAME: &'static str = "MyGtkAppCustomButton";
type Type = super::CustomButton;
type ParentType = gtk::Button;
}
// Trait shared by all GObjects
impl ObjectImpl for CustomButton {}
// Trait shared by all widgets
impl WidgetImpl for CustomButton {}
// Trait shared by all buttons
impl ButtonImpl for CustomButton {}

27
src/custom_button/mod.rs Normal file
View File

@ -0,0 +1,27 @@
mod imp;
use glib::Object;
use gtk::glib;
use gtk4 as gtk;
glib::wrapper! {
pub struct CustomButton(ObjectSubclass<imp::CustomButton>)
@extends gtk::Button, gtk::Widget,
@implements gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget;
}
impl CustomButton {
pub fn new() -> Self {
Object::builder().build()
}
pub fn with_label(label: &str) -> Self {
Object::builder().property("label", label).build()
}
}
impl Default for CustomButton {
fn default() -> Self {
Self::new()
}
}

View File

@ -1,106 +1,12 @@
// Based on examples
// - https://crates.io/crates/gtk4
// - https://github.com/gtk-rs/gtk4-rs/blob/main/examples/clock/main.rs
// - https://github.com/gtk-rs/gtk4-rs/blob/main/examples/clipboard/main.rs
// - https://github.com/kashifsoofi/blog-code-samples/blob/gtk4-c-counter-app/gtk4-rust-counter-app/src/main.rs
mod custom_button;
mod custom_app;
use chrono::Local;
use std::cell::Cell;
use std::rc::Rc;
use gtk::gio::{Notification, NotificationPriority};
use gtk::glib;
use gtk::glib::clone;
use gtk::prelude::*;
use gtk4 as gtk;
const APP_ID: &str = "de.privacy1st.pdt";
const DUR_MIN: f64 = 1.0;
const INTERVAL_SEC: u32 = 1;
use crate::custom_app::CustomApp;
fn main() -> glib::ExitCode {
let app = gtk::Application::builder().application_id(APP_ID).build();
app.connect_activate(move |app| build_ui(app));
let app = CustomApp::new();
app.send_record_success_notification().await;
app.run()
}
fn build_ui(app: &gtk::Application) {
let window = gtk::ApplicationWindow::builder()
.application(app)
.title("PleaseDisturbTimer")
.default_width(350)
.default_height(70)
.build();
let container = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.margin_top(24)
.margin_bottom(24)
.margin_start(24)
.margin_end(24)
.halign(gtk::Align::Center)
.valign(gtk::Align::Center)
.spacing(24)
.build();
// How to change variable using GTK4 Button? https://stackoverflow.com/a/76657798/6334421
let running = Rc::new(Cell::new(false));
let button_start = gtk::Button::with_label("Start");
button_start.connect_clicked(clone!(@strong running => move |_| {
running.set(true);
eprintln!("Timer started.");
}));
container.append(&button_start);
let button_stop = gtk::Button::with_label("Stop");
button_stop.connect_clicked(clone!(@strong running => move |_| {
running.set(false);
eprintln!("Timer stopped.");
}));
container.append(&button_stop);
let mut progress: f64 = 0.0;
let progress_bar = gtk::ProgressBar::new();
progress_bar.set_fraction(progress);
container.append(&progress_bar);
let time = current_time();
let label = gtk::Label::default();
label.set_text(&time);
// label.set_halign(gtk::Align::Center);
container.append(&label);
window.set_child(Some(&container));
window.present();
let tick = clone!(@strong app => move || {
if running.get() {
progress += ((INTERVAL_SEC as f64) / 60.0) / DUR_MIN;
progress_bar.set_fraction(progress);
}
if progress > 1.0 {
eprintln!("Time over.");
// TODO: Notification does not show up
let notification = Notification::new("Alert");
notification.set_body(Some("Time is over"));
notification.set_priority(NotificationPriority::Urgent);
app.send_notification(Some(APP_ID), &notification);
}
let time = current_time();
label.set_text(&time);
// we could return glib::ControlFlow::Break to stop our clock after this tick
glib::ControlFlow::Continue
});
// executes the closure once every `INTERVAL_SEC` seconds
glib::timeout_add_seconds_local(INTERVAL_SEC, tick);
}
fn current_time() -> String {
format!("{}", Local::now().format("%Y-%m-%d %H:%M:%S"))
}

View File

@ -1,29 +0,0 @@
use gtk4 as gtk;
use gtk::glib;
use gtk::prelude::*;
use gtk::gio::{Notification, NotificationPriority};
const APP_ID: &str = "de.privacy1st.pdt";
fn main() -> glib::ExitCode {
let app = gtk::Application::builder().application_id(APP_ID).build();
app.connect_activate(move |app| build_ui(app));
app.run()
}
fn build_ui(app: &gtk::Application) {
let window = gtk::ApplicationWindow::builder()
.application(app)
.title("System Notification")
.default_width(350)
.default_height(70)
.build();
window.present();
// TODO: Notification does not show up
let notification = Notification::new("Alert");
notification.set_body(Some("Time is over"));
notification.set_priority(NotificationPriority::Urgent);
let notification_id = "pdt.timeout";
app.send_notification(Some(notification_id), &notification);
}