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
31 changes: 31 additions & 0 deletions plugins/power-button/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# OS / editor
.DS_Store
Thumbs.db
*.swp
*~
.idea/
.vscode/
*.code-workspace

# Logs / temp
*.log
*.tmp
*.bak
.cache/

# Secrets / local env (never commit)
.env
.env.*
!.env.example
*.pem
*.key
credentials.json
secrets.json
*_secret*
*_token*

# Build / package artifacts
dist/
build/
*.qmlc
*.jsc
84 changes: 84 additions & 0 deletions plugins/power-button/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Agent guide for the power-button plugin

This folder is a Ryoku shell plugin. If you are an AI agent editing it, follow
every rule below. They are the same rules `ryoku plugin validate` enforces, so
work that breaks one will not install. This file is self-contained: you do not
need any other document to work here.

## The rules

R1 Place. A plugin is one folder named after its id. Public / canonical source
is https://github.com/leandrofelix-dev/power-button ; local authoring clone is
`~/Work/power-button` (not the store install root). Never write into
`~/.local/share/ryoku/plugins/` (receipt-owned) or `~/.config/quickshell/` (the
shipped shell).

R2 Shape. `manifest.json` at the root; `service/Main.qml` (logic, no UI);
`content/Widget.qml` (the one view); optional `content/Panel.qml` (a bar panel);
`README.md`; `LICENSE`; `assets/preview-widget.png` (a real capture); scripts
only under `bin/` with a shebang and the exec bit. Every extra file listed in
manifest `files`.

R3 Id. Lowercase `[a-z0-9][a-z0-9-]*`, unique, not a built-in widget id.

R4 Imports. Only `QtQuick*`, `Quickshell*`, `Ryoku.PluginKit`,
`Ryoku.PluginKit.Singletons`, and files inside the plugin folder. Never
`shell.*`, `Ryoku.Ui*` internals, or a relative import that climbs out of the
folder.

R5 Settings. Declared in `metadata.settings` (types the bar renders: toggle,
choice, multi, int, text); read through `pluginApi.pluginSettings` behind a
default; written only through `pluginApi.saveSetting(key, value)`. Never edit
`shell.json` or `plugins.json` directly.

R6 Commands. Every external program the plugin runs is either in its own `bin/`
or listed in `dependencies.commands`. No `sudo`, `doas`, `su`. A privileged
action is allowed only through `pkexec`, only on an explicit click, only when
listed in manifest `capabilities.privileged` (exact command strings) and
explained in README.

R7 Network. Every host the plugin talks to is listed in
`capabilities.network`. No `curl … | sh`, no downloading and running code.

R8 Files. Write only under `pluginApi.stateDir`
(`$XDG_STATE_HOME/ryoku/plugins/<id>`), `$XDG_CACHE_HOME/ryoku/plugins/<id>`, or
a temp dir. Never touch `~/.ssh`, `/etc`, shell rc files, or another plugin's
folder.

R9 Shell. No `sh -c` with a string built from settings or program output
(injection). Pass argv arrays.

R10 Secrets and binaries. No tokens, keys or credentials in the tree; no
compiled binaries (ELF/Mach-O/.so); scripts only.

R11 Honesty. `official` is never true for a community plugin. `author` is
`Name <mail>`. README says what runs, what it reads, what it writes, and every
privileged/network capability.

## The workflow

1. `ryoku plugin new <id>` scaffolds this folder (already done).
2. Edit: `service/Main.qml` for logic, `content/Widget.qml` for the view, and
`content/Panel.qml` for the bar panel. Capture `assets/preview-widget.png`.
3. `ryoku plugin validate <dir>` and fix every blocking finding (warnings are
advisory but read them).
4. `ryoku plugin add <dir> --bar --yes` to install it, then verify it on the bar
and under **QS Bar Settings > Community**.
5. `ryoku plugin share <id>` ONLY when the user asks to publish. Never share a
plugin unless asked.

## What the host sets

Every entry point receives, from the host: `pluginApi` (see below), `s` (a scale
factor), `active`, `density`, and `widthBudget`. Read them; never assign them.

`pluginApi` exposes:

- `mainInstance`: the live `service/Main.qml` instance every view shares.
- `pluginSettings`: the resolved settings object; read behind a default.
- `pluginDir`: this plugin's folder.
- `stateDir`: `$XDG_STATE_HOME/ryoku/plugins/<id>`; the only place to write.
- `saveSetting(key, value)`: persist one setting.
- `panelOpen` (bool), `openPanel()`, `closePanel()`, `togglePanel()`: the bar
panel controls (present but inert off the bar). The sanctioned widget click is
`onClicked: pluginApi.togglePanel()`.
4 changes: 4 additions & 0 deletions plugins/power-button/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# CLAUDE.md

