-
-
Notifications
You must be signed in to change notification settings - Fork 70
feat: add WhatsApp chat name tracking for macOS #122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -20,6 +20,68 @@ var url = undefined, incognito = undefined, title = undefined; | |||||
| // it's not possible to get the URL from firefox | ||||||
| // https://stackoverflow.com/questions/17846948/does-firefox-offer-applescript-support-to-get-url-of-windows | ||||||
|
|
||||||
| // Handle WhatsApp first (before switch statement) due to potential hidden characters in app name | ||||||
| if(appName.indexOf("WhatsApp") !== -1) { | ||||||
| // Get the active chat name from WhatsApp's navigation bar header | ||||||
| try { | ||||||
| mainWindow = oProcess. | ||||||
| windows(). | ||||||
| find(w => w.attributes.byName("AXMain").value() === true); | ||||||
|
|
||||||
| if (mainWindow) { | ||||||
| var chatName = undefined; | ||||||
|
|
||||||
| // The chat name appears as an AXHeading with identifier "NavigationBar_HeaderViewButton" | ||||||
| // In WhatsApp/Electron apps, the text is in AXDescription, not AXLabel | ||||||
| var allElements = mainWindow.entireContents(); | ||||||
| for (var i = 0; i < allElements.length; i++) { | ||||||
| try { | ||||||
| var elem = allElements[i]; | ||||||
| var role = elem.attributes.byName("AXRole").value(); | ||||||
|
|
||||||
| // Look for AXHeading elements (the chat name in the header) | ||||||
| if (role === "AXHeading") { | ||||||
| try { | ||||||
| // Check if it has the NavigationBar identifier (WhatsApp-specific) | ||||||
| var identifier = elem.attributes.byName("AXIdentifier").value(); | ||||||
| if (identifier && identifier === "NavigationBar_HeaderViewButton") { | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| // Use AXDescription, not AXLabel (Electron app quirk) | ||||||
| chatName = elem.attributes.byName("AXDescription").value(); | ||||||
| break; | ||||||
| } | ||||||
| } catch (e) { | ||||||
| // Continue searching | ||||||
| } | ||||||
| } | ||||||
| } catch (e) { | ||||||
| // Continue searching | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| // Set title to just the chat name | ||||||
| if (chatName && chatName.length > 0) { | ||||||
| title = chatName; | ||||||
| } else { | ||||||
| // Fallback to default window title if we couldn't get the chat name | ||||||
| title = mainWindow.attributes.byName("AXTitle").value(); | ||||||
| } | ||||||
| } | ||||||
| } catch (e) { | ||||||
| // If anything fails, fall back to default title behavior | ||||||
| try { | ||||||
| mainWindow = oProcess. | ||||||
| windows(). | ||||||
| find(w => w.attributes.byName("AXMain").value() === true); | ||||||
| if (mainWindow) { | ||||||
| title = mainWindow.attributes.byName("AXTitle").value(); | ||||||
| } | ||||||
| } catch (e2) { | ||||||
| // Final fallback | ||||||
| title = undefined; | ||||||
| } | ||||||
| } | ||||||
|
Comment on lines
+70
to
+82
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The outer Consider a two-phase approach: get |
||||||
| } | ||||||
|
|
||||||
| switch(appName) { | ||||||
| case "Safari": | ||||||
| // incognito is not available via safari applescript | ||||||
|
|
@@ -42,17 +104,20 @@ switch(appName) { | |||||
| title = Application(appName).windows[0].name(); | ||||||
| break; | ||||||
| default: | ||||||
| mainWindow = oProcess. | ||||||
| windows(). | ||||||
| find(w => w.attributes.byName("AXMain").value() === true) | ||||||
| // Only set title if it hasn't been set already (e.g., by WhatsApp handler above) | ||||||
| if (title === undefined) { | ||||||
| mainWindow = oProcess. | ||||||
| windows(). | ||||||
| find(w => w.attributes.byName("AXMain").value() === true) | ||||||
|
|
||||||
| // in some cases, the primary window of an application may not be found | ||||||
| // this occurs rarely and seems to be triggered by switching to a different application | ||||||
| if(mainWindow) { | ||||||
| title = mainWindow. | ||||||
| attributes. | ||||||
| byName("AXTitle"). | ||||||
| value() | ||||||
| // in some cases, the primary window of an application may not be found | ||||||
| // this occurs rarely and seems to be triggered by switching to a different application | ||||||
| if(mainWindow) { | ||||||
| title = mainWindow. | ||||||
| attributes. | ||||||
| byName("AXTitle"). | ||||||
| value() | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
entireContents()is called on every poll — severe performance costmainWindow.entireContents()synchronously fetches the entire macOS Accessibility UI element tree for the WhatsApp window. For a complex Electron app like WhatsApp, this can return thousands of elements and is known to take several hundred milliseconds to multiple seconds per call.Since
printAppStatus.jxais re-executed on every watcher poll (default: 1–2 seconds), this means the watcher will stall on every tick while WhatsApp is the frontmost app. In practice this will result in significant CPU usage and degraded watcher accuracy.A much cheaper approach is to avoid a full tree traversal and instead use a targeted AX query:
Even if
whosedoesn't descend recursively, it avoids materializing thousands of elements in one shot and is meaningfully faster for common-case searches.