Skip to content

fix: preserve environment variables through fork to fix Waybar launch#20

Open
Curious-Keeper wants to merge 3 commits intohyprwm:mainfrom
Curious-Keeper:pr/waybar-launch-fix
Open

fix: preserve environment variables through fork to fix Waybar launch#20
Curious-Keeper wants to merge 3 commits intohyprwm:mainfrom
Curious-Keeper:pr/waybar-launch-fix

Conversation

@Curious-Keeper
Copy link
Contributor

@Curious-Keeper Curious-Keeper commented Jan 25, 2026

Fixes #15

Problem

When hyprshutdown is launched from Waybar menu items (or other launchers like rofi), it hangs with a black screen instead of showing the UI and closing apps properly. The workaround was to use hyprctl dispatch exec hyprshutdown, but this should work directly.

Root Cause

SDDM relies on the WAYLAND_DISPLAY variable and hyprland cleans it up

Solution

Capture the environment variable before forking and restore after each fork in the daemonization process. This ensures they remain available for IPC and Wayland operations.

Changes

  • Added captureEnvVars() to save environment variables before forking
  • Added restoreEnvVars() to restore them after each fork
  • Modified forkoff() to accept and restore environment variables

Testing

Tested on both NVIDIA and non-NVIDIA systems. Works correctly when launched from:

  • Waybar menu items
  • Terminal
  • Keybinds
  • Tested against all my other changes too, non-breaking to incoming merges.

Files Changed

  • src/main.cpp - Environment variable preservation logic

Closes #15

This branch combines all three patch branches:

1. Waybar launch fix:
   - Preserve environment variables (HYPRLAND_INSTANCE_SIGNATURE,
     XDG_RUNTIME_DIR, WAYLAND_DISPLAY) through double-fork
   - Fixes hang when launched from Waybar menu

2. SDDM+NVIDIA fix:
   - Add --no-fork option to skip daemonization
   - Add --vt option for VT switching after exit
   - Fixes black screen on NVIDIA+SDDM systems

3. Code quality fixes:
   - AppState.cpp: Add validation for empty addresses and invalid PIDs
   - HyprlandIPC.cpp: Use ScopeGuard for automatic socket cleanup

Files changed:
- src/main.cpp: Environment preservation + --no-fork/--vt options
- src/state/AppState.cpp: Validation fixes
- src/state/HyprlandIPC.cpp: ScopeGuard for socket cleanup
When hyprshutdown is launched from Waybar menu items, it hangs with a
black screen because the double-fork daemonization process loses critical
environment variables needed for Wayland connection and Hyprland IPC.

This fix captures and restores the following environment variables:
- HYPRLAND_INSTANCE_SIGNATURE (required for Hyprland socket)
- XDG_RUNTIME_DIR (required for socket path resolution)
- WAYLAND_DISPLAY (required for Wayland backend connection)

The environment variables are captured before forking and restored after
each fork in the daemonization process, ensuring they remain available
for IPC and Wayland operations.

Fixes: [issue number/URL]

Note: This branch was tested with additional changes from other PRs
(code-quality and sddm-nvidia fixes) to verify compatibility. Those
changes have been removed to keep this PR focused on the single issue.
@Curious-Keeper
Copy link
Contributor Author

let me know if you want this as a clean PR with no other history and I can branch and resubmit. thanks.

Copy link
Member

@vaxerski vaxerski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait wait wait, a better question is why are the env variables lost? Fork shouldnt lose any?

@Curious-Keeper
Copy link
Contributor Author

when launched form terminal or direct exec keybinds, the parent is wayland/hyprland, my shell = full login session, so all env var are present.

However, when launched from waybar or rofi, the parent is no longer wayland it's waybar or that rofi menu, so it likely doesn't have all the variables to begin with, or they may be in a different state... so fork() just copies what the parent is, so parent=waybar=missing env vars=can't close correctly

@vaxerski
Copy link
Member

if waybar had no env it wouldnt be able to connect to the compositor to open.

@Curious-Keeper
Copy link
Contributor Author

Curious-Keeper commented Jan 26, 2026

Update:
See follow-up comments

Full disclaimer, I am not an expert and if I am wrong, please tell me why and I will fix or adjust. I am more concerned with learning and helping.

Also, trying to explain things is not my best.

So my incomplete and simplified way of describing what I think is going on earlier, here is what I think.

The parent process context does matter. When launched from a terminal or direct Hyprland keybind, the process inherits a full login environment, so the relevant variables are already present and stable. When launched from Waybar or rofi, the parent is Waybar/rofi instead, and the environment may be incomplete or in a transitional state.

What I didn’t say, because who want's to read a book on a PR.

Here is a really long way of explaining what I think

The real issue (I think) shows up when that environment state intersects with static initialization timing inside the IPC code. If the environment is read too early even briefly the static variable captures a NULL/empty value and never updates, regardless of what the environment looks like later.

