Category: Linux

Adding Window Previews to Dash to Dock: A GNOME Shell Extension Deep Dive

I ported, debugged, and shipped a window preview feature for Dash to Dock — one of the most popular GNOME Shell extensions. This post documents the journey: what worked, what broke repeatedly, and the hard-won lessons about GNOME Shell extension development on Wayland.

The Starting Point: A Stale PR

It began with PR #574 on the upstream repository — an old pull request that added window preview popups when hovering over dock icons. The code was outdated and no longer compatible with current GNOME Shell. I pulled it down, analysed it, and set about porting it to the modern GNOME Shell API.

The goal was straightforward: when a user hovers over an application icon in the dock, show live thumbnail previews of that application’s open windows — similar to the taskbar preview feature in Windows.

The Focus Nightmare

The single hardest problem was focus management. GNOME Shell’s popup menu system uses a modal grab model — when a popup opens, it captures input events. This is fine for right-click context menus, but for hover-triggered preview windows it creates a nightmare:

  • The preview popup would steal focus — once shown, moving the mouse to another dock icon did nothing. The user was trapped.
  • The only escape was clicking elsewhere — hover-out events were swallowed by the modal grab.
  • Fixing focus broke the context menu — every attempt to make the preview release focus would break the right-click context menu. This happened at least 5 times during development.

The solution required carefully managing the popup’s reactive state: the preview needed to be open for rendering purposes but not capture input in the way a traditional menu does. We had to iterate through all dock icons on enter/leave events, explicitly closing previews for other applications.

Debugging on Wayland

GNOME Shell extensions on Wayland present unique debugging challenges. You cannot simply restart the shell — you need a nested Wayland session:

dbus-run-session -- gnome-shell --nested --wayland

Copy-paste does not work between the nested session and the host, so I added file-based logging early on to /tmp/dash-to-dock-DATE.log. This turned out to be essential. The log output was invaluable for analysis, creating a tight feedback loop: run the extension, reproduce the bug, analyse the logs, apply a fix, rebuild, repeat.

The Open/Close/Open Flicker Bug

One persistent bug: on the first hover over any icon, the preview would appear, disappear, then appear again — a visible flicker. It only happened once per icon, then worked fine afterward.

The root cause was a race condition between the hover-open timeout (300ms delay before showing) and the menu system’s own open/close signals. When the popup first rendered, GNOME Shell’s menu manager would fire a menu-closed signal during initial layout, which triggered our close handler, which then got re-triggered by the still-active hover state.

The fix was a justOpened guard flag that suppressed close events for a brief window after the initial open.

Window Preview Sizing

Getting the preview thumbnails to size correctly went through several iterations:

  1. First attempt: Fixed-size preview boxes — resulted in disproportionate previews with large empty spaces for narrow windows.
  2. Second attempt: Scale app thumbnails to fit the preview box — made everything look like squashed squares.
  3. Final approach: Dynamic width based on the application window’s aspect ratio, with a fixed height and a maximum width cap at 90% of desktop horizontal space. Single windows get a tight fit; multiple windows of the same app expand horizontally.

Aero Peek: Fading Away the Clutter

With previews working, I added an Aero Peek feature inspired by Windows: when hovering over a specific window thumbnail in the preview popup, all other windows fade to near-transparency (1% opacity), letting the user see the target window beneath the stack.

I initially tried full transparency (0% opacity) with a glow outline around windows, but GNOME Shell’s compositor does not support per-window glow effects without custom shaders. The simple opacity fade at 1% proved effective enough — windows are essentially invisible but technically still rendered, avoiding compositor edge cases.

Animation Styles That Refused to Animate

I added a settings dropdown for preview animation styles: Instant, Fade, Slide, Scale, Expand, and Dissolve. The settings UI worked, the values were stored and read correctly, the animation config function returned the right style… but nothing visually changed.

Over a dozen debug sessions later, the issue was that the animation transitions were being applied after the popup was already visible. The Clutter animation framework needs the initial state set before the actor is shown, then the target state set to trigger the transition. We were setting both states after show(), so there was nothing to animate between.

The Translation Gap

The feature introduced new user-facing strings — Show window previews on mouse hover, animation style names, etc. These all needed translations across the extension’s supported languages. GNOME Shell extensions use gettext, and each string needed entries in every .po file.

Key Takeaways for GNOME Shell Extension Development

  1. File-based logging is non-negotiable on Wayland — console.log goes to the journal, but you cannot easily copy from a nested session. Write to /tmp/.
  2. The menu/popup system is a minefield — GNOME Shell’s PopupMenu assumes modal interaction. If you need non-modal popups (like hover previews), expect to fight the framework.
  3. Test the right-click menu after every change — focus and event handling changes have a remarkable ability to break context menus silently.
  4. Race conditions are the default — hover events, timeouts, animation completions, and menu signals all fire asynchronously. Guard everything.
  5. Aspect ratio math is harder than it sounds — single window vs. multi-window layout, mixed landscape/portrait windows, and screen size limits all interact.
  6. Use a nested Wayland session — dbus-run-session gnome-shell nested wayland saves you from logging out after every crash.

The Result

The final feature, submitted as PR #2470, adds:

  • Hover-triggered window preview popups with live thumbnails
  • Dynamic sizing based on window aspect ratios
  • Clickable previews that bring windows to focus
  • Close buttons on individual previews
  • Aero Peek transparency effect
  • Multiple animation styles (fade, slide, scale, expand, dissolve)
  • Late-arriving window detection (preview appears when an app finishes launching)
  • Full translation support

Dozens of iterations, countless regressions, and one very stubborn focus-stealing bug later — it works.


S0ix Sleep and Hibernate on Meteor Lake, NVIDIA and the ThinkPad P1 Gen 7

Getting modern sleep (S0ix / “Modern Standby”) working on a Lenovo ThinkPad P1 Gen 7 (21KV) with Intel Meteor Lake and an NVIDIA RTX 4060 has been a significant undertaking. This post documents the hardware, the problems encountered, every fix applied, and the remaining blockers — in the hope it saves someone else the same multi-day debugging session.

Hardware

  • Laptop: Lenovo ThinkPad P1 Gen 7 (21KV0025UK)
  • CPU: Intel Core Ultra 7 165H (Meteor Lake)
  • GPU: NVIDIA RTX 4060 Max-Q Mobile (AD107M) — PRIME offload only, the Intel iGPU drives the display
  • OS: Fedora 43, kernel 6.19.x

The Problem: S0ix Does Not Work (Out of the Box)

Meteor Lake dropped legacy S3 sleep entirely. The only suspend option is S0ix (also called s2idle / Modern Standby), which requires every single device and subsystem to reach a low-power state. If any component stays awake, the CPU package cannot enter its deepest idle state and the laptop drains battery as if it were still running.

On this machine, over 35 PMC substate requirements were unmet out of the box. The root cause traces to the IOE die (PMC1) failing to power-gate, which appears to be a CSME firmware issue. The behaviour is even boot-dependent — some boots may partially work where others will not.

Kernel Parameters

The following kernel parameters were added to /etc/default/grub on the GRUB_CMDLINE_LINUX line:

pcie_aspm=force acpi.ec_no_wakeup=1 nmi_watchdog=0 snd_hda_intel.power_save=1
  • pcie_aspm=force — Force Active State Power Management on PCIe devices that don’t advertise support. Required because several Meteor Lake root ports won’t enter L1 otherwise.
  • acpi.ec_no_wakeup=1 — Prevent the Embedded Controller from generating spurious wakeups. ThinkPads are notorious for this.
  • nmi_watchdog=0 — Disable the NMI watchdog. It prevents the CPU from entering deep C-states.
  • snd_hda_intel.power_save=1 — Enable power saving on the HDA audio codec (1 second timeout).

ACPI Wake Sources

Several ACPI wake sources were causing immediate or spurious wake from suspend. A systemd service was created to disable them at boot:

# /etc/systemd/system/disable-wakeup-sources.service
# Disables: RP12, TXHC, TDM0, TRP0, TRP1

[Unit]
Description=Disable problematic ACPI wake sources
After=multi-user.target

[Service]
Type=oneshot
ExecStart=/bin/bash -c 'for src in RP12 TXHC TDM0 TRP0 TRP1; do echo $src > /proc/acpi/wakeup || true; done'

[Install]
WantedBy=multi-user.target

These sources relate to PCIe root ports and Thunderbolt controllers that would wake the machine immediately after suspend or on any USB-C cable event.

PMC LTR (Latency Tolerance Reporting) Overrides

Several devices were reporting LTR values that prevented the SoC from entering S0ix. These were overridden by writing to the PMC LTR ignore registers:

# LTR ignore indices:
# 1  = SOUTHPORT_B
# 3  = GBE (Gigabit Ethernet)
# 6  = ME (Management Engine)
# 8  = SOUTHPORT_C
# 25 = IOE_PMC

The GBE and ME entries are particularly important — the Intel Management Engine’s LTR blocks S0ix on virtually every Meteor Lake system unless ignored.

USB Subsystem

Two USB devices were holding the XHCI controller in D0, blocking package C-state entry:

  • The fingerprint reader
  • The Bluetooth adapter

Both were resolved by enabling USB autosuspend for all devices. Runtime PM was also explicitly enabled on the Thunderbolt host controller.

BIOS: Disable Intel AMT

Intel Active Management Technology (AMT) was enabled by default in the BIOS. This caused three PMC blockers:

  • KVMCC — KVM Controller for remote management
  • USBR0 — USB Redirection
  • SUSRAM — Suspend RAM used by AMT

Disabling AMT in the BIOS (Config → Network → Intel AMT) immediately cleared all three.

NVIDIA GPU Power Management

The NVIDIA GPU required extensive configuration to stop it from blocking low-power states:

Services Disabled

  • nvidia-powerd — Was causing the GPU to cycle between D3cold and D0 repeatedly
  • nvidia-persistenced — Kept a handle on the GPU, preventing it from entering D3cold

D3cold Enabled

Runtime power management and D3cold (full power off) were enabled for the NVIDIA GPU via /etc/modprobe.d/nvidia-pm.conf.

Proprietary Driver with GSP Firmware Disabled

The open-source nvidia-open kernel module was replaced with the proprietary NVIDIA driver, and GSP (GPU System Processor) firmware was disabled:

options nvidia NVreg_EnableGpuFirmware=0

This was necessary because the GSP firmware interfered with proper suspend/resume cycling on this GPU generation.

CUDA Isolation for Background Services

Any background service that might poke the GPU (e.g., monitoring tools, bots) was configured with:

Environment=CUDA_VISIBLE_DEVICES=

This prevents them from initialising the CUDA runtime and keeping the GPU in D0.

Other Fixes

  • intel-lpmd — Intel’s Low Power Mode Daemon was installed to help manage CPU power states on Meteor Lake.
  • SELinux — A custom /data partition had incorrect SELinux contexts, which was blocking systemd-logind from writing hibernate state. Relabelling the partition fixed it.

Hibernate: Broken

Hibernate (suspend-to-disk) does not work on this hardware combination and is unlikely to work any time soon.

The kernel panics during the hibernate image write phase. The NVIDIA driver’s pci_pm_freeze callback either fails or causes a kernel panic. This was tested with:

  • The open-source nvidia-open driver
  • The proprietary NVIDIA driver
  • GSP firmware on and off
  • Writing via /sys/power/state (procfs) directly
  • Skipping VT switch (no chvt)

All combinations result in a panic. This is a known upstream bug tracked at NVIDIA/open-gpu-kernel-modules Issue #922.

Do not attempt hibernate on this platform with NVIDIA drivers loaded.

What Still Blocks S0ix

Even after all the above fixes, S0ix is not fully achieved. The remaining blockers are in the PMC and relate to PLLs and fabric that cannot be controlled from userspace:

PMC1 (IOE Die)

  • SBR0–4 (Sideband Router instances)
  • FABRIC_PLL, TMU_PLL, BCLK_PLL, D2D_PLL, G5FPW PLLs, REF_PLL
  • VNN_SOC_REQ_STS

PMC0 (SoC Die)

  • LPSS — I2C4 stuck in D0 (likely the touchpad or touchscreen controller)
  • FABRIC_PLL, IOE_COND_MET
  • ITSS_CLK_SRC_REQ_STS
  • LSX_Wake4–7

These are firmware-level blockers. The IOE die (PMC1) issue in particular appears to require a CSME firmware update from Lenovo.

Things to Avoid

  • Do not restart systemd-logind — It will kill your entire GNOME session.
  • Do not use sleep hooks that run modprobe -r nvidiagnome-shell holds nvidia_drm open; the unload will hang.
  • Do not use rtcwake -m mem — It bypasses the NVIDIA systemd suspend services and will likely result in a broken resume.
  • Do not attempt hibernate — Kernel panic (see above).

Configuration Files Modified

For reference, the following files were created or modified during this process:

  • /etc/default/grub — Kernel parameters
  • /etc/modprobe.d/nvidia-pm.conf — NVIDIA power management options
  • /etc/modprobe.d/nvidia-installer-disable-nouveau.conf — Nouveau blacklist
  • /etc/systemd/logind.conf.d/lid-suspend-then-hibernate.conf — Lid action configuration
  • /etc/systemd/sleep.conf — Sleep/hibernate configuration
  • /etc/systemd/system/disable-wakeup-sources.service — ACPI wake source disabling
  • /usr/lib/systemd/system-sleep/ollama-nvidia-hibernate.sh — Pre/post sleep hooks

Conclusion

Modern Standby on Meteor Lake with NVIDIA discrete graphics is a minefield. While many of the userspace-controllable blockers can be resolved, the core IOE die power-gating issue and the NVIDIA hibernate panic are firmware bugs that no amount of kernel tuning can fix. If you’re buying a Meteor Lake workstation for Linux, check the NVIDIA hibernate bug status and your vendor’s CSME firmware changelog before committing.

]]>


PHP Binance API

I’ve been spending some time helping out the the PHP Binance API over on github – php binance.

check back in soon, i’ll be releasing a containerized PHP bot for currency trading 🙂

For now you can get started with the API

Clone

git clone https://github.com/jaggedsoft/php-binance-api.git

Clone

PHP Binance API


Raspberry pi 3 CSI camera with motion /dev/video0

There are a number of topics on the web, about getting a modified version of motion (motion-mmcal) to work with raspberry pi v2.1 CSI camera.

A simpler method is to expose the camera interface through the standard video 4 linux kernel interface /dev/video0.

This can be achieved by simply enabling the video 4 linux kernel module and installing the standard motion.

Install motion

apt-get install motion

Enable the kernel module on boot

# /etc/modules: kernel modules to load at boot time.
#
# This file contains the names of kernel modules that should be loaded
# at boot time, one per line. Lines beginning with "#" are ignored.

i2c-dev
cuse
bcm2835-v4l2

Load the module without reboot

modprobe bcm2835-v4l2

Start service as normal

systemctl enable motion
systemctl start motion

Raspberry pi 3 disable red and green lights

Disabling the lights on raspberry pi has been documented a number of times on the web.
If differs from model to model, however the following method just sets the brightness to zero and should work on all models.

Modify your /etc/rc.local too like the following

#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

# Print the IP address
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
  printf "My IP address is %s\n" "$_IP"
fi

echo 0 > /sys/class/leds/led1/brightness
echo 0 > /sys/class/leds/led0/brightness

exit 0