Create rust.yml

This commit is contained in:
Alexander Mohr 2025-04-06 22:35:08 +02:00
parent 96ca92e036
commit 43cb14b9e8
6 changed files with 89 additions and 54 deletions

26
.github/workflows/rust.yml vendored Normal file
View file

@ -0,0 +1,26 @@
name: Rust
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Format
run: cargo fmt --check
- name: Clippy
run: cargo clippy
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose

View file

@ -1,9 +1,8 @@
use std::str::FromStr;
use clap::Parser; use clap::Parser;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::str::FromStr;
use thiserror::Error; use thiserror::Error;
// Define a custom error type using the `thiserror` crate // Define a custom error type using the `thiserror` crate
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum ArgsError { pub enum ArgsError {
@ -19,7 +18,7 @@ pub enum Mode {
Drun, Drun,
/// reads from stdin and displays options which when selected will be output to stdout. /// reads from stdin and displays options which when selected will be output to stdout.
Dmenu Dmenu,
} }
impl FromStr for Mode { impl FromStr for Mode {
@ -30,7 +29,9 @@ impl FromStr for Mode {
"run" => Ok(Mode::Run), "run" => Ok(Mode::Run),
"drun" => Ok(Mode::Drun), "drun" => Ok(Mode::Drun),
"dmenu" => Ok(Mode::Dmenu), "dmenu" => Ok(Mode::Dmenu),
_ => Err(ArgsError::InvalidParameter(format!("{s} is not a valid argument show this, see help for details").to_owned())) _ => Err(ArgsError::InvalidParameter(
format!("{s} is not a valid argument show this, see help for details").to_owned(),
)),
} }
} }
} }
@ -38,7 +39,6 @@ impl FromStr for Mode {
#[derive(Parser, Debug, Deserialize, Serialize)] #[derive(Parser, Debug, Deserialize, Serialize)]
#[clap(about = "Ravi is a wofi clone written in rust, it aims to be a drop in replacement")] #[clap(about = "Ravi is a wofi clone written in rust, it aims to be a drop in replacement")]
pub struct Args { pub struct Args {
/// Forks the menu so you can close the terminal /// Forks the menu so you can close the terminal
#[clap(short = 'f', long = "fork")] #[clap(short = 'f', long = "fork")]
fork: bool, fork: bool,

View file

@ -1,9 +1,9 @@
use crate::args::Args;
use anyhow::anyhow; use anyhow::anyhow;
use gtk4::prelude::ToValue; use gtk4::prelude::ToValue;
use merge::Merge; use merge::Merge;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use crate::args::Args;
#[derive(Debug, Deserialize, Serialize, Merge, Clone)] #[derive(Debug, Deserialize, Serialize, Merge, Clone)]
pub struct Config { pub struct Config {
@ -236,7 +236,6 @@ fn default_password_char() -> Option<String> {
Some("*".to_owned()) Some("*".to_owned())
} }
pub fn merge_config_with_args(config: &mut Config, args: &Args) -> anyhow::Result<Config> { pub fn merge_config_with_args(config: &mut Config, args: &Args) -> anyhow::Result<Config> {
let args_json = serde_json::to_value(args)?; let args_json = serde_json::to_value(args)?;
let mut config_json = serde_json::to_value(config)?; let mut config_json = serde_json::to_value(config)?;

View file

@ -46,8 +46,7 @@ pub fn default_icon() -> String {
} }
fn fetch_icon_from_desktop_file(icon_name: &str) -> Option<String> { fn fetch_icon_from_desktop_file(icon_name: &str) -> Option<String> {
find_desktop_files().into_iter() find_desktop_files().into_iter().find_map(|desktop_file| {
.find_map(|desktop_file| {
desktop_file desktop_file
.get("Desktop Entry") .get("Desktop Entry")
.filter(|desktop_entry| { .filter(|desktop_entry| {
@ -154,6 +153,7 @@ pub(crate) fn find_desktop_files() -> Vec<HashMap<String, HashMap<String, Option
let mut conf = Ini::new(); let mut conf = Ini::new();
conf.load(desktop_file.as_path().to_str().unwrap()).ok() conf.load(desktop_file.as_path().to_str().unwrap()).ok()
}) })
}).collect(); })
.collect();
p p
} }

View file

@ -1,22 +1,27 @@
use crate::config::Config;
use anyhow::Context;
use gdk4::Display;
use gdk4::gio::File; use gdk4::gio::File;
use gdk4::glib::Propagation; use gdk4::glib::Propagation;
use gdk4::prelude::{Cast, DisplayExt, MonitorExt}; use gdk4::prelude::{Cast, DisplayExt, MonitorExt};
use gdk4::Display; use gtk4::prelude::{
use gtk4::prelude::{ApplicationExt, ApplicationExtManual, BoxExt, ButtonExt, EditableExt, EntryExt, FlowBoxChildExt, GtkWindowExt, ListBoxRowExt, NativeExt, WidgetExt}; ApplicationExt, ApplicationExtManual, BoxExt, ButtonExt, EditableExt, EntryExt,
use gtk4::{Align, EventControllerKey, Expander, FlowBox, FlowBoxChild, Label, ListBox, ListBoxRow, PolicyType, ScrolledWindow, SearchEntry, Widget}; FlowBoxChildExt, GtkWindowExt, ListBoxRowExt, NativeExt, WidgetExt,
};
use gtk4::{
Align, EventControllerKey, Expander, FlowBox, FlowBoxChild, Label, ListBox, ListBoxRow,
PolicyType, ScrolledWindow, SearchEntry, Widget,
};
use gtk4::{Application, ApplicationWindow, CssProvider, Orientation}; use gtk4::{Application, ApplicationWindow, CssProvider, Orientation};
use gtk4_layer_shell::{KeyboardMode, LayerShell}; use gtk4_layer_shell::{KeyboardMode, LayerShell};
use std::process::exit;
use anyhow::Context;
use log::error; use log::error;
use crate::config::Config; use std::process::exit;
pub struct EntryElement { pub struct EntryElement {
pub label: String, // todo support empty label? pub label: String, // todo support empty label?
pub icon_path: Option<String>, pub icon_path: Option<String>,
pub action: Box<dyn Fn() + Send + 'static>, pub action: Box<dyn Fn() + Send + 'static>,
pub sub_elements: Option<Vec<EntryElement>>, pub sub_elements: Option<Vec<EntryElement>>,
} }
pub fn init(config: Config, elements: Vec<EntryElement>) -> anyhow::Result<()> { pub fn init(config: Config, elements: Vec<EntryElement>) -> anyhow::Result<()> {
@ -33,7 +38,6 @@ pub fn init(config: Config, elements: Vec<EntryElement>) -> anyhow::Result<()> {
gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION, gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION,
); );
let display = Display::default().expect("Could not connect to a display"); let display = Display::default().expect("Could not connect to a display");
// Apply CSS to the display // Apply CSS to the display
gtk4::style_context_add_provider_for_display( gtk4::style_context_add_provider_for_display(
@ -43,9 +47,7 @@ pub fn init(config: Config, elements: Vec<EntryElement>) -> anyhow::Result<()> {
); );
// No need for application_id unless you want portal support // No need for application_id unless you want portal support
let app = Application::builder() let app = Application::builder().application_id("ravi").build();
.application_id("ravi")
.build();
app.connect_activate(move |app| { app.connect_activate(move |app| {
// Create a toplevel undecorated window // Create a toplevel undecorated window
@ -141,8 +143,6 @@ pub fn init(config: Config, elements: Vec<EntryElement>) -> anyhow::Result<()> {
window.show(); window.show();
// Get the display where the window resides // Get the display where the window resides
let display = window.display(); let display = window.display();
@ -151,17 +151,22 @@ pub fn init(config: Config, elements: Vec<EntryElement>) -> anyhow::Result<()> {
let monitor = display.monitor_at_surface(&surface); let monitor = display.monitor_at_surface(&surface);
if let Some(monitor) = monitor { if let Some(monitor) = monitor {
let geometry = monitor.geometry(); let geometry = monitor.geometry();
if let Some(w) = percent_or_absolute(&config.width.clone().unwrap_or("800".to_owned()), geometry.width()) { if let Some(w) = percent_or_absolute(
&config.width.clone().unwrap_or("800".to_owned()),
geometry.width(),
) {
window.set_width_request(w); window.set_width_request(w);
} }
if let Some(h) = percent_or_absolute(&config.height.clone().unwrap_or("500".to_owned()), geometry.height()) { if let Some(h) = percent_or_absolute(
&config.height.clone().unwrap_or("500".to_owned()),
geometry.height(),
) {
window.set_height_request(h); window.set_height_request(h);
} }
} else { } else {
error!("failed to get monitor to init window size"); error!("failed to get monitor to init window size");
} }
}); });
}); });
let empty_array: [&str; 0] = []; let empty_array: [&str; 0] = [];
@ -235,7 +240,7 @@ fn percent_or_absolute(value: &String, base_value: i32) -> Option<i32> {
Ok(n) => { Ok(n) => {
let result = ((n as f32 / 100.0) * base_value as f32) as i32; let result = ((n as f32 / 100.0) * base_value as f32) as i32;
Some(result) Some(result)
}, }
Err(_) => None, Err(_) => None,
} }
} else { } else {

View file

@ -3,6 +3,7 @@
use crate::args::{Args, Mode}; use crate::args::{Args, Mode};
use crate::config::Config; use crate::config::Config;
use crate::desktop::find_desktop_files;
use crate::gui::EntryElement; use crate::gui::EntryElement;
use clap::Parser; use clap::Parser;
use gdk4::prelude::Cast; use gdk4::prelude::Cast;
@ -18,7 +19,6 @@ use std::os::unix::process::CommandExt;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use std::sync::Arc; use std::sync::Arc;
use crate::desktop::find_desktop_files;
mod args; mod args;
mod config; mod config;
@ -68,8 +68,11 @@ fn main() -> anyhow::Result<()> {
let mut entries: Vec<EntryElement> = Vec::new(); let mut entries: Vec<EntryElement> = Vec::new();
for file in &find_desktop_files() { for file in &find_desktop_files() {
if let Some(desktop_entry) = file.get("desktop entry") { if let Some(desktop_entry) = file.get("desktop entry") {
let icon = desktop_entry.get("icon").and_then(|x| x.as_ref().map(|x| x.to_owned())); let icon = desktop_entry
let Some(exec) = desktop_entry.get("exec").and_then(|x| x.as_ref().cloned()) else { .get("icon")
.and_then(|x| x.as_ref().map(|x| x.to_owned()));
let Some(exec) = desktop_entry.get("exec").and_then(|x| x.as_ref().cloned())
else {
continue; continue;
}; };
@ -87,7 +90,9 @@ fn main() -> anyhow::Result<()> {
}) })
}; };
let name = desktop_entry.get("name").and_then(|x| x.as_ref().map(|x| x.to_owned())); let name = desktop_entry
.get("name")
.and_then(|x| x.as_ref().map(|x| x.to_owned()));
if let Some(name) = name { if let Some(name) = name {
entries.push({ entries.push({
EntryElement { EntryElement {