So the parent process explanation still applies, but the timing of when those variables are first read is what actually makes the bug stick.

My patch is essentially a workaround for those. or what I think are contributing to the issue. It works by capturing and explicitly restoring the relevant values, which avoids timing-related problems.

The main issue seems to come from static initialization timing in HyprlandIPC.cpp around line 48:

static const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE");

This is evaluated once, on the first call to getFromSocket(). If that call happens before the environment variables are restored after daemonization, it can capture a NULL or empty value and never update.

Here’s what I think is actually causing the problem:

  • Static variable initialization timing:
    In HyprlandIPC.cpp:48, the static HIS variable captures the environment variable value on first use. If getFromSocket() is called (directly or indirectly) before the environment is restored post-daemonization, it locks in a NULL/empty value permanently.

  • Waybar launch context:
    When Waybar launches hyprshutdown (via a button click, rofi, etc.), it may use system() or something similar. That can spawn a shell that doesn’t reliably inherit the full environment, or it may read the environment at a point where daemonization hasn’t finished restoring it yet. I am also not a waybar or rofi expert, this is just what seems to drive it.

  • Double-fork timing:
    The fork() → setsid() → fork() pattern creates a new session. While environment variables should be inherited, if any code path reads them before restoreEnvVars() is called after each fork — or if IPC code initializes too early — things break. Hangs on a black screen for this specific issue.

The patch works because it captures the critical environment variables (HYPRLAND_INSTANCE_SIGNATURE, XDG_RUNTIME_DIR, WAYLAND_DISPLAY) before any forking occurs, then explicitly restores them after each fork. This guarantees they’re available regardless of:

  • how Waybar launches the process, because I don't know.

  • when the static variable in HyprlandIPC.cpp gets initialized, because I couldn't figure it out.

  • the exact timing of the daemonization sequence, because I am not an expert.

basically a defensive patch for processes that depend on environment context.

A maybe fix could be changing this:

static const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE");

to:

const char* getHIS() {
    static const char* HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE");
    return HIS;
}

Or better yet, pass it as a parameter or make it non-static entirely. The patch I suggested may not even be the ideal long-term solution, it’s just a valid workaround. Plus I didn't test if changing timing or static fixed anything. I just added the patch to main and submitted. I needed it to work for my build and someone here had the same issue so I patched, tested, and pushed.

@Curious-Keeper
Copy link
Contributor Author

a very long way of saying, this is the best I got. hope it makes sense.

During investigation, we discovered that hyprshutdown works correctly
from waybar and rofi unless you are using an SDDM greeter. The SDDM
greeter requires WAYLAND_DISPLAY to be preserved through the fork
process to maintain proper Wayland context.

This change simplifies the environment variable preservation to only
keep WAYLAND_DISPLAY, which is sufficient for SDDM compatibility while
maintaining functionality. The other environment variables (HYPRLAND_INSTANCE_SIGNATURE
and XDG_RUNTIME_DIR) are not needed for the patch.
@Curious-Keeper
Copy link
Contributor Author

after getting some help, this PR is needed to keep SDDM happy.

hyprland cleans up WAYLAND_DISPLAY, and sddm relies on it.

removed extra variables.

@vaxerski
Copy link
Member

As far as I know, env is passed once to the process and a fork() should inherit it. Something else resets the env.

@Curious-Keeper
Copy link
Contributor Author

really not sure what else it can be. I am stuck here for now, unless someone else knows where to look. I know the patch works, but if what you are saying is correct, I am not sure what is un-setting envs.

@quantumfate
Copy link

Perhaps this #15 and issue on hyprpanel can give more context to the situation?

@Curious-Keeper
Copy link
Contributor Author

Perhaps this #15 and issue on hyprpanel can give more context to the situation?

yea, I ref 15 in the description. the issue isn't a crash for me. It doesn't "crash", it hangs on a black screen, because it doesn't reinstate sddm to complete the logout sequence for a user, even though hyprshutdown actually did work correctly, it's getting to sddm greeter from hyprshutdown that's the issue.

You can use ctl+alt+f2 and it loads back to sddm and log in as expected, so no crash, just disconnected.

Since hyprshutdown is only meant to shutdown hyprland and return to login greet (for me that is sddm) I made the patch that stores the WAYLAND_DISPLAY env, then calls it at the end so it properly initiates sddm.

Note: This only seems to be an issue with sddm greeter, when used from tuigreet, it logs out as expected, but tuigreet is tty based, not vt. this likely plays a roll but I am not sure how to better trouble shoot it. Also note, that this only happens from waybar or rofi menus, I can exec hyprshutdown normally and it works fine, it is only from a different parent that it seems to matter

I already pushed one change for sddm providing a vt flag, in 16 but that doesn't fix this issue. Even though they both resulted in the same black screen prior to patch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

To run hypershutdown from waybar, or rofi etc, run it with hyprctl dispatch exec

3 participants