Just wondering if I can convert a "watch me do" event in Automator to applescript and then edit the resulting code? I've got a recording of entering a query (i.e. Apple1) into Google, but I'd like the query to increase ++ for each loop of the recording, so the result is Apple1, then the next loop would be Apple2, Apple3, etc. I know increments are elementary in programming, but I'm just not sure how to do it using Automator and/or applescript. Any help would be greatly appreciated.
A few years late but yes you can.
How to convert a list of Watch Me Do events to AppleScript:
1. Record the Watch Me Do actions
2. In the Watch Me Do window select all of the events you want to edit
3. Drag those events to a blank position below the Watch Me Do window (where you would place another Automator action)
4. Edit!
--------------------------------------------------See Below for Images--------------------------------------------------
NOTE: In Step 3, when dragging the actions to a blank space you will see a green circle icon with a white plus. Sorry I can't find the exact icon but here is one that looks close enough to give you the idea.
STEP 1
STEP 2
STEP 3
STEP 4
It may be simpler just doing it all in Applescript.
Example:
set counter to 0
set theTerm to "apple"
repeat 4 times
set counter to counter + 1
open location "http://www.google.co.uk/#hl=en&output=search&sclient=psy-ab&q=" & theTerm & counter
end repeat
Running this code will have your browser open a new search for apple1 through 4
Update: Response to Comment request:
This example shows you how to search google images and randomise the search using different terms and numbers.
set theTerms to {"apple", "orange", "banana", "pear"}
set termCount to count of theTerms
set searchLoc to "http://images.google.com/search?hl=en&site=&tbm=isch&source=hp&biw=2171&bih=1062&q="
repeat 4 times
set termRandNumber to random number from 1 to termCount
set randomSuffixNumber to random number from 1 to 7000
open location searchLoc & item termRandNumber of theTerms & randomSuffixNumber
end repeat
Related
I'd like to preface that I am very new to Applescript and UI scripting. I am working inside a proprietary CMS and wish to build a script to open a file search box, paste in the filename, select it, and click Finish. I have been successful scripting the UI hierarchy, but have noticed that if I scroll around the page then the Group number changes which breaks my script.
For example, this snip of script works, but if I scroll around the page then the Group number changes from 47 to 48. The entire script is performing several copy/paste functions and this Group change sometimes occurs mid-run, so I can't "set it and forget it".
tell application "System Events
perform action "AXShowMenu" of text field 1 of group 1 of group 1 of group 1 of group 47 of UI element 1 of scroll area 1 of group 1 of group 1 of tab group 1 of splitter group 1 of window "Strapi - Content Manager" of application process "Safari"
end tell
I'd like to target this text field via an UI attribute (and hopefully ignore Group number or possibly condense the UI string), but am not sure exactly what to use. Several fields such as Title and Description are blank. I am including a screenshot of UI Browser for this element. I'm also open to incorporating JavaScript but have zero knowledge so there'd be an obvious learning curve there. I appreciate any and all help!
I've been searching everywhere for around 6 hours to find a way to deselect file items from a previously selected array of items in Finder via AppleScript via Automator with a Quick Action. I've seen deselect all items, I've seen add items to a selection/list for process, but not remove items from a selection...
The goal: I usually have to process a lot of images and get them approved by supervisors before providing them on our servers for use. All of which come in a set of a transparent PNG and an opaque JPG. I only need to upload the JPG files over for approval. So, let's say I have 30 images. 15 of which are JPGs, 15 of which are PNGs. I would love to be able to select all 30 images, and deselect the 15 of which are PNGs (via the script), left with a selection of all the JPGs.
There are other ways to achieve this, but it would be exceptionally helpful if I could just select the images, run the script, and then move them over for upload (website upload). That appears to be the fastest (by hand) way to go about achieving the end result.
What I know: The selection property. Through my research, I've seen the selection property called in a small amount of different ways, but am unsure how to properly manipulate it. I've tried converting the selection to a string and splitting that by detecting a word to split from (like "Macintosh HD") allowing them to be manipulated in a list, and then potentially removing them from a selection...? I'm also very unsure how to just properly manipulate a property as-is...
I've also attempted to just use the given files via input through Automator AppleScript. But alas, that still leads to knowing how to remove/deselect a file from the current selection.
Anybody know how to achieve this?
I appreciate all the time spend and information in advance. Thank you.
There are two approaches you can use for this, one in pure AppleScript, the other using Automator actions.
The pure AppleScript solution is take the selection as a list of files, copy the JPG files to another list, and set the selection to that new list, like so:
set new_selection_list to {}
tell application "Finder"
set selection_list to selection
repeat with this_item in selection_list
if name extension of this_item is "jpg" then
copy this_item to end of new_selection_list
end if
end repeat
set selection to new_selection_list
end tell
Of course, if you're passing these files in through the input variable in automator, you'd use that rather than testing the selection again, but the principle still stands.
Alternately, you can set up your workflow with a Filter Finder Items action, like so:
I'd recommend the second approach. It's likely significantly faster, and takes less programming.
Although not really deselecting items per se, the Finder has a reveal command that will show the specified items. One approach would be to edit the list of selected items, and then reveal the result.
Note that the sorting of selected items varies depending on whether it is from an Automator action (sorted by name) or a Finder tell statement (the order selected), and items mixed from multiple folders (as can happen in an Automator workflow), will result in multiple Finder windows.
The following Automator Quick Action example will remove the first two items from the input items and reveal the rest of the items in the Finder window:
Workflow receives current files or folders in Finder.app
Run AppleScript:
on run {input, parameters}
set newSelection to input
if (count input) > 2 then -- guard for following statement(s)
set newSelection to items 3 thru -1 of input -- or whatever keep/remove criteria
end if
tell application "Finder" to reveal newSelection
return newSelection
end run
I am writing a code to highlight the list of batches or lines of data in my screen based on the current date and time.
This code is in Visual basic, in my screen i will get a list of batches like
11212\1113
3232\14313
I need to highlight the one of the batch based on the current date and time.
I have tried the below code for selecting the batches, but it is highlighting for every batch when it is displayed and once the next batch is displayed in the screen the above highlighted thing will be gone.
Dim i As Integer
For i = 0 To lstBatches.ListCount - 1
MsgBox "i : " & i
lstBatches.Selected(i) = True
Next i
in the above two mentioned batches when the first is displayed it is highlighting but when the second is going to display the first one is not highlighting, i need the two or one batch need to be highlighted and it should not dis-highlight when the other batch is loading based on the current date time.
Thanks in Advance.
Set the MultiSelect property of the listbox (LstBatches) to 1 (Simple) on its properties panel. You currently have it set to 0 (None).
I'm recording an Automator Script to help me scrape an URL that I use to get information from a week at a time. I would like to specific value to be a vaiable.
When I have recorded the script I would like to have the specific values (i.e. 2018-01-02) be able to change into a variable that can be changed within the script (i.e. a day at a time to 2018-01-03). I can not change the date to a variable in Automator. If I copy the script to a text document I can not get it back into Automator.
Is there a way to get this done?
The script as text looks like this:
-- Click the “2018-01-02” button.
delay 2.631525
set timeoutSeconds to 2.000000
set uiScript to "click UI Element \"2018-01-03\” of group 7 of UI Element 1 of scroll area 1 of group 23 of UI Element 1 of scroll area 1 of group 1 of group 1 of tab group 1 of splitter group 1 of window \"Historical Data Feed\" of application process \"Safari\""
my doWithTimeout( uiScript, timeoutSeconds )
in Automator it looks like:
Click the "2018-01-02" button
#Matts gave a fine answer about using variables in Automator. But, having discussed the nature of your objective in more depth, it's clear that knowing how to reference variables in Automator is not at all the issue, and won't help you.
In fact, don't even bother with Automator. It's not going to do what you want it to. The whole endeavour of trying to automate physical interaction with Safari means it's going to involve a fair bit of AppleScripting, and it's the worst kind—GUI (graphical user interface) scripting.
Come out of Automator and use Script Editor. Make sure Script Editor has relevant accessibility permissions in order for GUI scripting to work. You can do this by going to System Preferences > Security & Privacy > Privacy > Accessibility, unlocking the padlock in the bottom left of the window, and then finding Script Editor in the list of apps allowed to control your computer. Make sure it's checked.
Briefly, GUI scripting is pretty much doing just that, in a nutshell: controlling your computer, in a way that allows it to issue mouse clicks and press buttons or menu items as if the user were there physically doing it themselves.
But it's a hell of a nightmare figuring out how to reference objects in the GUI hierarchy of on-screen elements that can be pressed or clicked.
I think your snippet of Automator-manufactured code was misleading you:
click UI Element "2017-01-03” of group 7 of UI Element 1 of scroll area 1 of ¬
group 23 of UI Element 1 of scroll area 1 of group 1 of group 1 of tab group 1 ¬
of splitter group 1 of window "..." of application process "Safari"
I think you saw UI Element "2017-01-03" and deduced that one must be able to access date-specific data by substituting the appropriate date string in for the one that's there currently. Hence, why you asked about using variables in Automator. But it wouldn't have worked. That date string just reports the name of the element (which happens to be something called a static text element) that contained a piece of text with a date in it, but not one that was editable. Therefore, referencing UI Element "2017-01-02" would just throw an error, because no such element exists.
So here's what I've come up with so far. It's by no means a full package: I haven't written the script that will achieve your objective straight out of the box, but I'm definitely providing you with all the necessary components for you to be able to piece together a finished product. Believe me, I did the most grueling part of it, which a painstaking search to obtain all the specific object references for every button you will want to be able to click within the GUI hierarchy.
First, an important note: I didn't use the URL you posted in the comments to do my GUI inspection. The webpage was far too busy and convoluted, which would have made the job much harder. But luckily, they had a link to a page that contains only the widget with which we'll be interacting. But, because the GUI makeup of the page is different (the widget is identical to the one your original link, it's just the rest of the page that's different), none of the code I've written will work with your original link. So you'll have to use the URL that I've provided in the script, which is https://www.dukascopy.com/plugins/fxMarketWatch/detach.php?id=ct23.
Referencing the GUI objects
Declare some property variables, which you can change according to what month and year you want to retrieve the datasets from. You can also specify your instrument, by either the short abbreviation code, or its full description:
property |URL| : "https://www.dukascopy.com/plugins/fxMarketWatch/detach.php?id=ct23"
property Instrument : {|short|:"AAPL.US/USD", |description|:"APPLE INC"}
property |month| : "December"
property |year| : "2017"
This next part is where System Events and the Safari process are employed, which is the root of the GUI scripting commands. I also lumped in with it a reference to all the elements that were common to all ones we'll be interacting with. Otherwise, you'd have to write them out over and over, whereas it's easier to just reference them once as a group of UI elements that form the basis of a tell... block:
tell application "System Events" to ¬
tell application process "Safari" to ¬
tell (window named |URL|) to ¬
tell UI element 1 of scroll area 1 of group 1 of ¬
UI element 1 of scroll area 1 of group 1 of ¬
group 1 of tab group 1 of splitter group 1
.
.
(* The rest of our code goes
inside this tell block *)
.
.
end tell
Note also that the commands are being sent to the window that's named the same as the URL of the webpage. So this script assumes that the webpage has already been loaded up in Safari—it doesn't load it for you (although that wouldn't be difficult to implement).
Also make sure you log in after you load the page. If you don't, clicking the download button brings up the login box, which will interrupt the whole script and throw an error. But, once you're logged in and click the box to "stay logged in", from then on it will proceed to the download options by itself.
The next part is the meat of the endeavour. This is where I define variables, each of which reference a GUI object that you will want to be able to issue clicks and presses to from the script.
These three form the main components for each row of available instruments that you can select:
set Instruments to a reference to groups of group 1 of group 1 of group 3
--> All Instruments (410 of them)
set Instrument_Abbreviations to a reference to static texts of group 1 of group 2 of Instruments
--> The short text code for each instrument
set Instrument_Descriptions to a reference to static texts of group 2 of Instruments
--> The long text for each instrument
This one is the pair of elements for selecting the timezone, local or GMT:
set LOCAL_GMT to a reference to checkboxes of group 1 of group 7 of group 4
This next block of elements took some work to navigate through. These are the ones responsible for selecting the date. Pressing the date button allows a calendar pop-up to appear on-screen, in which you choose the year, month and day. This will also form the core of what will become your program loop, as you use the variables to cycle through the days of a month to retrieve the data files for all of, say, December.
The thing about menus and objects such as this calendar that "pop up" is that they don't physically exist until the moment they are created and appear on our screens. Therefore, there's potential in GUI scripting for errors to get thrown when the script tries to reference an object that will be there at some future point, but is not currently there and doesn't exist yet. Using a reference to is one way to cater for this problem, and I've done this for most of these variable declarations.
set Date_button to a reference to button 1 of group 3 of group 4
--> The date button
set Calendar_year_button to a reference to button 2 of UI element 2 of row 1 of table 1 of group 5
--> The button that pops up the year menu (as shown)
set Calendar_year_list_items to a reference to static texts of groups of group 1 of group 1 of UI element 1 of row 1 of table 1 of group 5
--> The year menu
set Calendar_month_button to a reference to button 2 of UI element 1 of row 1 of table 1 of group 5
--> The button that pops up the month menu
set Calendar_month_list_items to a reference to static texts of groups of group 1 of group 1 of UI element 1 of row 1 of table 1 of group 5
--> The month menu
set Calendar_days to a reference to static texts of UI elements 2 thru -1 of rows 3 thru -2 of table 1 of group 5
--> All the day text objects visible at any one time
--> 6 rows x 7 days = 42 text objects
The next three variables are for clicking the "Tick" button, which brings up a pop-up menu. Last in this group are the pair of BID/ASK buttons.
set Tick_button to a reference to static text 1 of group 1 of group 1 of list 2 of group 1 of group 4
--> The tick button
set Tick_menu_items to a reference to menu items of list 1
--> The pop-up menu items as shown below
set BID_ASK to a reference to checkboxes of group 1 of group 2 of group 4
--> The "BID" and "ASK" buttons
Next is the download button:
set Download_button to a reference to button "Download" of group 8 of group 4
Now, when the download button is clicked, the GUI goes under a reconstruction in this bottom section. Firstly, a progress bar is shown in place of all the buttons that temporarily vanish:
I ignored these elements as I didn't see any need to have access to the cancel button in such a short space of time.
Then another reconstruction and the "Save As CVS" and "Reset" buttons come into existence. We need both:
set Reset_button to a reference to button "Reset" of group 2 of group 4
set Save_button to a reference to button "Save as .csv" of group 2 of group 2 of group 4
And now we have all the GUI objects we need access to, stored by reference in a set of variables that we (by which I mean you) can make use of to carry out the actual process of automation.
Performing Some Actions
Some of the variables contain references to elements that can perform actions straight out of the box, e.g. click Date_button. However, others will throw an error or not perform quite as you expect them to. This is because some of the variables contain a reference to a single object, such as Date_button, whilst others contains references to several different objects or groups of objects, in a nested list, such as Calendar_year_list_items.
Before utilising these nested lists, we have to flatten them into a single list. The following function will do this, and can be added at the very end of the script (after the end tell:
on flattened(L) -- recurse nested lists and return a one-dimensional list
if class of L is not list then
return [L]
else if length of L is 0 then
return L
else
return flattened(the first item of L) & ¬
flattened(the rest of L)
end if
end flattened
Now we have everything we need to start getting these objects to perform some actions. For this, I think it's best to write another function. It can just before the on flattened function declaration:
on Download_CSV_Data for _instrument from _date
local _instrument, _date
set [_day, _month, _year] to [day, month, year] of date _date
global Instruments, Instrument_Abbreviations, Instrument_Descriptions, LOCAL_GMT, ¬
Date_button, Calendar_year_button, Calendar_year_list_items, Calendar_month_button, ¬
Calendar_month_list_items, Calendar_days, Tick_button, Tick_menu_items, ¬
BID_ASK, Download_button, Reset_button, Save_button
tell application "System Events"
click 1st item of my flattened(Instrument_Abbreviations whose name is _instrument)
-- click 1st item of my flattened(Instrument_Descriptions whose name is Instrument's |description|)
click Tick_button
click (Tick_menu_items whose name is "Second")
click (BID_ASK whose name is "ASK")
click (LOCAL_GMT whose name is "GMT")
click Date_button
click Calendar_year_button
click 1st item of my flattened(Calendar_year_list_items whose name is _year)
click Calendar_month_button
click 1st item of my flattened(Calendar_month_list_items whose name is (_month as text))
click 1st item of my flattened(Calendar_days whose name is _day)
click Download_button
repeat while not (exists Reset_button)
delay 1
end repeat
click Save_button
click Reset_button
end tell
end Download_CSV_Data
In order to download a data file pertaining to a specific date, all you have to do is call that function. You would place that function call between the first end tell statement (after the variable declarations), ad before the first function declaration, so your script will look something like this:
property |URL| : "https://www.dukascopy.com/plugins/fxMarketWatch/detach.php?id=ct23"
property Instrument : {|short|:"AAPL.US/USD", |description|:"APPLE INC"}
property |month| : "December"
property |year| : "2017"
-- GUI object variable declarations
tell application "System Events" to ¬
tell application process "Safari" to ¬
tell (window named |URL|) to ¬
tell UI element 1 of scroll area 1 of group 1 of ¬
UI element 1 of scroll area 1 of group 1 of ¬
group 1 of tab group 1 of splitter group 1
.
.
.
end tell
(*** Insert function calls here ***)
Download_CSV_Data for Instrument's |short| from "17/12/2017"
on Download_CSV_Data for _instrument from _date
.
.
.
end Download_CSV_Data
on flattened(L) -- recurse nested lists and return a one-dimensional list
.
.
.
end flattened
If you're in America, you might have you switch the month and day around in the short date. So where I've written Download_CSV_Data for Instrument's |short| from "17/12/2017", you might have to write Download_CSV_Data for Instrument's |short| from "12/17/2017" (use whatever short-date format your computer system uses).
You can set variables in Automator, by using the "run applescript" command. Search for the command by typing "applescript" into the search box in automator. Then you can set the variable within the script. Here's an example of how it could be done:
set dateVar to "2018-01-02"
-- Click the “2018-01-02” button.
delay 2.631525
set timeoutSeconds to 2.0
set uiScript to "click UI Element \"" & dateVar & "\" of group 7 of UI Element 1 of scroll area 1 of group 23 of UI Element 1 of scroll area 1 of group 1 of group 1 of tab group 1 of splitter group 1 of window \"Historical Data Feed\" of application process \"Safari\""
my doWithTimeout(uiScript, timeoutSeconds)
You could also use other methods of setting the date. This article has a lot of information about using dates with Applescript https://macscripter.net/viewtopic.php?id=24737
The UI element numbers can also vary, so you would need to account for this to get your script to work with variables.
Edit: As you're getting assistance to use another method, I can see how my answer wouldn't be relevant to your situation now. Although Applescript will get the job done, it's quite a difficult way to accomplish the end goal of downloading the files. I've found that using Selenium, Firefox and Python to be a far easier way to automate the browser to download files. It works best when you understand how to use xpath, and occasionally execute some javascript, but if it's something you're doing more of in the future, you might want to consider this method. You can also use tag, id or class selectors instead of xpath.
I'm trying to figure out how to get the current space # from mission control. Source would be helpful, but more helpful would be info on how to figure this out myself. I've written a few applescripts, but more often than not it seems like any time I need to do something new (that I can't find dictionary documentation for) it falls under the category of "tell this specific app (e.g. "System Events") this very specific thing" and I've no clue how I would actually figure that out.
Specifically what I am trying to do:
I hate the new mission control in OSX 10.7. I want my spaces "grid" back since I used it all the time. I used to navigate between spaces using arrow keys (e.g. ALT+↑) every few seconds. Now I'm stuck with this clunky 1x9 array of spaces instead of an elegant 3x3 grid. I've re-mapped all my spaces to use the number pad, which partially takes care of the problem (since it is a 3x3 grid), but only when I have an external keyboard attached.
Basically, I want to be able to use ALT+↑ and ↓ again, but to do so I need to detect the current space # so that I can switch from space 5-->2, for example.
Dave's answer below, although far more detailed than I expected, requires writing an app to do this (plus it still doesn't fully answer the question). If it's at all possible, I'd rather just bind a few keys to an applescript.
I'm trying to figure this out myself. Not there yet, but in the right direction:
Each Mission Control "space" gets a uuid assigned to it...
...except for the very first one (AFAIK), and the Dashboard one.
You can read them here:
$ defaults read com.apple.spaces
$ defaults read com.apple.desktop
File locations:
~/Library/Preferences/com.apple.spaces.plist
~/Library/Preferences/com.apple.desktop.plist
Here's mine. I have four spaces enabled, and three entries show up:
$ defaults read com.apple.spaces
{
spaces = (
{
type = 0;
uuid = "9F552977-3DB0-43E5-8753-E45AC4C61973";
},
{
type = 0;
uuid = "44C8072A-7DC9-4E83-94DD-BDEAF333C924";
},
{
type = 0;
uuid = "6FADBDFE-4CE8-4FC9-B535-40D7CC3C4C58";
}
);
}
If you delete a space, that entry will get removed from the file. If you add a space, an entry will be added. Again, there's never an entry for Desktop 1 or Dashboard.
I'm not sure if there's a public API to figure out what space uuid is being displayed on a display. I'd assume that no uuid means Display 1, and the others' mean Display 1+n.
I took a quick glance through the AppleScript Editor Library (Window ---> Library) and didn't see any entries under System Events for spaces. This is probably something that can be done with Cocoa, perhaps via private API, but I'm not sure about AppleScript.
UPDATE - July 23, 2011
It looks like Dock controls Mission Control. You can grab its header files like so:
Go to: /System/Library/CoreServices/Dock
Right-Click and Show Package Contents
Navigate: /Contents/MacOS/
Copy and paste the Dock binary to your desktop.
Run: $class-dump ~/Desktop/Dock
That'll spit out all of its header files (it's long; almost 7,500 lines). You can see the spaceUUID strings appearing in there. There's a class called WVSpace which appears to represent a single Space in Mission Control, and a lot of other WV* classes.
I'll keep looking at it tomorrow; too tired now. :)
UPDATE - July 24, 2011
Inside Dock there's a class called WVSpaces. It has a number of attributes including:
WVSpace *currentSpace;
unsigned int currentWorkspace;
WVSpace *nextSpace; // Space on the right???
WVSpace *previousSpace; // Space on the left???
BOOL currentSpaceIsDashboard;
BOOL dashboardIsCurrent;
...lots more...
Each WVSpace class has an NSString *_uuid; attribute, which is likely its SpaceUUID. So theoretically you can get the current space number like so:
WVSpace *currentSpace = [[WVSpaces sharedInstance] currentSpace];
NSString *currentSpaceUUID = [currentSpace _uuid]; // Empty string if main space???
The trick is, how to get access to the private WVSpaces class buried inside of Dock? I'm assuming it's Singleton as it has an NSMutableArray *_spaces; attribute, probably with every space listed in it. Only one space gets displayed at a time (this holds true if you're using multiple monitors; the space spans across both of them), so it makes sense to only have one WVSpaces instance.
So it looks like it'll require some SIMBL hacking of Dock to gain access to WVSpaces.
I've been poking around, and I came up with this: https://gist.github.com/1129406
Spaces have a nonsequential ID and a sequential index (0-based). You can get the ID in two ways:
from public APIs (see get_space_id)
from the private CGS API CGSGetWorkspace
You can set the current space by index using public APIs (though the notifications themselves are not publicly documented): see set_space_by_index
You can set the current space by ID using private the CGS API CGSSetWorkspace.
You cannot get the current space index directly. However, if you're always using the same set of nine spaces, you can rotate through them once using set_space_by_index, collect their IDs, and build a mapping. Then you will be able to get the current index from the ID.
... also been working on this :)
You say that you "need to to detect the current space #". This is not strictly true: To move down one row, you just move 3 spaces right, so in principle you could just bind something like
tell application "System Events" to tell process "WindowServer"
key code {124, 124, 124} using control down
end tell
to Alt-down (with FastScripts, Alfred or some other fast method that avoids the overhead of Automator). This approach will fail if you ever hit down in the bottom row, of course -- but if you are truly hard-wired, you never do :)
You have to "Enable access for assistive devices" in the Universal Access preference pane for the key code approach to work.
Caveat: This doesn't work. When I launch the script above, I nicely jump three spaces. The problem is that afterwards my keyboard goes unresponsive: It seems that only the window manager is receiving events: I can close windows and switch space, but I cannot interact with any applications.
My theory is that this happens when the jump causes the current application to change during the execution of the script -- but I have no idea how to fix this.
A related observation: The Mission Control (i.e. /Applications/Mission Control.app/Contents/MacOS/Mission\ Control) seems to react to some command line arguments:
Mission\ Control: show mission control
Mission\ Control 1: show desktop
Mission\ Control 2: show current application windows
I tried putting in some of the UUID's from defaults read com.apple.spaces, but that didn't do much. So much for fumbling in the dark.
I wrote an app - does it work for you?
Change Space.app
The keys to make it work are control-shift and the arrow keys, although this may be fixable if you are stuck on ALT.
Make sure you have 9 spaces (desktops) set up before you start, and you'll need to change the default ctrl-up and ctrl-down key bindings in System Preferences to something else (in Keyboard -> Keyboard Shortcuts -> Mission Control : Mission Control and Show Desktop).
On the first run it it will cycle through your desktops to enumerate them when you first change space.
Then you should be able to change between desktops like in a 3x3 grid.
There may be a few wrinkles, but it's basically functional, at least for me.
http://switchstep.com/ReSpaceApp
This works, is free (right now) and is awesome.
Just be sure to manually create as many spaces as your layout (in preferences) is expecting.
I'm on Mountain Lion and this seems to work for me.
defaults read com.apple.spaces
Look for "Current Space". You'll notice that running this command with different active spaces doesn't change the current space BUT if you check and uncheck a checkbox button in "System Preferences" and run it again, you'll see it updated.
Hopefully this helps someone else!
EDIT: It's ugly, but I'm using this:
killall Dock && sleep 0.2 && defaults read com.apple.spaces | grep -A1 "Current Space" | tail -1 | awk '{print $NF }' | cut -f1 -d';'
on openNewSpace()
tell application "System Events"
—start mission control
do shell script "/Applications/Mission\\ Control.app/Contents/MacOS/Mission\\ Control"
tell process "Dock"
set countSpaces to count buttons of list 1 of group 1
--new space
click button 1 of group 1
--switch to new space
repeat until (count buttons of list 1 of group 1) = (countSpaces + 1)
end repeat
click button (countSpaces + 1) of list 1 of group 1
end tell
end tell
end openNewSpace
I have come up with a workaround for this for myself in macOS Catalina, though I expect this should work for multiple macOS versions. This solution solves my problems, namely:
The inability to identify which desktop contains which project, because desktops cannot be named. (I usually am splitting time on work on multiple projects at once and each desktop is dedicated to work on a different project [and they all use the same apps])
The inability to programmatically(/easily) determine which desktop I'm on at any one time
The lack of tools to track time spent on each desktop
I solved item 1 quite some time back using Stickies.app. I put the project name in a huge enough font that it's easily legible in the desktop thumbnails in Mission Control and I hide the stickie window behind my Dock, assigned specifically to the corresponding project's desktop. (I also duplicate the desktop name in small superscripted text that pokes out from under the left side of the dock so that I can identify the current desktop outside of mission control.)
I just solved item 2 via applescript just now. In the stickie, I add a tiny, unobtrustive font string that identifies the stickie as the desktop name, e.g. 'dtop'. E.g. "small_superscripted_name LARGE_NAME tiny_dtop_string" or "project1 PROJECT1 dtop". Note, this script assumes that the project name contains no spaces (i.e. it's just one word). You can split on a different charcter/string, if you wish. Here is the applescript that, when run, results in the desktop name:
tell application "System Events"
--obtain the stickie with the desktop name
set dstr to name of first item of (windows of application process "Stickies" of application "System Events" whose name contains "dtop")
--Parse the desktop name from the stickie
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to " "
set dname to first item of (dstr's text items)
set AppleScript's text item delimiters to astid
--Show the result in a dialog window
display dialog "Desktop: " & dname
end tell
And as far as item 3 goes, I have yet to implement it, but I could easily poll via a cron job by calling the script using osascript. However, I may explore the possibility of mapping the desktop keyboard shortcuts I use to trigger the script, say, after a delay like 1 second after a control-right/left-arrow, 10 seconds after F3 or control-up-arrow. (It wouldn't catch window-drags that trigger desktop changes, but that hasn't worked anyway since I started using 2 monitors.)
Once I have that set up, I'll likely output the desktop name and a timestamp to a log so I can track time spent on each desktop.
UPDATE: I did eventually solve item 3 with an Applescript run once a minute in a cron job. I also wrote a perl script to generate a bar plot of both: how much time spent on each project (i.e. desktop) over a period of time (e.g. the past week), and a per day plot showing how much time I spent on what projects each day. Here's an example: