#![doc = include_str!("../README.md")]
#![cfg_attr(docs_rs, feature(doc_cfg, async_fn_in_trait))]
#![deny(rust_2018_idioms)]
#![warn(clippy::pedantic)]
#![allow(
clippy::missing_errors_doc,
clippy::doc_markdown,
clippy::tabs_in_doc_comments,
clippy::module_name_repetitions,
clippy::ignored_unit_patterns
)]
#[path = "async.rs"]
#[cfg(feature = "tokio")]
#[cfg_attr(docs_rs, doc(cfg(feature = "tokio")))]
mod _async;
mod sync;
use std::any::Any;
use std::collections::HashMap;
use std::marker::PhantomData;
#[cfg(feature = "tokio")]
pub use _async::*;
use dbus::arg::{Append, Arg, ArgType, Dict, IterAppend, PropMap, RefArg, Variant};
use dbus::Signature;
#[cfg(feature = "derive")]
#[cfg_attr(docs_rs, doc(cfg(feature = "derive")))]
pub use krunner_derive::Action;
pub use sync::*;
pub trait Action: Sized {
fn all() -> &'static [Self];
fn from_id(s: &str) -> Option<Self>;
fn to_id(&self) -> String;
fn info(&self) -> ActionInfo;
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Config<A> {
pub match_filter: Option<MatchFilter>,
pub min_letter_count: Option<u32>,
_phan: PhantomData<A>,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum MatchFilter {
Keywords(Vec<String>),
Regex(String),
}
#[derive(Debug, Clone, PartialEq)]
pub struct Match<A> {
pub id: String,
#[doc(alias = "text")]
pub title: String,
#[doc(alias = "subtext")]
pub subtitle: Option<String>,
pub icon: MatchIcon,
pub ty: MatchType,
pub relevance: f64,
pub urls: Vec<String>,
pub category: Option<String>,
pub multiline: bool,
pub actions: Vec<A>,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum MatchIcon {
ByName(String),
Custom(ImageData),
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ActionInfo {
#[doc(alias = "text")]
pub title: String,
pub icon: String,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ImageData {
pub width: i32,
pub height: i32,
pub row_stride: i32,
pub has_alpha: bool,
pub format: ImageFormat,
pub data: Vec<u8>,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum ImageFormat {
Argb32,
Rgb32,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
pub enum MatchType {
NoMatch = 0,
CompletionMatch = 10,
PossibleMatch = 30,
#[deprecated(since = "0.1.0")]
InformationalMatch = 50,
HelperMatch = 70,
ExactMatch = 100,
}
pub(crate) fn action_as_arg<A: Action>(action: &A) -> (String, String, String) {
let ActionInfo { title, icon } = action.info();
(action.to_id(), title, icon)
}
impl<A> Default for Config<A> {
fn default() -> Self {
Self {
match_filter: None,
min_letter_count: None,
_phan: PhantomData,
}
}
}
impl MatchIcon {
fn new() -> Self {
Self::default()
}
}
impl Default for MatchIcon {
fn default() -> Self {
Self::ByName(String::new())
}
}
impl From<String> for MatchIcon {
fn from(s: String) -> Self {
Self::ByName(s)
}
}
impl From<ImageData> for MatchIcon {
fn from(i: ImageData) -> Self {
Self::Custom(i)
}
}
type AnyVariant = Variant<Box<dyn RefArg + 'static>>;
fn assert_sig<T: Arg>(expected: &'static str) -> Signature<'static> {
let sig = <T as Arg>::signature();
debug_assert_eq!(&*sig, expected);
sig
}
impl<A: Action> Arg for Config<A> {
const ARG_TYPE: ArgType = ArgType::Array;
fn signature() -> Signature<'static> {
assert_sig::<PropMap>("a{sv}")
}
}
impl<A: Action + 'static> Append for Config<A> {
fn append_by_ref(&self, i: &mut IterAppend<'_>) {
let mut fields = HashMap::<&'static str, AnyVariant>::new();
match &self.match_filter {
Some(MatchFilter::Keywords(kws)) => {
fields.insert("TriggerWords", Variant(kws.box_clone()));
}
Some(MatchFilter::Regex(r)) => {
fields.insert("MatchRegex", Variant(r.box_clone()));
}
_ => {}
}
if let Some(min_letter_count) = self.min_letter_count {
fields.insert("MinLetterCount", Variant(min_letter_count.box_clone()));
}
let actions: Vec<_> = A::all().iter().map(action_as_arg).collect();
fields.insert("Actions", Variant(actions.box_clone()));
Dict::new(fields.iter()).append_by_ref(i);
}
}
impl<A: Action> Default for Match<A> {
fn default() -> Self {
Self {
id: String::new(),
title: String::new(),
subtitle: None,
icon: MatchIcon::new(),
ty: MatchType::PossibleMatch,
relevance: 1.0,
urls: vec![],
category: None,
multiline: false,
actions: vec![],
}
}
}
impl<A: Action> Arg for Match<A> {
const ARG_TYPE: ArgType = ArgType::Struct;
fn signature() -> Signature<'static> {
assert_sig::<(String, String, String, MatchType, f64, PropMap)>("(sssida{sv})")
}
}
impl<A: Action> Append for Match<A> {
fn append_by_ref(&self, i: &mut IterAppend<'_>) {
let mut fields = HashMap::<&'static str, AnyVariant>::new();
let icon = match &self.icon {
MatchIcon::ByName(n) => n,
MatchIcon::Custom(_) => "",
};
if !self.urls.is_empty() {
fields.insert("urls", Variant(self.urls.box_clone()));
}
if let Some(category) = &self.category {
fields.insert("category", Variant(category.box_clone()));
}
if let Some(subtext) = &self.subtitle {
fields.insert("subtext", Variant(subtext.box_clone()));
}
if self.multiline {
fields.insert("multiline", Variant(self.multiline.box_clone()));
}
if !self.actions.is_empty() {
let actions: Vec<_> = self.actions.iter().map(A::to_id).collect();
fields.insert("actions", Variant(actions.box_clone()));
}
if let MatchIcon::Custom(icon) = &self.icon {
fields.insert("icon-data", Variant(icon.box_clone()));
}
let fields = Dict::new(fields.iter());
i.append((
&self.id,
&self.title,
&icon,
&self.ty,
&self.relevance,
&fields,
));
}
}
impl Arg for MatchType {
const ARG_TYPE: ArgType = i32::ARG_TYPE;
fn signature() -> Signature<'static> {
<i32 as Arg>::signature()
}
}
impl Append for MatchType {
fn append_by_ref(&self, i: &mut IterAppend<'_>) {
(*self as i32).append_by_ref(i);
}
}
impl Arg for ImageData {
const ARG_TYPE: ArgType = ArgType::Struct;
fn signature() -> Signature<'static> {
assert_sig::<(i32, i32, i32, bool, i32, i32, Vec<u8>)>("(iiibiiay)")
}
}
impl RefArg for ImageData {
fn arg_type(&self) -> ArgType {
Self::ARG_TYPE
}
fn signature(&self) -> Signature<'static> {
<Self as Arg>::signature()
}
fn append(&self, i: &mut IterAppend<'_>) {
self.append_by_ref(i);
}
fn as_any(&self) -> &dyn Any
where
Self: 'static,
{
self
}
fn as_any_mut(&mut self) -> &mut dyn Any
where
Self: 'static,
{
self
}
fn box_clone(&self) -> Box<dyn RefArg + 'static> {
Box::new(self.clone())
}
}
impl Append for ImageData {
fn append_by_ref(&self, i: &mut IterAppend<'_>) {
i.append((
&self.width,
&self.height,
&self.row_stride,
&self.has_alpha,
&self.format.bits_per_sample(),
&self.format.channels(),
&self.data,
));
}
}
impl ImageFormat {
fn bits_per_sample(&self) -> i32 {
match self {
Self::Argb32 | Self::Rgb32 => 8,
}
}
fn channels(&self) -> i32 {
match self {
Self::Argb32 => 4,
Self::Rgb32 => 3,
}
}
}