Set Variable to Selection Without Involving Clipboard

Scriptability isn’t necessarily a limiting factor, as System Events can get the attributes of ui elements for the application process.

Most selectable text will be in a ui element of class text area or text field, or something of that ilk. These objects typically have an attribute called AXSelectedText, the value of which will yield what @Lantro is after.

Interesting. Do you have an example script that uses this method?

I too would be very interested to see an example script using the method @CJK describes, but in the meantime, it's possible to achieve this goal using without too much trouble using AppleScript and an Automator service. Create a service like this in Automator:

5

using an AppleScript like this:

on run {input}
	
	tell application "Keyboard Maestro Engine"
		setvariable "Selection" to input
	end tell
	
end run

And run it from KM like this:

02 PM

(The |Chrome part is because Chrome doesn't seem to respond properly to the APPLICATION token, so we have to specify it manually)

This will save the currently selected text to a variable called "Selection" in KM, all without going through the clipboard. (That said, in my experience it's easier and faster to just go through the clipboard and delete the most recent clipboard entry once you have what you need from it.)

1 Like
use sys : application "System Events"
use scripting additions

property initialRun : true


set _P to a reference to process "Spotlight"
set _M to a reference to menu bar item "Spotlight" of menu bar 1 of _P
set _W to a reference to window "Spotlight" of _P
set _T to a reference to text field 1 of _W


get [¬
	"After dismissing this alert, Spotlight will appear. ", ¬
	"Please select some text within its search bar. ", ¬
	"You have about 5 seconds to do this from the time ", ¬
	"Spotlight appears.", linefeed, linefeed, ¬
	"Press OK when ready."]
if initialRun then display alert (the result as text)
set initialRun to false

-- Raise Spotlight
-- click _M --> does not work
-- Change the following line as needed to match the key combo used to trigger Spotlight:
tell sys to keystroke space using {option down, command down}

repeat 15 times -- 3 seconds maximum
	if _T exists then ¬
		exit repeat
	delay 0.2
end repeat

if _W exists then if _T exists then tell _T
	set its value to "Select some of this text in Spotlight."
	
	delay 5
	
	set t to "You selected the following text:"
	set selectedText to the value of its attribute "AXSelectedText"
	
	if the selectedText is in {"", missing value} then ¬
            set t to "No text was selected."
	
	return display notification the selectedText with title t
end tell


"Spotlight did not appear.  Sorry."
Summary

An example AppleScript to demonstrate the retrieval of the AXSelectedText attribute in order to store a text selection in a variable.

3 Likes

Thanks for sharing that. Very interesting.
The challenge, of course, if figuring out the UI elements that contain the selection. Any guidelines for this?

1 Like

I created a sort of clipboard side save routine for my clipboard stacks, so I just meant that I might submit a request for a combined routine so that it’s available as an action, sort of like making a subroutine in AppleScript. I realize that there are caveats to storing the clipboard contents into a named clipboard (mainly that you won’t always get all the clip flavors), so you probably had that fact in mind when you replied.

This is true in theory. And again, in specific cases, you can probably get it to work.

However I looked into this (a lot) in the v8 development cycle and I could not make it work in any general sense. While given a specific element, you may well be able to get the text out of it, there are few applications that properly support that together with an ability to actually find the focussed element.

So if you know the specific application, and you know the specific window, and you know the specific element, then you have a half decent change of getting the selection from it using the Accessibility API, but in the general case of “give me the text of the current selection”, unfortunately I do not believe there is a good general solution to that that actually works in practice.

3 Likes

Actually, the easiest method is to just execute a Delete Current Clipboard action (KM Wiki) after you have done a COPY, and have finished with using the Clipboard:

image

In the KM Editor Edit > Actions, it shows up as "Delete Current Clipboard", although the Action is really "Delete Past Clipboard", where the Past Clipboard # is 0.

I use this all the time, and frankly, it is much easier to use this than trying to fuss with a script to read a selection.

1 Like

Yes, that solves my issue. Thanks!

Here's the code I've been working on so far to capture the selected text in a generalised situation. Notes follow below.

use sys : application "System Events"

-- Use the following delay to choose an application window
-- and highlight some text.  Then ensure that the window remains
-- in focus until the script terminates.
delay 5

set P to the first process whose frontmost is true
set _W to a reference to the first window of P

set _U to a reference to ¬
	(UI elements of P whose ¬
		name of attributes contains "AXSelectedText" and ¬
		value of attribute "AXSelectedText" is not "" and ¬
		class of value of attribute "AXSelectedText" is not class)

tell sys to if (count _U) ≠ 0 then ¬
	return the value of ¬
		attribute "AXSelectedText" of ¬
		_U's contents's first item

set _U to a reference to UI elements of _W

tell sys to repeat while (_U exists)
	tell (a reference to ¬
		(_U whose ¬
			name of attributes contains "AXSelectedText" and ¬
			value of attribute "AXSelectedText" is not "" and ¬
			class of value of attribute "AXSelectedText" is not class)) ¬
		to if (count) ≠ 0 then return the value of ¬
		attribute "AXSelectedText" of its contents's first item
	
	set _U to a reference to (UI elements of _U)
end repeat

"No selection found."

System info: AppleScript version: "2.7", system version: "10.13.3"

Notes

The script remains a work in progress, so don't expect a robust piece of deployment-standard code. I apologise that it's not the most readable script in its present format.

There's a 5-second delay at the start of the script to allow a user to select an application window, highlight some text, and then allow the script to run to completion.

It hunts for selected text in the front window of the frontmost application, as I thought this was a fairly reasonable condition under which real-life situations would fall.

It returns the selected text (if found), or a message stating that no selected text was found. It is a simple tweak should you want the actual UI element that contains the selected text to be returned instead (which might be useful in situations where text would be inserted to replace that which was highlighted).

Limitations

  1. The first limitation is obvious: as my script relies on referencing the front window of a frontmost application for the root of its UI-tree traversal, it currently cannot identify applications whose processes don't register themselves as having focus, such as some menu bar applications that remain background only (iTerm, SnippetsLab, Alfred, Spotlight, ...). However, if the variable P is set to one of these specific processes, it successfully obtains any highlighted text. Therefore, a workaround to this problem is probably very achievable.

  2. It also won't traverse menu bar elements. My initial script did do this, and was able to return text highlighted in the search box of the Help menu. However, the time cost of searching the menus weighed against the probability that selected text would be found there made its sacrifice seem reasonable.

  3. It can't traverse the desktop, should a user decide to select a file and edit the filename, highlighting the text in the editing field that appears. The desktop falls under Finder's process, and lacks a window reference under System Events (although it has one under Finder's application inheritance tree). [FIXED]

  4. Selecting text on a Safari webpage (and, it seems, Chrome as well, although I work mostly with Safari) goes unnoticed. Upon inspection, it seems that the UI elements that make up the AXWebArea of a Safari document all hold their own, unique set of attributes, tailored to the HTML DOM. This means all the static text and text area objects have no AXSelectedText attribute. A solution to this is to do JavaScript in the front document: document.getSelection().toString(); However, the rest of Safari's UI is compliant, so highlighting text in the search bar, for instance, will be detected.

Feel free to feedback any other issues that crop up, together with how to reproduce them. Or, better yet, feel free to improve the code should anyone feel so inclined. I'm not pledging myself to get this script working seamlessly; this was more just to highlight the possibilities, and I think the more realistic solutions to the OP's original dilemma have been provided. But it's an interesting educational exercise.

Reading over these issues, #1-3 are actually a subset of the same problem, which stems from seeking out an active application window to traverse. So, a three-bird-one-stone fix might very well pop into my head next week.

3 Likes

Hey @CJK,

I just got around to testing this.

Well done!

Ditch the delay though, it makes the script seem dead slow.

Allow the user to select their text and then activate the macro. When done this way it’s fast enough to be useful in many situations.

I have systems in place to bypass the Clipboard in many situations, and I’m very happy to add this to the toolbox.

Although the script doesn’t work on a general web page in Safari, interestingly enough it does work with text in the Discourse editor on this Forum.

-Chris

Hi, I am a newbie with AppleScript, just wondering how to use it in plain AppleScript to set the selected text to a useable variable to paste in different app (e.g. Chrome to Word)

Please it would be kind of you to explain how the script works or some pointers in the direction.

Thank you.

Hey @lucky,

As a newbie (which I'm certainly not) I wouldn't want to tackle @CJK's script, unless I had a really good reason.

I haven't fooled with it myself for a while, and I'll have to refamiliarize myself with it before commenting more.

In general you're better off working within the capabilities of given applications.

With Microsoft Word and Google Chrome you can do something like this:

Copy Selected Text in Google Chrome and Emplace in Word Without Pasting v1.00.kmmacros (7.8 KB)

-Chris

Hi Chris
This works a treat with word. I really wanted to use CJK's script to get the selected text in chrome/ any app, then use it in other apps s/a word or a different tab of chrome( tab selection based on url)
I believe, now I need to read about handlers :slight_smile:

I think magic of emplace happens with handler 2 " on setWordSelectionToText(theText, activateBool)"
If I had a different app, I need to "tell" that particular app to use variable "thetext"
I cant paste it (not involving clipboard), but it wud depend on that particular app's dictionary
is that correct?
I was hoping for a generic solution if I cud
Thank you again, U are kind

As you can see, you have to go to a lot of trouble to avoid using the Clipboard, and even then it may not work for all apps.

Why do you want to avoid using the Clipboard?

If you don't want the data to remain on the clipboard after pasting, it is very simple to remove/delete it using the KM Delete Past Clipboard action, where you use "0" for the past clipboard number. This refers to the current System Clipboard.

image

Hi, i wud copy a selection in Browser, and paste it DropBox paper, sometimes in word, then I wud copy the url & paste it.
My Idea was to set selection to variable which cud be inserted, and I wud copy the url to clipboard-> paste it
It wud save me change tabs x2
I was hoping to do with applescript as cud be used on any Mac
Thanks for helping Cheers

One method would be to make use of past Clipboards - which Keyboard Maestro can easily do. So, copy the two items you want then Paste in the Current Clipboard and then the Clipboard before that.
https://wiki.keyboardmaestro.com/action/Set_Clipboard_to_Past_Clipboard

Unfortunately not all apps support AppleScript, and apps that do support it have their own way of doing things.

Certain things are standardized, but only if Apple's standard AppleScript dictionary is included with the app – and devs can override things in that.

There is no such thing, although in my opinion there should be.

You can copy more than one item and then manipulate the past/present clipboards as @JMichaelTX and @Zabobon have suggested.

You can copy to the clipboard and then save that clipboard to a variable or to a saved clipboard – then you can manipulate those.

Or you could do something like this (with Google Chrome):

Chrome Selection + URL to Variables then Clipboard.kmmacros (3.3 KB)

-Chris

Hi Chris,
Thanks for updated Macro, I have managed to add Chrome title to it as well
Just out of interest, the macro Feb12
had "window.getSelection()+'';"
where as the the most recent one has window.getSelection().toString()
The output for both is similar to naked eye, are they different ?
Also do we have a similar javascript to paste a variable in chrome
(as get selection from window.getSelection().toString() in Tab1, then emplace it in Tab2
I have used this as textexpander snippet, and works brilliantly as a work around
Thank you for ur help

Hey @lucky,

The code is different – the result should be the same.

Using +'' is sort of a hackish way to turn an object to a string in JavaScript, whereas .toString() is now canonical.

(I'm not sure when .toString() became available and only noticed it sometime in the last year.)

Google Chrome's AppleScript dictionary does support:

paste selection reference

The reference being the tab you want to paste into.

This can be handy, but it's limited to the active selection in that tab.

-Chris