Get browser URL from third application [closed]
I am working on MacOS application and I would like to know if there is a way to get url of active browser from it. Application is done in C++.
I would like to get it without having to use AppleScript.
Is that possible?
Thanks
c++ macos url applescript
closed as too broad by Owen Pauling, RedX, mkaes, EdChum, demonplus Nov 28 '18 at 12:17
Please edit the question to limit it to a specific problem with enough detail to identify an adequate answer. Avoid asking multiple distinct questions at once. See the How to Ask page for help clarifying this question. If this question can be reworded to fit the rules in the help center, please edit the question.
add a comment |
I am working on MacOS application and I would like to know if there is a way to get url of active browser from it. Application is done in C++.
I would like to get it without having to use AppleScript.
Is that possible?
Thanks
c++ macos url applescript
closed as too broad by Owen Pauling, RedX, mkaes, EdChum, demonplus Nov 28 '18 at 12:17
Please edit the question to limit it to a specific problem with enough detail to identify an adequate answer. Avoid asking multiple distinct questions at once. See the How to Ask page for help clarifying this question. If this question can be reworded to fit the rules in the help center, please edit the question.
add a comment |
I am working on MacOS application and I would like to know if there is a way to get url of active browser from it. Application is done in C++.
I would like to get it without having to use AppleScript.
Is that possible?
Thanks
c++ macos url applescript
I am working on MacOS application and I would like to know if there is a way to get url of active browser from it. Application is done in C++.
I would like to get it without having to use AppleScript.
Is that possible?
Thanks
c++ macos url applescript
c++ macos url applescript
asked Nov 28 '18 at 8:26
RuLoViCRuLoViC
244114
244114
closed as too broad by Owen Pauling, RedX, mkaes, EdChum, demonplus Nov 28 '18 at 12:17
Please edit the question to limit it to a specific problem with enough detail to identify an adequate answer. Avoid asking multiple distinct questions at once. See the How to Ask page for help clarifying this question. If this question can be reworded to fit the rules in the help center, please edit the question.
closed as too broad by Owen Pauling, RedX, mkaes, EdChum, demonplus Nov 28 '18 at 12:17
Please edit the question to limit it to a specific problem with enough detail to identify an adequate answer. Avoid asking multiple distinct questions at once. See the How to Ask page for help clarifying this question. If this question can be reworded to fit the rules in the help center, please edit the question.
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
This isn't an easy answer but the good news is "yes, it's possible to do this without having to use AppleScript" and the bad news is "you'll have to use AppleScript to begin with".
Let me elaborate a bit: browser apps typically have an Applescript dictionary (which you can see by using the "Script Editor" application found in your /Applications/Utilities
folder. Here's what the dictionary looks like for Google Chrome:
You'll see I've found the "tab" class and in there you'll see the URL property.
So what you need to do is work up an AppleScript first to fetch the windows for the browser you're targeting. Then, when that's working, you need to convert the AppleScript to the underlying, raw AppleEvents (which is what AppleScripts compile to). Both AppleScripts and AppleEvents can be done in code.
The answer you're really looking for (e.g. "can I use some top secret API or send a URL from my code into some open port on my local machine to query the browser?") doesn't exist, as far as I know. AppleScript and AppleEvents are the longtime ways Apple provides to automate most apps and you'll need to leverage that.
If you decide to go ahead with using AppleScript and possibly converting them to AppleEvents, here's what I would do.
1) Find publicly available AppleScripts to do the work you want.
2) Convert the AppleScript to AppleEvents
3) Code up the AppleEvents.
For 1, here's a script that goes through every window in Safari, which I got from this tutorial:
tell application "Safari"
--Variables
set windowCount to number of windows
set docText to ""
--Repeat for Every Window
repeat with x from 1 to windowCount
set tabCount to number of tabs in window x
--Repeat for Every Tab in Current Window
repeat with y from 1 to tabCount
--Get Tab Name & URL
set tabName to name of tab y of window x
set tabURL to URL of tab y of window x
end repeat
end repeat
end tell
For step 2, I think you can use the Accessory View Pane in Script Editor to see the raw events and results produced.
For step 3, this ancient code that I wrote in C will get the URL for a browser window given a window ID.
OSStatus CreateTheAppleEventForGetURL( OSType appSignature, long windowid, char **retval)
{
OSErr err = noErr;
AEAddressDesc targetAddress;
AEDescList replyDesc = { typeNull, nil };
AEKeyword keyword;
DescType desiredClass;
AEDesc replySingle, theOptionalAttributeDesc, theSeldDesc,theObjSpec,theThirdObjSpec,theSecondObjSpec,theFormDesc,theNullDesc;
AppleEvent theEvent, reply;
AEIdleUPP theIdleProc;
Boolean gotReply = false;
long errNumber=0;
long buffer;
Size actualSize;
char *result = NULL;
theIdleProc = NewAEIdleUPP((AEIdleProcPtr)&TheIdleFunction );
if( NULL != theIdleProc )
{
err = AECreateDesc( typeApplSignature, &appSignature, sizeof( appSignature ), &targetAddress );
if( noErr == err )
{
err = AECreateAppleEvent( 'core', 'getd', &targetAddress, kAutoGenerateReturnID, kAnyTransactionID, &theEvent );
buffer = 0x10000;
err = AECreateDesc('magn', &buffer, 4, &theOptionalAttributeDesc);
if( noErr == err )
{
err = AECreateDesc(typeNull, nil, 0, &theNullDesc);
desiredClass = 'cwin';
buffer = 'ID ';
err = AECreateDesc('enum',&buffer,4,&theFormDesc);
buffer = windowid;
err = AECreateDesc(typeLongInteger,&buffer,4,&theSeldDesc);
err = CreateObjSpecifier(desiredClass,&theNullDesc,'ID ',&theSeldDesc,true,&theThirdObjSpec);
buffer = 'docu';
err = AECreateDesc(typeType,&buffer,4,&theSeldDesc);
desiredClass = 'prop';
err = CreateObjSpecifier(desiredClass,&theThirdObjSpec,'prop',&theSeldDesc,true,&theSecondObjSpec);
buffer = 'prop';
err = AECreateDesc('enum',&buffer,4,&theFormDesc);
err = AECreateDesc(typeNull, nil, 0, &theObjSpec);
buffer = 'pURL';
err = AECreateDesc(typeType,&buffer,4,&theSeldDesc);
err = CreateObjSpecifier(desiredClass,&theSecondObjSpec,'prop',&theSeldDesc,true,&theObjSpec);
err = AEPutAttributeDesc(&theEvent,'csig',&theOptionalAttributeDesc);
err = AEPutParamDesc(&theEvent,keyDirectObject, &theObjSpec);
}
if( noErr == err )
{
err = AESend( &theEvent, &reply, kAEWaitReply + kAENeverInteract, kAENormalPriority, 120, theIdleProc, NULL );
if( noErr == err )
{
gotReply = true;
}
else
{
gotReply = false;
}
err = AEDisposeDesc(&theObjSpec);
err = AEDisposeDesc(&theOptionalAttributeDesc);
err = AEDisposeDesc(&theSeldDesc);
err = AEDisposeDesc(&theSecondObjSpec);
err = AEDisposeDesc(&theThirdObjSpec);
err = AEDisposeDesc(&theFormDesc);
err = AEDisposeDesc(&theNullDesc);
}
}
err = AEGetParamPtr(&reply, keyErrorNumber, typeLongInteger, NULL, &errNumber, sizeof(errNumber), &actualSize);
if(true == gotReply )
{
err = AEGetParamDesc( &reply, keyDirectObject, typeAEList, &replyDesc );
keyword = typeAEList;
err = AEGetNthDesc( &replyDesc, 1, typeUnicodeText, &keyword, &replySingle);
if( noErr == err)
{
OSStatus status;
Size theSize;
UnicodeMapping iUnicodeMapping;
UnicodeToTextInfo theInfo;
UniChar theName[512];
unsigned char crapola[512]; // a.k.a. a pstring
iUnicodeMapping.unicodeEncoding = kTextEncodingUnicodeDefault;
iUnicodeMapping.otherEncoding = kTextEncodingMacRoman;
iUnicodeMapping.mappingVersion = kUnicodeUseLatestMapping;
status = CreateUnicodeToTextInfo(&iUnicodeMapping,&theInfo);
theSize = AEGetDescDataSize(&replySingle);
err = AEGetDescData(&replySingle,&theName,512);
if( noErr == err)
{
err = ConvertFromUnicodeToPString(theInfo,theSize,theName,crapola);
if( noErr == err )
{
result = malloc( theSize * sizeof( char ));
if( NULL != result )
{
p2cstrcpy(result,crapola);
printf( "URL returned is %sn", result);
}
}
}
status = DisposeUnicodeToTextInfo(&theInfo);
}
}
err = AEDisposeDesc( &targetAddress );
err = AEDisposeDesc( &replySingle );
DisposeAEIdleUPP( theIdleProc );
}
if( NULL != retval )
*retval = result;
return(err);
}
I'm sure it won't compile, since a number of Carbon API's have been updated and renamed since macOS 10.8, but you get the idea.
Hopefully this long essay helps you out!
Nice! Really well explained answer! I will dig into it. Thanks
– RuLoViC
Nov 28 '18 at 9:48
The reason I responded so quickly was because I've done this same kind of work on two separate projects (but this was more than 10 years ago) and it can be incredibly time consuming and frustrating, so hopefully my efforts will help set you on the right path to success. I'll be thrilled (and upvote) if somebody does come and post a better answer than me, in case there actually is a more optimal solution for you.
– Michael Dautermann
Nov 28 '18 at 9:51
Dauteman: in ay case, your solution needs to have accessibility or automation enabled for your app, right? otherwise it won't work. Am I right?
– RuLoViC
Nov 29 '18 at 11:19
1
No, apps will respond to AppleScript or Apple events regardless of accessibility settings. If the browser is sandboxed, that might be a different story, but I haven’t tested that scenario.
– Michael Dautermann
Nov 29 '18 at 11:21
add a comment |
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
This isn't an easy answer but the good news is "yes, it's possible to do this without having to use AppleScript" and the bad news is "you'll have to use AppleScript to begin with".
Let me elaborate a bit: browser apps typically have an Applescript dictionary (which you can see by using the "Script Editor" application found in your /Applications/Utilities
folder. Here's what the dictionary looks like for Google Chrome:
You'll see I've found the "tab" class and in there you'll see the URL property.
So what you need to do is work up an AppleScript first to fetch the windows for the browser you're targeting. Then, when that's working, you need to convert the AppleScript to the underlying, raw AppleEvents (which is what AppleScripts compile to). Both AppleScripts and AppleEvents can be done in code.
The answer you're really looking for (e.g. "can I use some top secret API or send a URL from my code into some open port on my local machine to query the browser?") doesn't exist, as far as I know. AppleScript and AppleEvents are the longtime ways Apple provides to automate most apps and you'll need to leverage that.
If you decide to go ahead with using AppleScript and possibly converting them to AppleEvents, here's what I would do.
1) Find publicly available AppleScripts to do the work you want.
2) Convert the AppleScript to AppleEvents
3) Code up the AppleEvents.
For 1, here's a script that goes through every window in Safari, which I got from this tutorial:
tell application "Safari"
--Variables
set windowCount to number of windows
set docText to ""
--Repeat for Every Window
repeat with x from 1 to windowCount
set tabCount to number of tabs in window x
--Repeat for Every Tab in Current Window
repeat with y from 1 to tabCount
--Get Tab Name & URL
set tabName to name of tab y of window x
set tabURL to URL of tab y of window x
end repeat
end repeat
end tell
For step 2, I think you can use the Accessory View Pane in Script Editor to see the raw events and results produced.
For step 3, this ancient code that I wrote in C will get the URL for a browser window given a window ID.
OSStatus CreateTheAppleEventForGetURL( OSType appSignature, long windowid, char **retval)
{
OSErr err = noErr;
AEAddressDesc targetAddress;
AEDescList replyDesc = { typeNull, nil };
AEKeyword keyword;
DescType desiredClass;
AEDesc replySingle, theOptionalAttributeDesc, theSeldDesc,theObjSpec,theThirdObjSpec,theSecondObjSpec,theFormDesc,theNullDesc;
AppleEvent theEvent, reply;
AEIdleUPP theIdleProc;
Boolean gotReply = false;
long errNumber=0;
long buffer;
Size actualSize;
char *result = NULL;
theIdleProc = NewAEIdleUPP((AEIdleProcPtr)&TheIdleFunction );
if( NULL != theIdleProc )
{
err = AECreateDesc( typeApplSignature, &appSignature, sizeof( appSignature ), &targetAddress );
if( noErr == err )
{
err = AECreateAppleEvent( 'core', 'getd', &targetAddress, kAutoGenerateReturnID, kAnyTransactionID, &theEvent );
buffer = 0x10000;
err = AECreateDesc('magn', &buffer, 4, &theOptionalAttributeDesc);
if( noErr == err )
{
err = AECreateDesc(typeNull, nil, 0, &theNullDesc);
desiredClass = 'cwin';
buffer = 'ID ';
err = AECreateDesc('enum',&buffer,4,&theFormDesc);
buffer = windowid;
err = AECreateDesc(typeLongInteger,&buffer,4,&theSeldDesc);
err = CreateObjSpecifier(desiredClass,&theNullDesc,'ID ',&theSeldDesc,true,&theThirdObjSpec);
buffer = 'docu';
err = AECreateDesc(typeType,&buffer,4,&theSeldDesc);
desiredClass = 'prop';
err = CreateObjSpecifier(desiredClass,&theThirdObjSpec,'prop',&theSeldDesc,true,&theSecondObjSpec);
buffer = 'prop';
err = AECreateDesc('enum',&buffer,4,&theFormDesc);
err = AECreateDesc(typeNull, nil, 0, &theObjSpec);
buffer = 'pURL';
err = AECreateDesc(typeType,&buffer,4,&theSeldDesc);
err = CreateObjSpecifier(desiredClass,&theSecondObjSpec,'prop',&theSeldDesc,true,&theObjSpec);
err = AEPutAttributeDesc(&theEvent,'csig',&theOptionalAttributeDesc);
err = AEPutParamDesc(&theEvent,keyDirectObject, &theObjSpec);
}
if( noErr == err )
{
err = AESend( &theEvent, &reply, kAEWaitReply + kAENeverInteract, kAENormalPriority, 120, theIdleProc, NULL );
if( noErr == err )
{
gotReply = true;
}
else
{
gotReply = false;
}
err = AEDisposeDesc(&theObjSpec);
err = AEDisposeDesc(&theOptionalAttributeDesc);
err = AEDisposeDesc(&theSeldDesc);
err = AEDisposeDesc(&theSecondObjSpec);
err = AEDisposeDesc(&theThirdObjSpec);
err = AEDisposeDesc(&theFormDesc);
err = AEDisposeDesc(&theNullDesc);
}
}
err = AEGetParamPtr(&reply, keyErrorNumber, typeLongInteger, NULL, &errNumber, sizeof(errNumber), &actualSize);
if(true == gotReply )
{
err = AEGetParamDesc( &reply, keyDirectObject, typeAEList, &replyDesc );
keyword = typeAEList;
err = AEGetNthDesc( &replyDesc, 1, typeUnicodeText, &keyword, &replySingle);
if( noErr == err)
{
OSStatus status;
Size theSize;
UnicodeMapping iUnicodeMapping;
UnicodeToTextInfo theInfo;
UniChar theName[512];
unsigned char crapola[512]; // a.k.a. a pstring
iUnicodeMapping.unicodeEncoding = kTextEncodingUnicodeDefault;
iUnicodeMapping.otherEncoding = kTextEncodingMacRoman;
iUnicodeMapping.mappingVersion = kUnicodeUseLatestMapping;
status = CreateUnicodeToTextInfo(&iUnicodeMapping,&theInfo);
theSize = AEGetDescDataSize(&replySingle);
err = AEGetDescData(&replySingle,&theName,512);
if( noErr == err)
{
err = ConvertFromUnicodeToPString(theInfo,theSize,theName,crapola);
if( noErr == err )
{
result = malloc( theSize * sizeof( char ));
if( NULL != result )
{
p2cstrcpy(result,crapola);
printf( "URL returned is %sn", result);
}
}
}
status = DisposeUnicodeToTextInfo(&theInfo);
}
}
err = AEDisposeDesc( &targetAddress );
err = AEDisposeDesc( &replySingle );
DisposeAEIdleUPP( theIdleProc );
}
if( NULL != retval )
*retval = result;
return(err);
}
I'm sure it won't compile, since a number of Carbon API's have been updated and renamed since macOS 10.8, but you get the idea.
Hopefully this long essay helps you out!
Nice! Really well explained answer! I will dig into it. Thanks
– RuLoViC
Nov 28 '18 at 9:48
The reason I responded so quickly was because I've done this same kind of work on two separate projects (but this was more than 10 years ago) and it can be incredibly time consuming and frustrating, so hopefully my efforts will help set you on the right path to success. I'll be thrilled (and upvote) if somebody does come and post a better answer than me, in case there actually is a more optimal solution for you.
– Michael Dautermann
Nov 28 '18 at 9:51
Dauteman: in ay case, your solution needs to have accessibility or automation enabled for your app, right? otherwise it won't work. Am I right?
– RuLoViC
Nov 29 '18 at 11:19
1
No, apps will respond to AppleScript or Apple events regardless of accessibility settings. If the browser is sandboxed, that might be a different story, but I haven’t tested that scenario.
– Michael Dautermann
Nov 29 '18 at 11:21
add a comment |
This isn't an easy answer but the good news is "yes, it's possible to do this without having to use AppleScript" and the bad news is "you'll have to use AppleScript to begin with".
Let me elaborate a bit: browser apps typically have an Applescript dictionary (which you can see by using the "Script Editor" application found in your /Applications/Utilities
folder. Here's what the dictionary looks like for Google Chrome:
You'll see I've found the "tab" class and in there you'll see the URL property.
So what you need to do is work up an AppleScript first to fetch the windows for the browser you're targeting. Then, when that's working, you need to convert the AppleScript to the underlying, raw AppleEvents (which is what AppleScripts compile to). Both AppleScripts and AppleEvents can be done in code.
The answer you're really looking for (e.g. "can I use some top secret API or send a URL from my code into some open port on my local machine to query the browser?") doesn't exist, as far as I know. AppleScript and AppleEvents are the longtime ways Apple provides to automate most apps and you'll need to leverage that.
If you decide to go ahead with using AppleScript and possibly converting them to AppleEvents, here's what I would do.
1) Find publicly available AppleScripts to do the work you want.
2) Convert the AppleScript to AppleEvents
3) Code up the AppleEvents.
For 1, here's a script that goes through every window in Safari, which I got from this tutorial:
tell application "Safari"
--Variables
set windowCount to number of windows
set docText to ""
--Repeat for Every Window
repeat with x from 1 to windowCount
set tabCount to number of tabs in window x
--Repeat for Every Tab in Current Window
repeat with y from 1 to tabCount
--Get Tab Name & URL
set tabName to name of tab y of window x
set tabURL to URL of tab y of window x
end repeat
end repeat
end tell
For step 2, I think you can use the Accessory View Pane in Script Editor to see the raw events and results produced.
For step 3, this ancient code that I wrote in C will get the URL for a browser window given a window ID.
OSStatus CreateTheAppleEventForGetURL( OSType appSignature, long windowid, char **retval)
{
OSErr err = noErr;
AEAddressDesc targetAddress;
AEDescList replyDesc = { typeNull, nil };
AEKeyword keyword;
DescType desiredClass;
AEDesc replySingle, theOptionalAttributeDesc, theSeldDesc,theObjSpec,theThirdObjSpec,theSecondObjSpec,theFormDesc,theNullDesc;
AppleEvent theEvent, reply;
AEIdleUPP theIdleProc;
Boolean gotReply = false;
long errNumber=0;
long buffer;
Size actualSize;
char *result = NULL;
theIdleProc = NewAEIdleUPP((AEIdleProcPtr)&TheIdleFunction );
if( NULL != theIdleProc )
{
err = AECreateDesc( typeApplSignature, &appSignature, sizeof( appSignature ), &targetAddress );
if( noErr == err )
{
err = AECreateAppleEvent( 'core', 'getd', &targetAddress, kAutoGenerateReturnID, kAnyTransactionID, &theEvent );
buffer = 0x10000;
err = AECreateDesc('magn', &buffer, 4, &theOptionalAttributeDesc);
if( noErr == err )
{
err = AECreateDesc(typeNull, nil, 0, &theNullDesc);
desiredClass = 'cwin';
buffer = 'ID ';
err = AECreateDesc('enum',&buffer,4,&theFormDesc);
buffer = windowid;
err = AECreateDesc(typeLongInteger,&buffer,4,&theSeldDesc);
err = CreateObjSpecifier(desiredClass,&theNullDesc,'ID ',&theSeldDesc,true,&theThirdObjSpec);
buffer = 'docu';
err = AECreateDesc(typeType,&buffer,4,&theSeldDesc);
desiredClass = 'prop';
err = CreateObjSpecifier(desiredClass,&theThirdObjSpec,'prop',&theSeldDesc,true,&theSecondObjSpec);
buffer = 'prop';
err = AECreateDesc('enum',&buffer,4,&theFormDesc);
err = AECreateDesc(typeNull, nil, 0, &theObjSpec);
buffer = 'pURL';
err = AECreateDesc(typeType,&buffer,4,&theSeldDesc);
err = CreateObjSpecifier(desiredClass,&theSecondObjSpec,'prop',&theSeldDesc,true,&theObjSpec);
err = AEPutAttributeDesc(&theEvent,'csig',&theOptionalAttributeDesc);
err = AEPutParamDesc(&theEvent,keyDirectObject, &theObjSpec);
}
if( noErr == err )
{
err = AESend( &theEvent, &reply, kAEWaitReply + kAENeverInteract, kAENormalPriority, 120, theIdleProc, NULL );
if( noErr == err )
{
gotReply = true;
}
else
{
gotReply = false;
}
err = AEDisposeDesc(&theObjSpec);
err = AEDisposeDesc(&theOptionalAttributeDesc);
err = AEDisposeDesc(&theSeldDesc);
err = AEDisposeDesc(&theSecondObjSpec);
err = AEDisposeDesc(&theThirdObjSpec);
err = AEDisposeDesc(&theFormDesc);
err = AEDisposeDesc(&theNullDesc);
}
}
err = AEGetParamPtr(&reply, keyErrorNumber, typeLongInteger, NULL, &errNumber, sizeof(errNumber), &actualSize);
if(true == gotReply )
{
err = AEGetParamDesc( &reply, keyDirectObject, typeAEList, &replyDesc );
keyword = typeAEList;
err = AEGetNthDesc( &replyDesc, 1, typeUnicodeText, &keyword, &replySingle);
if( noErr == err)
{
OSStatus status;
Size theSize;
UnicodeMapping iUnicodeMapping;
UnicodeToTextInfo theInfo;
UniChar theName[512];
unsigned char crapola[512]; // a.k.a. a pstring
iUnicodeMapping.unicodeEncoding = kTextEncodingUnicodeDefault;
iUnicodeMapping.otherEncoding = kTextEncodingMacRoman;
iUnicodeMapping.mappingVersion = kUnicodeUseLatestMapping;
status = CreateUnicodeToTextInfo(&iUnicodeMapping,&theInfo);
theSize = AEGetDescDataSize(&replySingle);
err = AEGetDescData(&replySingle,&theName,512);
if( noErr == err)
{
err = ConvertFromUnicodeToPString(theInfo,theSize,theName,crapola);
if( noErr == err )
{
result = malloc( theSize * sizeof( char ));
if( NULL != result )
{
p2cstrcpy(result,crapola);
printf( "URL returned is %sn", result);
}
}
}
status = DisposeUnicodeToTextInfo(&theInfo);
}
}
err = AEDisposeDesc( &targetAddress );
err = AEDisposeDesc( &replySingle );
DisposeAEIdleUPP( theIdleProc );
}
if( NULL != retval )
*retval = result;
return(err);
}
I'm sure it won't compile, since a number of Carbon API's have been updated and renamed since macOS 10.8, but you get the idea.
Hopefully this long essay helps you out!
Nice! Really well explained answer! I will dig into it. Thanks
– RuLoViC
Nov 28 '18 at 9:48
The reason I responded so quickly was because I've done this same kind of work on two separate projects (but this was more than 10 years ago) and it can be incredibly time consuming and frustrating, so hopefully my efforts will help set you on the right path to success. I'll be thrilled (and upvote) if somebody does come and post a better answer than me, in case there actually is a more optimal solution for you.
– Michael Dautermann
Nov 28 '18 at 9:51
Dauteman: in ay case, your solution needs to have accessibility or automation enabled for your app, right? otherwise it won't work. Am I right?
– RuLoViC
Nov 29 '18 at 11:19
1
No, apps will respond to AppleScript or Apple events regardless of accessibility settings. If the browser is sandboxed, that might be a different story, but I haven’t tested that scenario.
– Michael Dautermann
Nov 29 '18 at 11:21
add a comment |
This isn't an easy answer but the good news is "yes, it's possible to do this without having to use AppleScript" and the bad news is "you'll have to use AppleScript to begin with".
Let me elaborate a bit: browser apps typically have an Applescript dictionary (which you can see by using the "Script Editor" application found in your /Applications/Utilities
folder. Here's what the dictionary looks like for Google Chrome:
You'll see I've found the "tab" class and in there you'll see the URL property.
So what you need to do is work up an AppleScript first to fetch the windows for the browser you're targeting. Then, when that's working, you need to convert the AppleScript to the underlying, raw AppleEvents (which is what AppleScripts compile to). Both AppleScripts and AppleEvents can be done in code.
The answer you're really looking for (e.g. "can I use some top secret API or send a URL from my code into some open port on my local machine to query the browser?") doesn't exist, as far as I know. AppleScript and AppleEvents are the longtime ways Apple provides to automate most apps and you'll need to leverage that.
If you decide to go ahead with using AppleScript and possibly converting them to AppleEvents, here's what I would do.
1) Find publicly available AppleScripts to do the work you want.
2) Convert the AppleScript to AppleEvents
3) Code up the AppleEvents.
For 1, here's a script that goes through every window in Safari, which I got from this tutorial:
tell application "Safari"
--Variables
set windowCount to number of windows
set docText to ""
--Repeat for Every Window
repeat with x from 1 to windowCount
set tabCount to number of tabs in window x
--Repeat for Every Tab in Current Window
repeat with y from 1 to tabCount
--Get Tab Name & URL
set tabName to name of tab y of window x
set tabURL to URL of tab y of window x
end repeat
end repeat
end tell
For step 2, I think you can use the Accessory View Pane in Script Editor to see the raw events and results produced.
For step 3, this ancient code that I wrote in C will get the URL for a browser window given a window ID.
OSStatus CreateTheAppleEventForGetURL( OSType appSignature, long windowid, char **retval)
{
OSErr err = noErr;
AEAddressDesc targetAddress;
AEDescList replyDesc = { typeNull, nil };
AEKeyword keyword;
DescType desiredClass;
AEDesc replySingle, theOptionalAttributeDesc, theSeldDesc,theObjSpec,theThirdObjSpec,theSecondObjSpec,theFormDesc,theNullDesc;
AppleEvent theEvent, reply;
AEIdleUPP theIdleProc;
Boolean gotReply = false;
long errNumber=0;
long buffer;
Size actualSize;
char *result = NULL;
theIdleProc = NewAEIdleUPP((AEIdleProcPtr)&TheIdleFunction );
if( NULL != theIdleProc )
{
err = AECreateDesc( typeApplSignature, &appSignature, sizeof( appSignature ), &targetAddress );
if( noErr == err )
{
err = AECreateAppleEvent( 'core', 'getd', &targetAddress, kAutoGenerateReturnID, kAnyTransactionID, &theEvent );
buffer = 0x10000;
err = AECreateDesc('magn', &buffer, 4, &theOptionalAttributeDesc);
if( noErr == err )
{
err = AECreateDesc(typeNull, nil, 0, &theNullDesc);
desiredClass = 'cwin';
buffer = 'ID ';
err = AECreateDesc('enum',&buffer,4,&theFormDesc);
buffer = windowid;
err = AECreateDesc(typeLongInteger,&buffer,4,&theSeldDesc);
err = CreateObjSpecifier(desiredClass,&theNullDesc,'ID ',&theSeldDesc,true,&theThirdObjSpec);
buffer = 'docu';
err = AECreateDesc(typeType,&buffer,4,&theSeldDesc);
desiredClass = 'prop';
err = CreateObjSpecifier(desiredClass,&theThirdObjSpec,'prop',&theSeldDesc,true,&theSecondObjSpec);
buffer = 'prop';
err = AECreateDesc('enum',&buffer,4,&theFormDesc);
err = AECreateDesc(typeNull, nil, 0, &theObjSpec);
buffer = 'pURL';
err = AECreateDesc(typeType,&buffer,4,&theSeldDesc);
err = CreateObjSpecifier(desiredClass,&theSecondObjSpec,'prop',&theSeldDesc,true,&theObjSpec);
err = AEPutAttributeDesc(&theEvent,'csig',&theOptionalAttributeDesc);
err = AEPutParamDesc(&theEvent,keyDirectObject, &theObjSpec);
}
if( noErr == err )
{
err = AESend( &theEvent, &reply, kAEWaitReply + kAENeverInteract, kAENormalPriority, 120, theIdleProc, NULL );
if( noErr == err )
{
gotReply = true;
}
else
{
gotReply = false;
}
err = AEDisposeDesc(&theObjSpec);
err = AEDisposeDesc(&theOptionalAttributeDesc);
err = AEDisposeDesc(&theSeldDesc);
err = AEDisposeDesc(&theSecondObjSpec);
err = AEDisposeDesc(&theThirdObjSpec);
err = AEDisposeDesc(&theFormDesc);
err = AEDisposeDesc(&theNullDesc);
}
}
err = AEGetParamPtr(&reply, keyErrorNumber, typeLongInteger, NULL, &errNumber, sizeof(errNumber), &actualSize);
if(true == gotReply )
{
err = AEGetParamDesc( &reply, keyDirectObject, typeAEList, &replyDesc );
keyword = typeAEList;
err = AEGetNthDesc( &replyDesc, 1, typeUnicodeText, &keyword, &replySingle);
if( noErr == err)
{
OSStatus status;
Size theSize;
UnicodeMapping iUnicodeMapping;
UnicodeToTextInfo theInfo;
UniChar theName[512];
unsigned char crapola[512]; // a.k.a. a pstring
iUnicodeMapping.unicodeEncoding = kTextEncodingUnicodeDefault;
iUnicodeMapping.otherEncoding = kTextEncodingMacRoman;
iUnicodeMapping.mappingVersion = kUnicodeUseLatestMapping;
status = CreateUnicodeToTextInfo(&iUnicodeMapping,&theInfo);
theSize = AEGetDescDataSize(&replySingle);
err = AEGetDescData(&replySingle,&theName,512);
if( noErr == err)
{
err = ConvertFromUnicodeToPString(theInfo,theSize,theName,crapola);
if( noErr == err )
{
result = malloc( theSize * sizeof( char ));
if( NULL != result )
{
p2cstrcpy(result,crapola);
printf( "URL returned is %sn", result);
}
}
}
status = DisposeUnicodeToTextInfo(&theInfo);
}
}
err = AEDisposeDesc( &targetAddress );
err = AEDisposeDesc( &replySingle );
DisposeAEIdleUPP( theIdleProc );
}
if( NULL != retval )
*retval = result;
return(err);
}
I'm sure it won't compile, since a number of Carbon API's have been updated and renamed since macOS 10.8, but you get the idea.
Hopefully this long essay helps you out!
This isn't an easy answer but the good news is "yes, it's possible to do this without having to use AppleScript" and the bad news is "you'll have to use AppleScript to begin with".
Let me elaborate a bit: browser apps typically have an Applescript dictionary (which you can see by using the "Script Editor" application found in your /Applications/Utilities
folder. Here's what the dictionary looks like for Google Chrome:
You'll see I've found the "tab" class and in there you'll see the URL property.
So what you need to do is work up an AppleScript first to fetch the windows for the browser you're targeting. Then, when that's working, you need to convert the AppleScript to the underlying, raw AppleEvents (which is what AppleScripts compile to). Both AppleScripts and AppleEvents can be done in code.
The answer you're really looking for (e.g. "can I use some top secret API or send a URL from my code into some open port on my local machine to query the browser?") doesn't exist, as far as I know. AppleScript and AppleEvents are the longtime ways Apple provides to automate most apps and you'll need to leverage that.
If you decide to go ahead with using AppleScript and possibly converting them to AppleEvents, here's what I would do.
1) Find publicly available AppleScripts to do the work you want.
2) Convert the AppleScript to AppleEvents
3) Code up the AppleEvents.
For 1, here's a script that goes through every window in Safari, which I got from this tutorial:
tell application "Safari"
--Variables
set windowCount to number of windows
set docText to ""
--Repeat for Every Window
repeat with x from 1 to windowCount
set tabCount to number of tabs in window x
--Repeat for Every Tab in Current Window
repeat with y from 1 to tabCount
--Get Tab Name & URL
set tabName to name of tab y of window x
set tabURL to URL of tab y of window x
end repeat
end repeat
end tell
For step 2, I think you can use the Accessory View Pane in Script Editor to see the raw events and results produced.
For step 3, this ancient code that I wrote in C will get the URL for a browser window given a window ID.
OSStatus CreateTheAppleEventForGetURL( OSType appSignature, long windowid, char **retval)
{
OSErr err = noErr;
AEAddressDesc targetAddress;
AEDescList replyDesc = { typeNull, nil };
AEKeyword keyword;
DescType desiredClass;
AEDesc replySingle, theOptionalAttributeDesc, theSeldDesc,theObjSpec,theThirdObjSpec,theSecondObjSpec,theFormDesc,theNullDesc;
AppleEvent theEvent, reply;
AEIdleUPP theIdleProc;
Boolean gotReply = false;
long errNumber=0;
long buffer;
Size actualSize;
char *result = NULL;
theIdleProc = NewAEIdleUPP((AEIdleProcPtr)&TheIdleFunction );
if( NULL != theIdleProc )
{
err = AECreateDesc( typeApplSignature, &appSignature, sizeof( appSignature ), &targetAddress );
if( noErr == err )
{
err = AECreateAppleEvent( 'core', 'getd', &targetAddress, kAutoGenerateReturnID, kAnyTransactionID, &theEvent );
buffer = 0x10000;
err = AECreateDesc('magn', &buffer, 4, &theOptionalAttributeDesc);
if( noErr == err )
{
err = AECreateDesc(typeNull, nil, 0, &theNullDesc);
desiredClass = 'cwin';
buffer = 'ID ';
err = AECreateDesc('enum',&buffer,4,&theFormDesc);
buffer = windowid;
err = AECreateDesc(typeLongInteger,&buffer,4,&theSeldDesc);
err = CreateObjSpecifier(desiredClass,&theNullDesc,'ID ',&theSeldDesc,true,&theThirdObjSpec);
buffer = 'docu';
err = AECreateDesc(typeType,&buffer,4,&theSeldDesc);
desiredClass = 'prop';
err = CreateObjSpecifier(desiredClass,&theThirdObjSpec,'prop',&theSeldDesc,true,&theSecondObjSpec);
buffer = 'prop';
err = AECreateDesc('enum',&buffer,4,&theFormDesc);
err = AECreateDesc(typeNull, nil, 0, &theObjSpec);
buffer = 'pURL';
err = AECreateDesc(typeType,&buffer,4,&theSeldDesc);
err = CreateObjSpecifier(desiredClass,&theSecondObjSpec,'prop',&theSeldDesc,true,&theObjSpec);
err = AEPutAttributeDesc(&theEvent,'csig',&theOptionalAttributeDesc);
err = AEPutParamDesc(&theEvent,keyDirectObject, &theObjSpec);
}
if( noErr == err )
{
err = AESend( &theEvent, &reply, kAEWaitReply + kAENeverInteract, kAENormalPriority, 120, theIdleProc, NULL );
if( noErr == err )
{
gotReply = true;
}
else
{
gotReply = false;
}
err = AEDisposeDesc(&theObjSpec);
err = AEDisposeDesc(&theOptionalAttributeDesc);
err = AEDisposeDesc(&theSeldDesc);
err = AEDisposeDesc(&theSecondObjSpec);
err = AEDisposeDesc(&theThirdObjSpec);
err = AEDisposeDesc(&theFormDesc);
err = AEDisposeDesc(&theNullDesc);
}
}
err = AEGetParamPtr(&reply, keyErrorNumber, typeLongInteger, NULL, &errNumber, sizeof(errNumber), &actualSize);
if(true == gotReply )
{
err = AEGetParamDesc( &reply, keyDirectObject, typeAEList, &replyDesc );
keyword = typeAEList;
err = AEGetNthDesc( &replyDesc, 1, typeUnicodeText, &keyword, &replySingle);
if( noErr == err)
{
OSStatus status;
Size theSize;
UnicodeMapping iUnicodeMapping;
UnicodeToTextInfo theInfo;
UniChar theName[512];
unsigned char crapola[512]; // a.k.a. a pstring
iUnicodeMapping.unicodeEncoding = kTextEncodingUnicodeDefault;
iUnicodeMapping.otherEncoding = kTextEncodingMacRoman;
iUnicodeMapping.mappingVersion = kUnicodeUseLatestMapping;
status = CreateUnicodeToTextInfo(&iUnicodeMapping,&theInfo);
theSize = AEGetDescDataSize(&replySingle);
err = AEGetDescData(&replySingle,&theName,512);
if( noErr == err)
{
err = ConvertFromUnicodeToPString(theInfo,theSize,theName,crapola);
if( noErr == err )
{
result = malloc( theSize * sizeof( char ));
if( NULL != result )
{
p2cstrcpy(result,crapola);
printf( "URL returned is %sn", result);
}
}
}
status = DisposeUnicodeToTextInfo(&theInfo);
}
}
err = AEDisposeDesc( &targetAddress );
err = AEDisposeDesc( &replySingle );
DisposeAEIdleUPP( theIdleProc );
}
if( NULL != retval )
*retval = result;
return(err);
}
I'm sure it won't compile, since a number of Carbon API's have been updated and renamed since macOS 10.8, but you get the idea.
Hopefully this long essay helps you out!
answered Nov 28 '18 at 9:40
Michael DautermannMichael Dautermann
80.8k15135171
80.8k15135171
Nice! Really well explained answer! I will dig into it. Thanks
– RuLoViC
Nov 28 '18 at 9:48
The reason I responded so quickly was because I've done this same kind of work on two separate projects (but this was more than 10 years ago) and it can be incredibly time consuming and frustrating, so hopefully my efforts will help set you on the right path to success. I'll be thrilled (and upvote) if somebody does come and post a better answer than me, in case there actually is a more optimal solution for you.
– Michael Dautermann
Nov 28 '18 at 9:51
Dauteman: in ay case, your solution needs to have accessibility or automation enabled for your app, right? otherwise it won't work. Am I right?
– RuLoViC
Nov 29 '18 at 11:19
1
No, apps will respond to AppleScript or Apple events regardless of accessibility settings. If the browser is sandboxed, that might be a different story, but I haven’t tested that scenario.
– Michael Dautermann
Nov 29 '18 at 11:21
add a comment |
Nice! Really well explained answer! I will dig into it. Thanks
– RuLoViC
Nov 28 '18 at 9:48
The reason I responded so quickly was because I've done this same kind of work on two separate projects (but this was more than 10 years ago) and it can be incredibly time consuming and frustrating, so hopefully my efforts will help set you on the right path to success. I'll be thrilled (and upvote) if somebody does come and post a better answer than me, in case there actually is a more optimal solution for you.
– Michael Dautermann
Nov 28 '18 at 9:51
Dauteman: in ay case, your solution needs to have accessibility or automation enabled for your app, right? otherwise it won't work. Am I right?
– RuLoViC
Nov 29 '18 at 11:19
1
No, apps will respond to AppleScript or Apple events regardless of accessibility settings. If the browser is sandboxed, that might be a different story, but I haven’t tested that scenario.
– Michael Dautermann
Nov 29 '18 at 11:21
Nice! Really well explained answer! I will dig into it. Thanks
– RuLoViC
Nov 28 '18 at 9:48
Nice! Really well explained answer! I will dig into it. Thanks
– RuLoViC
Nov 28 '18 at 9:48
The reason I responded so quickly was because I've done this same kind of work on two separate projects (but this was more than 10 years ago) and it can be incredibly time consuming and frustrating, so hopefully my efforts will help set you on the right path to success. I'll be thrilled (and upvote) if somebody does come and post a better answer than me, in case there actually is a more optimal solution for you.
– Michael Dautermann
Nov 28 '18 at 9:51
The reason I responded so quickly was because I've done this same kind of work on two separate projects (but this was more than 10 years ago) and it can be incredibly time consuming and frustrating, so hopefully my efforts will help set you on the right path to success. I'll be thrilled (and upvote) if somebody does come and post a better answer than me, in case there actually is a more optimal solution for you.
– Michael Dautermann
Nov 28 '18 at 9:51
Dauteman: in ay case, your solution needs to have accessibility or automation enabled for your app, right? otherwise it won't work. Am I right?
– RuLoViC
Nov 29 '18 at 11:19
Dauteman: in ay case, your solution needs to have accessibility or automation enabled for your app, right? otherwise it won't work. Am I right?
– RuLoViC
Nov 29 '18 at 11:19
1
1
No, apps will respond to AppleScript or Apple events regardless of accessibility settings. If the browser is sandboxed, that might be a different story, but I haven’t tested that scenario.
– Michael Dautermann
Nov 29 '18 at 11:21
No, apps will respond to AppleScript or Apple events regardless of accessibility settings. If the browser is sandboxed, that might be a different story, but I haven’t tested that scenario.
– Michael Dautermann
Nov 29 '18 at 11:21
add a comment |