Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ members = [
"plugins/rink",
"plugins/shell",
"plugins/kidex",
"plugins/powermenu",
"plugins/translate",
"plugins/randr",
"plugins/stdin",
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ The flake provides multiple packages:
- **applications** - the applications plugin
- **dictionary** - the dictionary plugin
- **kidex** - the kidex plugin
- **powermenu** - the powermenu plugin
- **randr** - the randr plugin
- **rink** - the rink plugin
- **shell** - the shell plugin
Expand Down Expand Up @@ -207,6 +208,8 @@ list of plugins in this repository is as follows:
- Quickly translate text.
- [Kidex](plugins/kidex/README.md)
- File search provided by [Kidex](https://github.com/Kirottu/kidex).
- [Powermenu](/plugins/powermenu/README.md)
- System power menu actions: lock, log out, power off, etc.
- [Randr](plugins/randr/README.md)
- Rotate and resize; quickly change monitor configurations on the fly.
- TODO: Only supports Hyprland, needs support for other compositors.
Expand Down
1 change: 1 addition & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
applications = mkPlugin "applications";
dictionary = mkPlugin "dictionary";
kidex = mkPlugin "kidex";
powermenu = mkPlugin "powermenu";
randr = mkPlugin "randr";
rink = mkPlugin "rink";
shell = mkPlugin "shell";
Expand Down
15 changes: 15 additions & 0 deletions plugins/powermenu/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "powermenu"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
anyrun-plugin = { path = "../../anyrun-plugin" }
abi_stable = "0.11.1"
serde = { version = "1.0.204", features = ["derive"] }
ron = "0.8.1"
num_enum = "0.7.3"
fuzzy-matcher = "0.3.7"
41 changes: 41 additions & 0 deletions plugins/powermenu/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Powermenu

A plugin to lock, logout, power off your machine, etc.

## Usage

Search for one of the following actions: lock, logout, power off, reboot, suspend, hibernate.
Select the action.
If prompted, confirm it.

## Configuration

```ron
// <Anyrun config dir>/powermenu.ron
Config(
lock: (
command: "loginctl lock-session",
confirm: false,
),
logout: (
command: "loginctl terminate-user $USER",
confirm: true,
),
poweroff: (
command: "systemctl -i poweroff",
confirm: true,
),
reboot: (
command: "systemctl -i reboot",
confirm: true,
),
suspend: (
command: "systemctl -i suspend",
confirm: false,
),
hibernate: (
command: "systemctl -i hibernate",
confirm: false,
),
)
```
103 changes: 103 additions & 0 deletions plugins/powermenu/src/actions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use abi_stable::std_types::ROption;
use anyrun_plugin::Match;
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
use num_enum::{IntoPrimitive, TryFromPrimitive};

#[derive(Clone, Copy, IntoPrimitive, TryFromPrimitive)]
#[repr(u64)]
pub enum PowerAction {
Lock,
Logout,
Poweroff,
Reboot,
Suspend,
Hibernate,
}

impl PowerAction {
const VALUES: [Self; 6] = [
Self::Lock,
Self::Logout,
Self::Poweroff,
Self::Reboot,
Self::Suspend,
Self::Hibernate,
];

pub const fn get_title(&self) -> &str {
match self {
Self::Lock => "Lock",
Self::Logout => "Log out",
Self::Poweroff => "Power off",
Self::Reboot => "Reboot",
Self::Suspend => "Suspend",
Self::Hibernate => "Hibernate",
}
}
pub const fn get_description(&self) -> &str {
match self {
Self::Lock => "Lock the session screen",
Self::Logout => "Terminate the session",
Self::Poweroff => "Shut down the system",
Self::Reboot => "Restart the system",
Self::Suspend => "Suspend the system to RAM",
Self::Hibernate => "Suspend the system to disk",
}
}

pub const fn get_icon_name(&self) -> &str {
match self {
Self::Lock => "system-lock-screen",
Self::Logout => "system-log-out",
Self::Poweroff => "system-shutdown",
Self::Reboot => "system-reboot",
Self::Suspend => "system-suspend",
Self::Hibernate => "system-suspend-hibernate",
}
}

pub fn as_match(self) -> Match {
Match {
title: self.get_title().into(),
icon: ROption::RSome(self.get_icon_name().into()),
use_pango: false,
description: ROption::RSome(self.get_description().into()),
id: ROption::RSome(self.into()),
}
}

pub fn get_fuzzy_matching_values(phrase: &str) -> impl Iterator<Item = Self> {
let fuzzy_matcher = SkimMatcherV2::default().ignore_case();
let mut matches_with_scores = Self::VALUES
.into_iter()
.filter_map(|action| {
action
.get_fuzzy_score(&fuzzy_matcher, phrase)
.map(|score| (action, score))
})
.collect::<Vec<_>>();
matches_with_scores.sort_by_key(|(_action, score)| *score);
matches_with_scores
.into_iter()
.map(|(action, _score)| action)
}

fn get_fuzzy_score(self, matcher: &impl FuzzyMatcher, phrase: &str) -> Option<i64> {
matcher
.fuzzy_match(self.get_title(), phrase)
.max(matcher.fuzzy_match(self.get_description(), phrase))
}
}

#[derive(PartialEq, Eq, IntoPrimitive, TryFromPrimitive)]
#[repr(u64)]
pub enum ConfirmAction {
Confirm,
Cancel,
}

impl ConfirmAction {
pub fn is_confirmed(&self) -> bool {
*self == Self::Confirm
}
}
93 changes: 93 additions & 0 deletions plugins/powermenu/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use serde::Deserialize;

use crate::actions::PowerAction;

#[derive(Deserialize, Default)]
pub struct PowerActionConfig {
pub command: String,
pub confirm: bool,
}

#[derive(Deserialize)]
pub struct Config {
#[serde(default = "Config::default_lock_config")]
lock: PowerActionConfig,
#[serde(default = "Config::default_logout_config")]
logout: PowerActionConfig,
#[serde(default = "Config::default_poweroff_config")]
poweroff: PowerActionConfig,
#[serde(default = "Config::default_reboot_config")]
reboot: PowerActionConfig,
#[serde(default = "Config::default_suspend_config")]
suspend: PowerActionConfig,
#[serde(default = "Config::default_hibernate_config")]
hibernate: PowerActionConfig,
}

impl Config {
fn default_lock_config() -> PowerActionConfig {
PowerActionConfig {
command: String::from("loginctl lock-session"),
confirm: false,
}
}

fn default_logout_config() -> PowerActionConfig {
PowerActionConfig {
command: String::from("loginctl terminate-user $USER"),
confirm: true,
}
}

fn default_poweroff_config() -> PowerActionConfig {
PowerActionConfig {
command: String::from("systemctl -i poweroff"),
confirm: true,
}
}

fn default_reboot_config() -> PowerActionConfig {
PowerActionConfig {
command: String::from("systemctl -i reboot"),
confirm: true,
}
}

fn default_suspend_config() -> PowerActionConfig {
PowerActionConfig {
command: String::from("systemctl -i suspend"),
confirm: false,
}
}

fn default_hibernate_config() -> PowerActionConfig {
PowerActionConfig {
command: String::from("systemctl -i hibernate"),
confirm: false,
}
}

pub const fn get_action_config(&self, action: PowerAction) -> &PowerActionConfig {
match action {
PowerAction::Lock => &self.lock,
PowerAction::Logout => &self.logout,
PowerAction::Poweroff => &self.poweroff,
PowerAction::Reboot => &self.reboot,
PowerAction::Suspend => &self.suspend,
PowerAction::Hibernate => &self.hibernate,
}
}
}

impl Default for Config {
fn default() -> Self {
Self {
lock: Self::default_lock_config(),
logout: Self::default_logout_config(),
poweroff: Self::default_poweroff_config(),
reboot: Self::default_reboot_config(),
suspend: Self::default_suspend_config(),
hibernate: Self::default_hibernate_config(),
}
}
}
Loading