Publish active titles through events

cce363662acebfe01d1118f5bc54b7b70f6f9159
Alexis Sellier committed ago 1 parent b56dddf6
README +3 -3
122 122
metadata and Waybar integration.
123 123
124 124
On each state change swm also writes line-oriented status records to standard
125 125
output. When `-s` is used, that output is connected to the startup command's
126 126
standard input. Workspace metadata uses `workspace N title VALUE` and
127 -
`workspace N color #RRGGBBAA` records; empty values mean unset. The active window
128 -
title is published atomically to
129 -
`$XDG_RUNTIME_DIR/swm-title`, which can be consumed by a custom bar module.
127 +
`workspace N color #RRGGBBAA` records; empty values mean unset. Use
128 +
`swmctl events --filter=active-window` to receive the active window title for
129 +
a custom bar module.
130 130
Visible window rectangles are published atomically to
131 131
`$XDG_RUNTIME_DIR/swm-windows` in the format accepted by `slurp -r`.
132 132
133 133
134 134
# TESTING AND DEVELOPMENT
swm.c +1 -14
5056 5056
5057 5057
/* Publish current display, workspace, and focus state on standard output. */
5058 5058
void publish_status(void) {
5059 5059
    monitor_t  *m = nullptr;
5060 5060
    client_t   *c;
5061 -
    FILE       *titlefile;
5062 -
    char        titlepath[MAX_STATE_PATH], tmppath[MAX_STATE_PATH];
5063 5061
    char        title[MAX_STATUS_FIELD], appid[MAX_STATUS_FIELD];
5064 -
    const char *runtime, *src;
5062 +
    const char *runtime;
5065 5063
    uint32_t    occ, urg;
5066 5064
    int         i;
5067 5065
5068 5066
    occ = urg = 0;
5069 5067
    wl_list_for_each(c, &clients, link) {
5109 5107
        else
5110 5108
            printf("workspace %d color \n", i + 1);
5111 5109
    }
5112 5110
    runtime = getenv("XDG_RUNTIME_DIR");
5113 5111
    publish_windows(runtime);
5114 -
5115 -
    if (runtime && selmon) {
5116 -
        src = (c = focus_top(selmon)) ? client_get_title(c) : "";
5117 -
        status_field(title, sizeof(title), src);
5118 -
        snprintf(titlepath, sizeof(titlepath), "%s/swm-title", runtime);
5119 -
5120 -
        if ((titlefile = file_replace_begin(titlepath, tmppath, sizeof(tmppath)))) {
5121 -
            fprintf(titlefile, "%s\n", title);
5122 -
            file_replace_commit(titlefile, tmppath, titlepath);
5123 -
        }
5124 -
    }
5125 5112
    fflush(stdout);
5126 5113
    workspace_broadcast();
5127 5114
}
5128 5115
5129 5116
/* Replace a command child with its program or an error notification. */
test/run.py +22 -3
25 25
PROFILES = BUILD / "profiles"
26 26
LOGS = BUILD / "logs"
27 27
CHILDREN: list[subprocess.Popen] = []
28 28
CURRENT_COMMAND: list[str] = []
29 29
CURRENT_LOG: Path | None = None
30 +
TITLE_EVENTS_LOG: Path | None = None
30 31
31 32
32 33
class Failure(RuntimeError):
33 34
    """Report a test failure without an internal traceback."""
34 35
249 250
    test("control-client-parsing", lambda: run(BIN / "ctl-unit", env=env))
250 251
    shutil.rmtree(runtime)
251 252
252 253
253 254
def current_title() -> str:
254 -
    """Return the compositor's published title."""
255 +
    """Return the last active title from the control event stream."""
255 256
256 -
    path = Path(os.environ["XDG_RUNTIME_DIR"]) / "swm-title"
257 -
    return path.read_text().rstrip("\n") if path.exists() else ""
257 +
    assert TITLE_EVENTS_LOG is not None
258 +
    lines = TITLE_EVENTS_LOG.read_text().splitlines()
259 +
    return lines[-1] if lines else ""
258 260
259 261
260 262
def wait_title(title: str) -> None:
261 263
    """Wait for the compositor to publish a title."""
262 264
273 275
    """Return a fixture client's log path."""
274 276
275 277
    return Path(os.environ["SWM_TEST_LOG_DIR"]) / f"{name}.log"
276 278
277 279
280 +
def start_title_events() -> None:
281 +
    """Track the active title throughout one compositor fixture."""
282 +
283 +
    global TITLE_EVENTS_LOG
284 +
    TITLE_EVENTS_LOG = client_log("focused-title")
285 +
    spawn(
286 +
        TITLE_EVENTS_LOG,
287 +
        BIN / "swmctl",
288 +
        "events",
289 +
        "--filter=active-window",
290 +
        env=os.environ.copy(),
291 +
    )
292 +
    eventually("initial active title", lambda: TITLE_EVENTS_LOG.stat().st_size)
293 +
294 +
278 295
def start_client(name: str, color: str, fullscreen: bool = False) -> subprocess.Popen:
279 296
    """Start an XDG client and wait for its geometry report."""
280 297
281 298
    report = Path(os.environ["SWM_TEST_DIR"]) / f"{name}.geometry"
282 299
    env = os.environ.copy()
348 365
349 366
350 367
def integration_session() -> None:
351 368
    """Exercise compositor behavior in a one-output session."""
352 369
370 +
    start_title_events()
353 371
    one = start_client("one", "ff5588dd")
354 372
    two = start_client("two", "ff55aa88")
355 373
356 374
    def publication() -> None:
357 375
        windows = (Path(os.environ["XDG_RUNTIME_DIR"]) / "swm-windows").read_text()
730 748
731 749
732 750
def integration_multioutput() -> None:
733 751
    """Exercise workspace movement and output removal."""
734 752
753 +
    start_title_events()
735 754
    client = start_client("multi", "ff778899")
736 755
737 756
    def multioutput() -> None:
738 757
        protocol("workspace-groups")
739 758
        virtual_keyboard(105, 1)