Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
github: ClaudiuSchuster
github: oss-singularity
7 changes: 7 additions & 0 deletions .github/linters/.markdown-lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
default: true

# Product pages intentionally use centered HTML for the logo and badges.
MD013: false
MD033: false
MD041: false
9 changes: 9 additions & 0 deletions .github/linters/.yaml-lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
extends: default

rules:
comments:
min-spaces-from-content: 1
line-length: disable
truthy:
check-keys: false
29 changes: 29 additions & 0 deletions .github/social-preview-src/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Social preview source

The repository preview is rendered at 1280×640 from three lossless sources:

- `background.png`: original 1774×887 Nemo action-bar artwork.
- the existing duplicate-action icon under `icons/`.
- deterministic SVG typography, product copy and layout generated by `render.py`.
- output copy and repository-relative paths from `variants.json`.

Rendering requires `rsvg-convert` from `librsvg2-bin`. The background, icon and
SVG overlay are rasterized together by Librsvg so local and CI renders use one
rendering path.

The committed output is `.github/social-preview.png`. Render every configured
variant with:

```console
make social-preview
```

Verify that the committed PNGs still match their sources with:

```console
make check-social-preview
```

The background was generated specifically for Nemo Action Bar with the built-in
ImageGen tool. It is intentionally text-free so typography remains crisp and
fully deterministic in the repository renderer.
Binary file added .github/social-preview-src/background.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
58 changes: 58 additions & 0 deletions .github/social-preview-src/overlay.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
88 changes: 88 additions & 0 deletions .github/social-preview-src/render-all.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/env python3
"""Render or verify every configured Nemo Action Bar social-preview variant."""

from __future__ import annotations

import argparse
import json
import subprocess
import sys
import tempfile
from pathlib import Path


def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--check",
action="store_true",
help="fail if a committed preview differs from a fresh render",
)
return parser.parse_args()


def configured_output(repo_dir: Path, value: object) -> Path:
relative = Path(str(value))
if relative.is_absolute():
raise SystemExit(f"Preview output must be repository-relative: {relative}")

output = (repo_dir / relative).resolve()
try:
output.relative_to(repo_dir)
except ValueError as error:
raise SystemExit(f"Preview output escapes the repository: {relative}") from error
return output


def render(render_script: Path, variant: str, output: Path) -> None:
subprocess.run(
[
sys.executable,
str(render_script),
"--variant",
variant,
"--output",
str(output),
],
check=True,
)


def main() -> int:
args = parse_args()
source_dir = Path(__file__).resolve().parent
repo_dir = source_dir.parents[1].resolve()
render_script = source_dir / "render.py"
variants = json.loads((source_dir / "variants.json").read_text(encoding="utf-8"))

if not isinstance(variants, dict) or not variants:
raise SystemExit("At least one social-preview variant must be configured.")

if not args.check:
for name in sorted(variants):
output = configured_output(repo_dir, variants[name]["output"])
render(render_script, name, output)
return 0

stale: list[Path] = []
with tempfile.TemporaryDirectory(prefix="nemo-action-bar-social-preview-check-") as temporary:
check_dir = Path(temporary)
for name in sorted(variants):
committed = configured_output(repo_dir, variants[name]["output"])
candidate = check_dir / f"{name}.png"
render(render_script, name, candidate)
if not committed.is_file() or committed.read_bytes() != candidate.read_bytes():
stale.append(committed)

if stale:
for output in stale:
print(f"Stale social preview: {output.relative_to(repo_dir)}", file=sys.stderr)
print("Run make social-preview and commit the results.", file=sys.stderr)
return 1

print(f"All {len(variants)} social-preview variants are current.")
return 0


if __name__ == "__main__":
raise SystemExit(main())
81 changes: 81 additions & 0 deletions .github/social-preview-src/render.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env python3
"""Render a deterministic Nemo Action Bar social-preview variant."""

from __future__ import annotations

import argparse
import html
import json
import shutil
import subprocess
import tempfile
from pathlib import Path

WIDTH = 1280
HEIGHT = 640


def parse_args(variants: list[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--variant", choices=variants, required=True)
parser.add_argument("--output", type=Path, required=True)
return parser.parse_args()


def rsvg_convert() -> str:
executable = shutil.which("rsvg-convert")
if not executable:
raise SystemExit("librsvg2-bin is required (rsvg-convert).")
return executable


def main() -> int:
source_dir = Path(__file__).resolve().parent
repo_dir = source_dir.parents[1]
variants = json.loads((source_dir / "variants.json").read_text(encoding="utf-8"))
args = parse_args(sorted(variants))
variant = variants[args.variant]

background = source_dir / "background.png"
icon = repo_dir / "icons/nemo-action-bar-duplicate-symbolic.svg"
if not background.is_file() or not icon.is_file():
raise SystemExit("Social-preview background or Nemo Action Bar icon is missing.")

replacements = {
"{{TAGLINE}}": html.escape(str(variant["tagline"])),
"{{DETAIL}}": html.escape(str(variant["detail"])),
}
overlay_text = (source_dir / "overlay.svg").read_text(encoding="utf-8")
for marker, value in replacements.items():
overlay_text = overlay_text.replace(marker, value)

svg_renderer = rsvg_convert()
output = args.output.resolve()
output.parent.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(prefix="nemo-action-bar-social-preview-") as temporary:
work_dir = Path(temporary)
overlay_svg = work_dir / "overlay.svg"
shutil.copy2(background, work_dir / "background.png")
shutil.copy2(icon, work_dir / "icon.svg")
overlay_svg.write_text(overlay_text, encoding="utf-8")

subprocess.run(
[
svg_renderer,
"--width",
str(WIDTH),
"--height",
str(HEIGHT),
"--output",
str(output),
str(overlay_svg),
],
check=True,
)

print(f"Rendered {args.variant}: {output}")
return 0


if __name__ == "__main__":
raise SystemExit(main())
7 changes: 7 additions & 0 deletions .github/social-preview-src/variants.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"generic": {
"output": ".github/social-preview.png",
"tagline": "Native actions, exactly where you browse.",
"detail": "18 configurable GTK actions. Live reload. No shell commands."
}
}
Binary file added .github/social-preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y \
fonts-noto-core \
gir1.2-gtk-3.0 \
gir1.2-nemo-3.0 \
librsvg2-bin \
python3-gi \
shellcheck
- name: Validate repository
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 1.2.1 — 2026-09-01

- Add a polished 1280×640 GitHub social-preview card and a reproducible source
workflow for rendering it.
- Refresh the root README hero with the card, release badge and concise product
positioning.

## 1.2.0 — 2026-08-24

- Use English for default button labels, validation errors and runtime dialogs.
Expand Down
9 changes: 8 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
.PHONY: check
.PHONY: check social-preview check-social-preview

check:
python3 -m py_compile nemo_action_bar.py tests/validate_config.py
PYTHONPATH=. python3 tests/validate_config.py
python3 -m json.tool buttons.json >/dev/null
shellcheck install.sh uninstall.sh
$(MAKE) check-social-preview

social-preview:
python3 .github/social-preview-src/render-all.py

check-social-preview:
python3 .github/social-preview-src/render-all.py --check
36 changes: 22 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,19 @@

<p align="center">
<a href="https://github.com/ClaudiuSchuster/nemo-action-bar/actions/workflows/check.yml"><img alt="Checks" src="https://github.com/ClaudiuSchuster/nemo-action-bar/actions/workflows/check.yml/badge.svg"></a>
<a href="https://github.com/oss-singularity/nemo-action-bar/releases/latest"><img alt="Latest release" src="https://img.shields.io/github/v/release/oss-singularity/nemo-action-bar?display_name=tag&amp;sort=semver"></a>
<a href="LICENSE"><img alt="License GPL-2.0-or-later" src="https://img.shields.io/badge/license-GPL--2.0--or--later-6f5bd5"></a>
<img alt="Nemo 5 or newer" src="https://img.shields.io/badge/Nemo-5%2B-75c46b">
<img alt="Python GTK 3" src="https://img.shields.io/badge/Python-GTK%203-3776ab">
</p>

<p align="center">
<img src=".github/social-preview.png" width="100%"
alt="Nemo Action Bar — native GTK actions for Nemo">
</p>

<p align="center"><sub>18 native Nemo actions, one focused bar, and a faster path through every folder.</sub></p>

The current default layout in a real Nemo window, together with Nemo's original
toolbar and the [active-window highlight](https://github.com/ClaudiuSchuster/cinnamon-active-window-highlight):

Expand Down Expand Up @@ -90,20 +98,20 @@ lines are intentionally not supported.

### Supported actions

| Action ID | Behavior |
| --- | --- |
| `new-folder` | Create and immediately name a folder |
| `cut`, `copy`, `paste` | Nemo's native clipboard operations |
| `duplicate`, `rename`, `trash` | Operate on the current selection |
| `undo`, `redo` | Nemo's file-operation history |
| `properties`, `select-all` | Properties or full selection |
| `show-hidden` | Toggle hidden files for the current window |
| `copy-path` | Put selected local paths/URIs on the clipboard as plain text |
| `open-terminal` | Open Nemo's configured terminal at the selected/current folder |
| `open-admin` | Use Nemo's built-in “Open as Root” action and authentication dialog |
| `favorite-toggle` | Add or remove the selection according to its current state |
| `archive-create` | Open File Roller's archive-creation dialog (`nemo-fileroller`) |
| `archive-extract` | Extract the selected supported archive here (`nemo-fileroller`) |
| Action ID | Behavior |
| ------------------------------ | ------------------------------------------------------------------- |
| `new-folder` | Create and immediately name a folder |
| `cut`, `copy`, `paste` | Nemo's native clipboard operations |
| `duplicate`, `rename`, `trash` | Operate on the current selection |
| `undo`, `redo` | Nemo's file-operation history |
| `properties`, `select-all` | Properties or full selection |
| `show-hidden` | Toggle hidden files for the current window |
| `copy-path` | Put selected local paths/URIs on the clipboard as plain text |
| `open-terminal` | Open Nemo's configured terminal at the selected/current folder |
| `open-admin` | Use Nemo's built-in “Open as Root” action and authentication dialog |
| `favorite-toggle` | Add or remove the selection according to its current state |
| `archive-create` | Open File Roller's archive-creation dialog (`nemo-fileroller`) |
| `archive-extract` | Extract the selected supported archive here (`nemo-fileroller`) |

The shipped `open-admin`, `favorite-toggle`, `archive-create` and
`archive-extract` entries use `"enabled": false`. Change the desired values to
Expand Down