Read `AGENTS.md` in this folder before editing anything here.
It holds every rule this plugin must follow and the full authoring workflow.
21 changes: 21 additions & 0 deletions plugins/power-button/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Felix

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
105 changes: 105 additions & 0 deletions plugins/power-button/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Session

Session menu on the QS Bar: shut down, reboot, hibernate, or lock the screen.

![Session menu open on the QS Bar](assets/preview-widget.png)

## What it does

- **Widget** — a power glyph on the QS Bar. A click only opens the session panel;
the glyph never runs an action by itself.
- **Panel** — Shut Down, Restart, Hibernate, Lock screen. Labels follow the
system locale (English source strings; Portuguese in `content/I18n.qml`).
Escape or click outside dismisses via the host `PluginPanel`.
- **Service** — on an explicit panel row tap, runs one of the declared commands
below, then closes the panel.

No mutation ever happens on a bar-mark click.

## Install

From this repository on a Ryoku desktop:

```
ryoku plugin validate .
ryoku plugin add . --bar --yes
```

Or install from **Ryostore → Plugins** once it is listed. Enable and place it
under **QS Bar Settings → Community**.

## How it plugs in

The shell owns the bar host and the panel card. This plugin ships:

- `service/Main.qml` — headless session actions (`systemctl` / `ryoku-shell`).
- `content/Widget.qml` — the bar glyph (`density: glyph`); click calls
`pluginApi.togglePanel()`.
- `content/Panel.qml` — the session list (`density: full`); rows call the
service, then `pluginApi.closePanel()`.
- `content/I18n.qml` — plugin-local translations (R4 forbids shell I18n).

`hosts` is `topbarGlyph` only; `defaults.host` is `topbarGlyph`.

## Settings

None. `metadata.settings` is empty.

## What it runs

Every external command, and exactly when (only after a panel row tap):

| Command | When |
| --- | --- |
| `systemctl poweroff` | Shut Down |
| `systemctl reboot` | Restart |
| `systemctl hibernate` | Hibernate |
| `ryoku-shell lock` | Lock screen (same path as Super+L / native quick settings) |

## What it never does

- No network access.
- No privileged escalation (`pkexec` / `sudo` / `doas` / `su`).
- No writes under the plugin state dir, cache, or elsewhere.
- No compositor-specific tools (`hyprctl`, etc.).

## Requirements

- A running Ryoku shell with `Ryoku.PluginKit`.
- `systemctl` and `ryoku-shell` on `PATH` (declared in `dependencies.commands`).

## Source

Canonical / public source:
[https://github.com/leandrofelix-dev/power-button](https://github.com/leandrofelix-dev/power-button)

Local development clone (optional): `~/Work/power-button`.

## Where it lists

Community plugin (`official: false`). It appears under **QS Bar Settings >
Community** with the store's community warning, its author, and its switch.

## Develop

```
power-button/
manifest.json # id, version, hosts, entry points, files
service/Main.qml # main: session actions
content/Widget.qml # bar glyph
content/Panel.qml # session panel
content/I18n.qml # en / pt / pt_BR labels
assets/preview-widget.png # README / Store preview
LICENSE # MIT
```

## Maintenance & safety

Session is a community contribution. Its author maintains and updates it — not
the Ryoku team. Ryostore screens submissions against the plugin rules, but
screening is a safeguard, not a guarantee: review what a plugin does before
enabling it.

## Credits

Felix \<[email protected]\>. MIT licensed. See [LICENSE](LICENSE).
Binary file added plugins/power-button/assets/preview-widget.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 46 additions & 0 deletions plugins/power-button/content/I18n.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
pragma ComponentBehavior: Bound

import QtQuick

// Plugin-local i18n (R4 forbids Ryoku.Ui.Singletons.I18n). Same contract as the
// shell/greeter: English source keys, Qt.locale() for system language, miss → key.
QtObject {
id: i18n

// Exact regional codes first (pt_BR), then language base (pt), else English.
readonly property string lang: {
var n = Qt.locale().name; // e.g. pt_BR, en_US
if (i18n.catalogs[n])
return n;
var base = n.split("_")[0];
return i18n.catalogs[base] ? base : "en";
}

// en is implicit (keys are the strings). pt + pt_BR cover Portuguese locales.
readonly property var catalogs: ({
"pt": {
"Session": "Sessão",
"Shut Down": "Desligar",
"Restart": "Reiniciar",
"Hibernate": "Hibernar",
"Lock screen": "Bloquear o ecrã"
},
"pt_BR": {
"Session": "Sessão",
"Shut Down": "Desligar",
"Restart": "Reiniciar",
"Hibernate": "Hibernar",
"Lock screen": "Bloquear a tela"
}
})

function tr(s) {
if (s === undefined || s === null || s === "")
return s;
var map = i18n.catalogs[i18n.lang];
if (!map)
return s;
var v = map["" + s];
return v === undefined ? s : v;
}
}
Loading