Replace async-signal-unsafe SIGTERM handling with signalfd registered in
the wlroots event loop. The traditional path (startup.c handleExitSig →
DispatchEvent → wl_event_loop_terminate → pipe write) works in headless
mode but races in nested-Wayland mode: the combination of SA_RESTART and
epoll ordering could cause the pipe write to go unnoticed until the next
event, leaving SIGTERM apparently ignored in interactive Sway sessions.
wl_event_loop_run() now:
1. Creates a signalfd(2) for SIGTERM, SIGINT, SIGHUP with SFD_NONBLOCK
2. Blocks those signals from async delivery (sigprocmask SIG_BLOCK)
3. Registers the fd in the wlroots event loop via wl_event_loop_add_fd
4. wl_signalfd_handler() reads the pending signal and calls
wl_display_terminate() from within loop context — fully synchronous,
no async-signal-safety concerns
The pipe-based wl_event_loop_terminate() is retained as a secondary
mechanism for code paths that call it outside the main event loop.
Cleanup (event source removal + fd close) happens in wl_display_close().
Smoke test updated with a new "signal check" that verifies the log line
"signalfd installed for SIGTERM/SIGINT/SIGHUP" before sending SIGTERM.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>