Create rust.yml
This commit is contained in:
parent
96ca92e036
commit
43cb14b9e8
6 changed files with 89 additions and 54 deletions
26
.github/workflows/rust.yml
vendored
Normal file
26
.github/workflows/rust.yml
vendored
Normal 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
|
||||||
10
src/args.rs
10
src/args.rs
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
@ -77,7 +77,7 @@ pub struct Config {
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Config{
|
Config {
|
||||||
style: None,
|
style: None,
|
||||||
stylesheet: None,
|
stylesheet: None,
|
||||||
color: None,
|
color: None,
|
||||||
|
|
@ -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)?;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
41
src/gui.rs
41
src/gui.rs
|
|
@ -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] = [];
|
||||||
|
|
@ -233,9 +238,9 @@ fn percent_or_absolute(value: &String, base_value: i32) -> Option<i32> {
|
||||||
let value = value.trim();
|
let value = value.trim();
|
||||||
match value.parse::<i32>() {
|
match value.parse::<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 {
|
||||||
|
|
|
||||||
17
src/main.rs
17
src/main.rs
|
|
@ -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;
|
||||||
|
|
@ -65,11 +65,14 @@ fn main() -> anyhow::Result<()> {
|
||||||
match args.mode {
|
match args.mode {
|
||||||
Mode::Run => {}
|
Mode::Run => {}
|
||||||
Mode::Drun => {
|
Mode::Drun => {
|
||||||
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 {
|
||||||
|
|
@ -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() {
|
if config.prompt.is_none() {
|
||||||
config.prompt = Some("dmenu".to_owned());
|
config.prompt = Some("dmenu".to_owned());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue