- Add `einkRefresh()` helper calling `pdlc-cli --refresh` after rotation, brightness changes, waveform changes, and when the widget opens - Disable CSS transitions and animations globally to reduce ghosting - Remove button hover transform and transition effects - Update README with e-ink-specific guidance and tuning notes
PineNote Shell Widget
A fullscreen GTK widget designed for Pine64 PineNote running Arch Linux with Hyprland, controllable via hyprgrass gestures.
Overview
This Astal-based GTK application provides:
- Screen rotation (portrait/landscape)
- Display brightness control (100%, 75%, 25%, 0% steps)
- Power operations (suspend, shutdown, reboot)
- Quick launch shortcuts for terminal, browser, and e-reader
- Display refresh functionality
- Audio waveform switching
Requirements
- OS: Arch Linux with Hyprland
- Desktop Environment: AGS (Aylur's GTK Shell) v3 / Astal
- Dependencies: GJS, GTK 3, Astal (
libastal-*-git),ags, hyprgrass - Display: Pine64 PineNote (or any compatible monitor)
Installation
Prerequisites
Astal and AGS are not distributed via npm. Install the system packages from the AUR:
# Install AGS and Astal core libraries (GTK3 + GJS bindings)
yay -S ags libastal-meta libastal-gjs-git libastal-hyprland-git libastal-brightness-git
# Base GTK/Layer-Shell support
sudo pacman -S gjs gtk3 gtk-layer-shell
Clone and Setup
cd ~/projects/pineNoteShell
# Project already set up in pinenote-shell/
# No npm dependencies are required
cd pinenote-shell
# Make widget executable
chmod +x ./src/main.js
Note:
npm installis not needed for this project. The previouspackage.jsonreferenced a package (@agi/gtk3) that does not exist on the npm registry.
Running
The entry point uses an ags run shebang, so you can start it directly:
./src/main.js
Or use the ags CLI explicitly:
ags run ./src/main.js
You can toggle the window from another terminal:
ags toggle pinenote-fullscreen -i pinenote-shell
Hyprland Configuration
Add the following to your ~/.config/hypr/hyprgrass.conf:
Note: AGS v3 uses
ags toggle/ags requestinstead of the old--js-evaluateflag.
[gesture:toggle_widget]
hotkeys = [Mod1, T]
shorthand = "pinch open"
actions = [
{
command = "exec ags toggle pinenote-fullscreen -i pinenote-shell",
timeout = 300,
target = "window:pinenote-fullscreen"
}
]
[gesture:brightness_up]
hotkeys = [Mod1, B]
shorthand = "swipe up"
actions = [
{
command = "exec ags request 'brightness 100' -i pinenote-shell",
timeout = 300,
target = "window:pinenote-fullscreen"
}
]
[gesture:brightness_down]
hotkeys = [Mod1, V]
shorthand = "swipe down"
actions = [
{
command = "exec ags request 'brightness 0' -i pinenote-shell",
timeout = 300,
target = "window:pinenote-fullscreen"
}
]
If you want the brightness requests to actually work, add a requestHandler to app.start in src/main.js, for example:
app.start({
instanceName: "pinenote-shell",
css: readFile(cssPath),
requestHandler(argv, response) {
if (argv[0] === "brightness") {
const level = parseInt(argv[1], 10)
brightness.set(level)
response(`brightness set to ${level}`)
}
response("unknown command")
},
main() {
PinenoteShell()
},
})
(You'll need to lift the brightness manager out of the component scope for this.)
Additional Gestures
Add more gestures for other controls:
[gesture:rotate_landscape]
hotkeys = [Mod1, R]
shorthand = "swipe left"
actions = [
{
command = "exec ags request 'rotate landscape' -i pinenote-shell",
timeout = 300,
target = "window:pinenote-fullscreen"
}
]
[gesture:launch_terminal]
hotkeys = [Mod1, T]
shorthand = "double tap left"
actions = [
{
command = "exec ags request 'launch terminal' -i pinenote-shell",
timeout = 300,
target = "window:pinenote-fullscreen"
}
]
Usage
Basic Controls
- Open/Close: Activate with hyprgrass gesture (see configuration)
- Screen Rotation: Tap landscape/portrait icons to rotate display
- Brightness: Click brightness level buttons or use gestures in config
- System Actions: Use suspend/shutdown buttons for power operations
- Launch Apps: Click terminal, browser, or e-reader shortcuts
Advanced Features
- Waveform Switching: Use the waveform control to switch between audio output modes
- Display Refresh: Force display refresh with the refresh button
- Status Display: Monitor rotation and brightness levels in footer
Configuration
Custom Applications
Edit src/main.js and modify the LauncherManager.launch() method:
const launchers = {
terminal: "alacritty", // Your preferred terminal
browser: "brave-browser", // Or chromium/firefox/edge
"e-reader": "calibre", // Your e-reader application
};
Hyprland Integration
The widget uses hyprctl and the AstalHyprland library to communicate with Hyprland:
- Screen rotation: Monitors the focused monitor's
transformproperty - Display refresh: Uses
pdlc-cli --refreshfor PineNote e-ink refresh - Brightness: Uses
AstalBrightness(/sys/class/backlight/)
Styling
The widget uses CSS styling loaded from $HOME/.config/ags/style.css. The style.css file in this directory is the source of truth; copy or symlink it there before running:
mkdir -p ~/.config/ags
cp ./style.css ~/.config/ags/style.css
Common customization options:
background-color: Change widget backgroundborder-radius,margin: Adjust widget borders and spacing- Icon sets: Use different icon themes for better visual consistency
Troubleshooting
Widget Won't Appear
- Check Astal/AGS installation: Ensure
ags,gjs, and the Astal libraries are installed correctly - Verify CSS path: Make sure
~/.config/ags/style.cssexists; otherwise the app will crash on start - Verify hyprgrass config: Make sure your gesture bindings are correct
- Debug output: Run with
ags run ./src/main.jsin a terminal to see console errors
Controls Not Working
- Permission issues: Some operations may require appropriate permissions
- Missing applications: Ensure your preferred terminal/browser exist
- Hyprland compatibility: Test basic
hyprctlcommands manually first
E-ink / PineNote-specific Tweaks
The code and CSS are already tuned for e-ink:
- No CSS transitions or animations in
style.cssto avoid ghosting. - E-ink refreshes are triggered automatically after rotation, brightness changes, waveform changes, and when the widget opens.
- If you still see ghosting, increase the refresh frequency or add
einkRefresh()after other interactive actions insrc/main.js.
Performance Issues
- The widget is designed to be lightweight and efficient
- For best performance on PineNote, avoid animations and continuous polling
- E-ink displays update slowly; the app uses GObject signals instead of polling wherever possible
- Monitor system resources with
htopor similar tools
Future Enhancements
Potential features for future development:
- Multi-monitor support
- Advanced brightness curves (not just discrete steps)
- Custom app launcher configuration
- Audio waveform visualization
- System monitoring widgets
- Integration with PineNote-specific settings
License
This project is provided as-is for your PineNote setup. Use responsibly and test changes before deploying to production hardware.