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 serde::{Deserialize, Serialize};
use std::str::FromStr;
use thiserror::Error;
// Define a custom error type using the `thiserror` crate
#[derive(Debug, Error)]
pub enum ArgsError {
@ -19,7 +18,7 @@ pub enum Mode {
Drun,
/// reads from stdin and displays options which when selected will be output to stdout.
Dmenu
Dmenu,
}
impl FromStr for Mode {
@ -30,7 +29,9 @@ impl FromStr for Mode {
"run" => Ok(Mode::Run),
"drun" => Ok(Mode::Drun),
"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)]
#[clap(about = "Ravi is a wofi clone written in rust, it aims to be a drop in replacement")]
pub struct Args {
/// Forks the menu so you can close the terminal
#[clap(short = 'f', long = "fork")]
fork: bool,

View file

@ -1,9 +1,9 @@
use crate::args::Args;
use anyhow::anyhow;
use gtk4::prelude::ToValue;
use merge::Merge;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::args::Args;
#[derive(Debug, Deserialize, Serialize, Merge, Clone)]
pub struct Config {
@ -77,7 +77,7 @@ pub struct Config {
impl Default for Config {
fn default() -> Self {
Config{
Config {
style: None,
stylesheet: None,
color: None,
@ -236,7 +236,6 @@ fn default_password_char() -> Option<String> {
Some("*".to_owned())
}
pub fn merge_config_with_args(config: &mut Config, args: &Args) -> anyhow::Result<Config> {
let args_json = serde_json::to_value(args)?;
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> {
find_desktop_files().into_iter()
.find_map(|desktop_file| {
find_desktop_files().into_iter().find_map(|desktop_file| {
desktop_file
.get("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();
conf.load(desktop_file.as_path().to_str().unwrap()).ok()
})
}).collect();
})
.collect();
p
}

View file

@ -1,22 +1,27 @@
use crate::config::Config;
use anyhow::Context;
use gdk4::Display;
use gdk4::gio::File;
use gdk4::glib::Propagation;
use gdk4::prelude::{Cast, DisplayExt, MonitorExt};
use gdk4::Display;
use gtk4::prelude::{ApplicationExt, ApplicationExtManual, BoxExt, ButtonExt, EditableExt, EntryExt, FlowBoxChildExt, GtkWindowExt, ListBoxRowExt, NativeExt, WidgetExt};
use gtk4::{Align, EventControllerKey, Expander, FlowBox, FlowBoxChild, Label, ListBox, ListBoxRow, PolicyType, ScrolledWindow, SearchEntry, Widget};
use gtk4::prelude::{
ApplicationExt, ApplicationExtManual, BoxExt, ButtonExt, EditableExt, EntryExt,
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_layer_shell::{KeyboardMode, LayerShell};
use std::process::exit;
use anyhow::Context;
use log::error;
use crate::config::Config;
use std::process::exit;
pub struct EntryElement {
pub label: String, // todo support empty label?
pub icon_path: Option<String>,
pub action: Box<dyn Fn() + Send + 'static>,
pub sub_elements: Option<Vec<EntryElement>>,
}
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,
);
let display = Display::default().expect("Could not connect to a display");
// Apply CSS to the 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
let app = Application::builder()
.application_id("ravi")
.build();
let app = Application::builder().application_id("ravi").build();
app.connect_activate(move |app| {
// Create a toplevel undecorated window
@ -141,8 +143,6 @@ pub fn init(config: Config, elements: Vec<EntryElement>) -> anyhow::Result<()> {
window.show();
// Get the display where the window resides
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);
if let Some(monitor) = monitor {
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);
}
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);
}
} else {
error!("failed to get monitor to init window size");
}
});
});
let empty_array: [&str; 0] = [];
@ -233,9 +238,9 @@ fn percent_or_absolute(value: &String, base_value: i32) -> Option<i32> {
let value = value.trim();
match value.parse::<i32>() {
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)
},
}
Err(_) => None,
}
} else {

View file

@ -3,6 +3,7 @@
use crate::args::{Args, Mode};
use crate::config::Config;
use crate::desktop::find_desktop_files;
use crate::gui::EntryElement;
use clap::Parser;
use gdk4::prelude::Cast;
@ -18,7 +19,6 @@ use std::os::unix::process::CommandExt;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::sync::Arc;
use crate::desktop::find_desktop_files;
mod args;
mod config;
@ -65,11 +65,14 @@ fn main() -> anyhow::Result<()> {
match args.mode {
Mode::Run => {}
Mode::Drun => {
let mut entries : Vec<EntryElement> = Vec::new();
let mut entries: Vec<EntryElement> = Vec::new();
for file in &find_desktop_files() {
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 Some(exec) = desktop_entry.get("exec").and_then(|x| x.as_ref().cloned()) else {
let icon = desktop_entry
.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;
};
@ -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 {
entries.push({
EntryElement {
@ -100,7 +105,7 @@ fn main() -> anyhow::Result<()> {
}
}
}
entries.sort_by(|l, r|l.label.cmp(&r.label));
entries.sort_by(|l, r| l.label.cmp(&r.label));
if config.prompt.is_none() {
config.prompt = Some("dmenu".to_owned());
}