Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ _OBJ = alias.o background.o bar_item.o custom_events.o event.o graph.o \
image.o mouse.o shadow.o font.o text.o message.o mouse.o bar.o color.o \
window.o bar_manager.o display.o group.o mach.o popup.o \
animation.o workspace.om volume.o slider.o power.o wifi.om media.om \
hotload.o app_windows.o
hotload.o app_windows.o source_pid.om

OBJ = $(patsubst %, $(ODIR)/%, $(_OBJ))

Expand Down
107 changes: 86 additions & 21 deletions src/alias.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "alias.h"
#include "misc/helpers.h"
#include "source_pid.h"
#include <CoreFoundation/CFBase.h>
#include <CoreFoundation/CoreFoundation.h>

Expand All @@ -15,6 +16,13 @@ void print_all_menu_items(FILE* rsp) {
}

#endif

// On macOS 26+, check if we need to use accessibility API workaround
bool use_source_pid_workaround = source_pid_needs_workaround();
if (use_source_pid_workaround) {
source_pid_cache_refresh();
}

CFArrayRef window_list = CGWindowListCopyWindowInfo(kCGWindowListOptionAll,
kCGNullWindowID );
int window_count = CFArrayGetCount(window_list);
Expand Down Expand Up @@ -55,18 +63,36 @@ void print_all_menu_items(FILE* rsp) {
if (layer != MENUBAR_LAYER) continue;
CGRect bounds = CGRectNull;
if (!CGRectMakeWithDictionaryRepresentation(bounds_ref, &bounds)) continue;

char* owner_copy = cfstring_copy(owner_ref);
if (string_equals(owner_copy, "Window Server")) {
free(owner_copy);
continue;
}

// On macOS 26+, try to find the real source application name
// when the owner is "Control Centre" (items are now owned by Control Center)
if (use_source_pid_workaround &&
(string_equals(owner_copy, "Control Centre") ||
string_equals(owner_copy, "Control Center"))) {
char* source_name = source_name_for_window(bounds);
if (source_name) {
free(owner_copy);
owner_copy = source_name;
}
}

owner[item_count] = owner_copy;
name[item_count] = cfstring_copy(name_ref);
x_pos[item_count++] = bounds.origin.x;
}

if (item_count > 0) {
fprintf(rsp, "[\n");
fprintf(rsp, "Available menu bar items for aliases:\n");
fprintf(rsp, "=====================================\n\n");
fprintf(rsp, "%-30s %s\n", "APP NAME", "ALIAS COMMAND");
fprintf(rsp, "%-30s %s\n", "--------", "-------------");

int counter = 0;
for (int i = 0; i < item_count; i++) {
float current_pos = x_pos[0];
Expand All @@ -81,19 +107,24 @@ void print_all_menu_items(FILE* rsp) {

if (!name[current_pos_id] || !owner[current_pos_id]) continue;
if (strcmp(name[current_pos_id], "") != 0) {
if (counter++ > 0) {
fprintf(rsp, ", \n");
}
fprintf(rsp, "\t\"%s,%s\"", owner[current_pos_id],
name[current_pos_id] );
// Show clean app name on the left, full alias command on the right
fprintf(rsp, "%-30s --add alias \"%s,%s\" <position>\n",
owner[current_pos_id],
owner[current_pos_id],
name[current_pos_id]);
counter++;
}
x_pos[current_pos_id] = -9999.f;
}
fprintf(rsp, "\n]\n");
fprintf(rsp, "\nFound %d menu bar items.\n", counter);
fprintf(rsp, "Position can be: left, center, right\n");

for (int i = 0; i < window_count; i++) {
if (owner[i]) free(owner[i]);
if (name[i]) free(name[i]);
}
} else {
fprintf(rsp, "No menu bar items found.\n");
}
CFRelease(window_list);
}
Expand Down Expand Up @@ -132,6 +163,12 @@ static void alias_find_window(struct alias* alias) {
kCGNullWindowID );
int window_count = CFArrayGetCount(window_list);

// On macOS 26+, check if we need to use accessibility API workaround
bool use_source_pid_workaround = source_pid_needs_workaround();
if (use_source_pid_workaround) {
source_pid_cache_refresh();
}

for (int i = 0; i < window_count; ++i) {
CFDictionaryRef dictionary = CFArrayGetValueAtIndex(window_list, i);
if (!dictionary) continue;
Expand All @@ -145,39 +182,67 @@ static void alias_find_window(struct alias* alias) {
CFStringRef name_ref = CFDictionaryGetValue(dictionary, kCGWindowName);
if (!name_ref) continue;
if (!owner_ref) continue;

// Get bounds first (needed for source PID lookup on macOS 26+)
CFDictionaryRef bounds_ref = CFDictionaryGetValue(dictionary, kCGWindowBounds);
if (!bounds_ref) continue;
CGRect bounds;
if (!CGRectMakeWithDictionaryRepresentation(bounds_ref, &bounds)) continue;

char* owner = cfstring_copy(owner_ref);
char* name = cfstring_copy(name_ref);

if (!(alias->owner && strcmp(alias->owner, owner) == 0
&& ((alias->name && strcmp(alias->name, name) == 0)
|| (!alias->name && strcmp(name, "") != 0) ))) {
free(owner);
free(name);
continue;
// On macOS 26+, resolve the real owner name if it's Control Centre
char* resolved_owner = owner;
if (use_source_pid_workaround &&
(string_equals(owner, "Control Centre") ||
string_equals(owner, "Control Center"))) {
char* source_name = source_name_for_window(bounds);
if (source_name) {
resolved_owner = source_name;
}
}

bool owner_matches = alias->owner && strcmp(alias->owner, resolved_owner) == 0;
bool name_matches = (alias->name && strcmp(alias->name, name) == 0)
|| (!alias->name && strcmp(name, "") != 0);


if (resolved_owner != owner) free(resolved_owner);
free(owner);
free(name);

if (!(owner_matches && name_matches)) {
continue;
}

CFNumberRef layer_ref = CFDictionaryGetValue(dictionary, kCGWindowLayer);
if (!layer_ref) continue;

uint64_t layer = 0;
CFNumberGetValue(layer_ref, CFNumberGetType(layer_ref), &layer);
if (layer != MENUBAR_LAYER) continue;

CFNumberGetValue(owner_pid_ref,
CFNumberGetType(owner_pid_ref),
&alias->pid );
// Get the source PID on macOS 26+, otherwise use the window owner PID
if (use_source_pid_workaround) {
pid_t source_pid = source_pid_for_window(bounds);
if (source_pid != 0) {
alias->pid = source_pid;
} else {
CFNumberGetValue(owner_pid_ref,
CFNumberGetType(owner_pid_ref),
&alias->pid );
}
} else {
CFNumberGetValue(owner_pid_ref,
CFNumberGetType(owner_pid_ref),
&alias->pid );
}

CFNumberRef window_id_ref = CFDictionaryGetValue(dictionary,
kCGWindowNumber);

if (!window_id_ref) continue;
CFDictionaryRef bounds_ref = CFDictionaryGetValue(dictionary, kCGWindowBounds);
if (!bounds_ref) continue;

CGRect bounds;
CGRectMakeWithDictionaryRepresentation(bounds_ref, &bounds);

uint64_t wid;
CFNumberGetValue(window_id_ref,
Expand Down
26 changes: 26 additions & 0 deletions src/source_pid.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#pragma once
#include <ApplicationServices/ApplicationServices.h>
#include <CoreFoundation/CoreFoundation.h>
#include <stdbool.h>

// In macOS 26 (Tahoe), menu bar item windows are owned by Control Center
// instead of their source applications. This module provides functions to
// find the actual source PID using the Accessibility API.

// Check if we're running on macOS 26 or later where this workaround is needed
bool source_pid_needs_workaround(void);

// Get the source PID for a menu bar item window by matching its bounds
// to accessibility elements in running applications' extras menu bars.
// Returns 0 if the source PID cannot be determined.
pid_t source_pid_for_window(CGRect window_bounds);

// Get the source application name for a menu bar item window.
// Returns NULL if not found. Caller must free the returned string.
char* source_name_for_window(CGRect window_bounds);

// Initialize the source PID cache (call once at startup)
void source_pid_cache_init(void);

// Refresh the cached running applications list
void source_pid_cache_refresh(void);
Loading