Publish active titles through events
cce363662acebfe01d1118f5bc54b7b70f6f9159
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) |