Skip to content
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

Added a (very) basic tree walking functionality for Mac #49

Merged
merged 8 commits into from
Jan 23, 2024
Merged
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
7 changes: 4 additions & 3 deletions examples/mac/dump_tree_mac.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@
#include "include/axaccess/mac/axapi_node.h"

using mac_inspect::AXAPINode;
using mac_inspect::AXAPINodePtr;

void print_usage(std::string& program_name) {
std::cout << "Usage: "<< program_name << " <pid>\n";
}

void logInfoForPID(pid_t pid) {
AXAPINode application = AXAPINode::createForPID(pid);
AXAPINodePtr application = AXAPINode::createForPID(pid);

std::string title = application.GetTitle();
std::string title = application->GetTitle();
std::cerr << "Title: " << title << "\n";
std::string role = application.GetRole();
std::string role = application->GetRole();
std::cerr << "Role: " << role << "\n";
}

Expand Down
18 changes: 15 additions & 3 deletions include/axaccess/mac/axapi_node.h
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
#ifndef LIB_MAC_AXAPI_NODE_H_
#define LIB_MAC_AXAPI_NODE_H_

#include <ApplicationServices/ApplicationServices.h>

#include <map>
#include <memory>
#include <string>
#include <vector>

namespace mac_inspect {

class AXAPINode;
typedef std::unique_ptr<AXAPINode> AXAPINodePtr;

class AXAPINode {
public:
explicit AXAPINode(AXUIElementRef ax_element);
~AXAPINode() = default;

static AXAPINode createForPID(long pid);
static AXAPINodePtr createForPID(long pid);

std::string GetRole();
std::string GetTitle();
std::vector<std::string> GetAttributeNames();
std::string GetStringAttributeValue(std::string& attribute_name);
long GetChildCount();
AXAPINodePtr GetChildAt(long index);

private:
AXUIElementRef ax_element_;
explicit AXAPINode(AXUIElementRef ax_element);
AXUIElementRef ax_ui_element_;
};

} // namespace mac_inspect

#endif // LIB_MAC_AXAPI_NODE_H_
54 changes: 43 additions & 11 deletions lib/mac/axapi_node.mm
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,43 @@
if (cf_string == nullptr)
return "";

const char* chars = CFStringGetCStringPtr(cf_string, CFStringGetSystemEncoding());
const char* chars =
CFStringGetCStringPtr(cf_string, CFStringGetSystemEncoding());
if (!chars) {
// CFStringRef can secretly be an NSString, in which case CFStringGetCStringPtr doesn't work.
NSString* ns_string = (__bridge NSString*) cf_string;
const char* ns_chars = [ns_string cStringUsingEncoding:[NSString defaultCStringEncoding]];
// CFStringRef can secretly be an NSString, in which case
// CFStringGetCStringPtr doesn't work.
NSString* ns_string = (__bridge NSString*)cf_string;
const char* ns_chars =
[ns_string cStringUsingEncoding:[NSString defaultCStringEncoding]];
chars = ns_chars ? ns_chars : "";
}
return std::string(chars);
}

AXAPINode::AXAPINode(AXUIElementRef ax_element) : ax_element_(ax_element) {}
AXAPINode::AXAPINode(AXUIElementRef ax_ui_element)
: ax_ui_element_(ax_ui_element) {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just curious the motivation for this name change?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just making it match the platform type name more closely, because I was confusing myself :)


AXAPINodePtr AXAPINode::createForPID(long pid) {
AXUIElementRef ax_ui_element = AXUIElementCreateApplication((pid_t)pid);
return std::unique_ptr<AXAPINode>(new AXAPINode(ax_ui_element));
}

std::string AXAPINode::GetRole() {
CFTypeRef cf_role = nullptr;
if (AXUIElementCopyAttributeValue(ax_element_, kAXRoleAttribute, &cf_role) != noErr)
if (AXUIElementCopyAttributeValue(ax_ui_element_, kAXRoleAttribute,
&cf_role) != kAXErrorSuccess) {
return "";
}

return CFStringRefToStdString((CFStringRef) cf_role);
return CFStringRefToStdString((CFStringRef)cf_role);
}

std::string AXAPINode::GetTitle() {
CFStringRef cf_title = nullptr;
if (AXUIElementCopyAttributeValue(ax_element_, kAXTitleAttribute, (CFTypeRef*)&cf_title) != noErr)
if (AXUIElementCopyAttributeValue(ax_ui_element_, kAXTitleAttribute,
(CFTypeRef*)&cf_title) != kAXErrorSuccess) {
return "";
}

return CFStringRefToStdString(cf_title);
}
Expand All @@ -48,9 +61,28 @@
return "";
}

AXAPINode AXAPINode::createForPID(long pid) {
AXUIElementRef axuielement = AXUIElementCreateApplication((pid_t) pid);
return AXAPINode(axuielement);
long AXAPINode::GetChildCount() {
CFArrayRef children_ref;
if ((AXUIElementCopyAttributeValue(ax_ui_element_, kAXChildrenAttribute,
(CFTypeRef*)&children_ref)) !=
kAXErrorSuccess) {
return 0;
}
return CFArrayGetCount(children_ref);
}

AXAPINodePtr AXAPINode::GetChildAt(long index) {
CFArrayRef children_ref;
if ((AXUIElementCopyAttributeValue(ax_ui_element_, kAXChildrenAttribute,
(CFTypeRef*)&children_ref)) !=
kAXErrorSuccess) {
return nullptr;
}
AXUIElementRef child_ref =
(AXUIElementRef)CFArrayGetValueAtIndex(children_ref, index);
if (!child_ref)
return nullptr;
return AXAPINodePtr(new AXAPINode(child_ref));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I think all of these helper functions, ultimately, might only be implemented in AxapiNodeImpl, like the AtspiNodeImpl: https://github.com/Igalia/AXAccess/pull/43/files#diff-3d31a27d5b501604330a8b1dd58434d1a6848428ec6b28afdefc85002e1e439b

And ultimately a goal for the test suite is to surface AXUIElementCopyAttributeValue almost directly, in order to test?

I wonder if the next step is to for you to copy the outline presented in Edu's PR?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I wonder if "GetChildCount" and "GetChildAt" are the right abstractions for the "cross platform" part of the API.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[...] And ultimately a goal for the test suite is to surface AXUIElementCopyAttributeValue almost directly, in order to test?

Yeah, makes sense!

I wonder if the next step is to for you to copy the outline presented in Edu's PR?

Yeah, once that change lands I can work on making the Mac version of NodeImpl, moving these functions over there and making this a much thinner wrapper around the Mac APIs.

Also I wonder if "GetChildCount" and "GetChildAt" are the right abstractions for the "cross platform" part of the API.

Yeah, good question. Do you think potentially returning a std::vector<NodePtr> might be more ergonomic?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just seems to make more sense from a scripting language perspective. We can change it at a future time!

}

} // namespace mac_inspect
4 changes: 2 additions & 2 deletions lib/mac/mac_inspect.i
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
%module mac_inspect

%{
#include <axaccess/mac/axapi_node.h>
%}


%include "std_string.i"
%include <std_unique_ptr.i>
%unique_ptr(AXAPINode)
%unique_ptr(mac_inspect::AXAPINode)

%include <axaccess/mac/axapi_node.h>