From e285b3c47b2fc7a4cdaba9a427d2958410e64620 Mon Sep 17 00:00:00 2001 From: Orestis Floros Date: Tue, 23 Dec 2025 15:11:03 +0100 Subject: [PATCH] Fix i3bar workspace buttons for primary screen (#6564) git bisect: c7344095ec242c5e2616841de27f11343a6010ee is the first bad commit ``` c7344095ec242c5e2616841de27f11343a6010ee (HEAD) Fix leak sanitizer memleaks (#6520) i3bar/src/ipc.c | 5 +++-- i3bar/src/workspaces.c | 58 +++++++++++++++++++++++++++++++++------------------------- i3bar/src/xcb.c | 1 + include/libi3.h | 10 ++++------ src/config_directives.c | 1 + src/config_parser.c | 85 +++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------ src/load_layout.c | 2 +- src/restore_layout.c | 4 ++++ 8 files changed, 96 insertions(+), 70 deletions(-) ``` To handle race conditions in the test, I tested with: ```diff diff --git a/i3bar/src/workspaces.c b/i3bar/src/workspaces.c index d712750d..0c481d18 100644 --- a/i3bar/src/workspaces.c +++ b/i3bar/src/workspaces.c @@ -286,6 +286,8 @@ void parse_workspaces_json(const unsigned char *json, const size_t size) { } } + sleep(1); + yajl_free(handle); FREE(params.cur_key); } ``` fixes https://github.com/i3/i3/issues/6560 --- i3bar/src/workspaces.c | 14 +- .../bugfixes/2-i3bar-primary-workspace-button | 1 + .../t/555-i3bar-workspace-output-assignment.t | 147 ++++++++++++++++++ 3 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 release-notes/bugfixes/2-i3bar-primary-workspace-button create mode 100644 testcases/t/555-i3bar-workspace-output-assignment.t diff --git a/i3bar/src/workspaces.c b/i3bar/src/workspaces.c index 26abf452..90b8c4ac 100644 --- a/i3bar/src/workspaces.c +++ b/i3bar/src/workspaces.c @@ -19,6 +19,7 @@ struct workspaces_json_params { struct ws_head *workspaces; i3_ws *workspaces_walk; char *cur_key; + bool need_output; bool parsing_rect; }; @@ -160,6 +161,7 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, const s TAILQ_INSERT_TAIL(ws->output->workspaces, ws, tailq); } + params->need_output = false; FREE(output_name); FREE(params->cur_key); @@ -181,6 +183,7 @@ static int workspaces_start_map_cb(void *params_) { new_workspace->num = -1; params->workspaces_walk = new_workspace; + params->need_output = true; params->parsing_rect = false; } else { params->parsing_rect = true; @@ -202,6 +205,15 @@ static int workspaces_end_map_cb(void *params_) { return 1; /* workspace already assigned to output */ } + /* If we processed the output field but didn't find the output, the + * workspace belongs to an output this bar instance doesn't manage. */ + if (!params->need_output) { + I3STRING_FREE(ws->name); + FREE(ws->canonical_name); + FREE(params->workspaces_walk); + return 1; + } + if (!ws->name || SLIST_EMPTY(outputs)) { /* Invalid state */ I3STRING_FREE(ws->name); FREE(ws->canonical_name); @@ -209,7 +221,7 @@ static int workspaces_end_map_cb(void *params_) { return 1; } - /* Handle no output case */ + /* Handle no output case - fallback to primary */ ws->output = get_output_by_name("primary"); if (ws->output == NULL) { ws->output = SLIST_FIRST(outputs); diff --git a/release-notes/bugfixes/2-i3bar-primary-workspace-button b/release-notes/bugfixes/2-i3bar-primary-workspace-button new file mode 100644 index 00000000..d4d63054 --- /dev/null +++ b/release-notes/bugfixes/2-i3bar-primary-workspace-button @@ -0,0 +1 @@ +fix i3bar workspace buttons showing up in primary screen diff --git a/testcases/t/555-i3bar-workspace-output-assignment.t b/testcases/t/555-i3bar-workspace-output-assignment.t new file mode 100644 index 00000000..bc50d766 --- /dev/null +++ b/testcases/t/555-i3bar-workspace-output-assignment.t @@ -0,0 +1,147 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • https://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • https://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • https://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • https://i3wm.org/downloads/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Verify that i3bar only shows correct workspace buttons in each output. +# Ticket: #6560 +# Bug still in: 4.25-6-g0e2e8290 +use File::Temp qw(tempdir); +use i3test i3_autostart => 0; +use i3test::Util qw(slurp); +use i3test::XTEST; +use POSIX qw(mkfifo); + +################################################################################ +# Test that a bar configured for primary output only shows workspaces from that +# output, not from other outputs. +################################################################################ + +# Create temp files for i3bar PID and exit signaling +my $tmpdir = tempdir(CLEANUP => 1); +my $pidfile = "$tmpdir/i3bar.pid"; +my $exitfifo = "$tmpdir/fifo"; +my $logfile = "$tmpdir/i3bar.log"; +mkfifo("$exitfifo", 0600) or BAIL_OUT "Could not create FIFO: $!"; + +# Create a wrapper script that tracks i3bar's PID and signals when it exits +my $scriptfile = "$tmpdir/i3bar-wrapper.sh"; +open(my $scriptfh, '>', $scriptfile) or BAIL_OUT "Cannot create wrapper: $!"; +print $scriptfh <<"EOF"; +#!/bin/sh +echo "---- DEBUG: i3bar wrapper ----" +cat "$scriptfile" +echo '---- DEBUG: i3bar wrapper ----' +# Use tee so that the logs also end up in the testsuite log file. +(i3bar -V "\$@" 2>&1 | tee "$logfile") & +echo \$! > "$pidfile" +wait +echo done > "$exitfifo" +EOF +close($scriptfh); +chmod 0755, $scriptfile; + +my $config = <<"EOT"; +font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 + +fake-outputs 1024x768+0+0P,1024x768+1024+0 +workspace 1 output fake-1 # primary +workspace 2 output fake-0 # nonprimary + +bar { + i3bar_command $scriptfile + output primary +} +EOT + +my $pid = launch_with_config($config); + +my $i3 = i3(get_socket_path()); +$i3->connect()->recv; +my $cv = AnyEvent->condvar; +my $timer = AnyEvent->timer(after => 1, interval => 0, cb => sub { $cv->send(0) }); +$i3->subscribe({ + window => sub { + my ($event) = @_; + if ($event->{change} eq 'new') { + if (defined($event->{container}->{window_properties}->{class}) && + $event->{container}->{window_properties}->{class} eq 'i3bar') { + $cv->send($event->{container}); + } + } + }, + })->recv; + +sub i3bar_present { + my ($nodes) = @_; + + for my $node (@{$nodes}) { + my $props = $node->{window_properties}; + if (defined($props) && $props->{class} eq 'i3bar') { + return $node->{window}; + } + } + + return 0 if !@{$nodes}; + + my @children = (map { @{$_->{nodes}} } @{$nodes}, + map { @{$_->{'floating_nodes'}} } @{$nodes}); + + return i3bar_present(\@children); +} + +my $i3bar_window = i3bar_present($i3->get_tree->recv->{nodes}); +if ($i3bar_window) { + ok(1, 'i3bar present'); +} else { + my $con = $cv->recv; + ok($con, 'i3bar appeared'); + $i3bar_window = $con->{window}; +} + +diag('i3bar window = ' . $i3bar_window); +xtest_sync_with_i3; +xtest_sync_with($i3bar_window); + +# The actual test +cmd 'workspace 1'; +my $win1 = open_window; +cmd 'workspace 2'; +my $win2 = open_window; + +# Kill i3bar gracefully BEFORE exiting i3 to ensure buffers are flushed +# (if i3 exits first, i3bar gets SIGPIPE and buffers are lost) +open(my $pidfh, '<', $pidfile) or BAIL_OUT "Cannot read i3bar PID: $!"; +my $bar_pid = <$pidfh>; +close($pidfh); +chomp($bar_pid); +kill('TERM', $bar_pid); + +# Wait for i3bar to exit by reading from the FIFO (blocks until wrapper writes) +open(my $fifofh, '<', $exitfifo) or BAIL_OUT "Cannot open FIFO: $!"; +my $result = <$fifofh>; +close($fifofh); +ok(defined($result), 'i3bar ended'); + +exit_gracefully($pid); + +my $log = slurp($logfile); + +my @ws2_draws = ($log =~ /Drawing button for WS 2 at/g); +ok(scalar(@ws2_draws) > 0, "Workspace 2 (on primary) is drawn in the bar"); + +my @ws1_draws = ($log =~ /Drawing button for WS 1 at/g); +is(scalar(@ws1_draws), 0, "Workspace 1 (on non-primary) should NOT be drawn on primary bar"); + +done_testing;