Spaces Library

I toyed with the idea of making a much improved Spaces menu for turning Spaces on and off, assigning apps to a particular Space, and switching between Spaces but I just don’t have the time to finish it now. However, I did develop a small library of routines (they require Mac OS X 10.5, naturally) that would be helpful in all of the above and, perhaps, someone else can take this and develop the utility themselves… Happy new year!

Jon

--my get_spaces_enabled()
--my set_spaces_enabled(enable_spaces)
--my toggle_spaces_enabled()
--my get_spaces_rows()
--my set_spaces_rows(row_count)
--my get_spaces_columns()
--my set_spaces_columns(column_count)
--my get_spaces_count()
--my show_all_spaces()
--my get_spaces_application_bindings()
--my set_spaces_application_bindings(new_bindings)
--my collect_application_in_current_space(application_bundle_id)
--my set_spaces_application_binding_for_application(application_bundle_id, chosen_space)
--my get_space_binding_for_application(application_bundle_id)
--my choose_space_for_current_application()
--my choose_space_for_application(application_bundle_id)
--my remove_spaces_application_binding(application_bundle_id)
--my get_spaces_arrow_key_modifiers()
--my get_spaces_numbers_key_modifiers()
--my switch_to_space(space_number)
--my open_spaces_preferences()
--my display_spaces_not_enabled_error()

if (my choose_space_for_current_application()) then my show_all_spaces()


-- ****************************************************************************************************************************** --
-- ****************************************************************************************************************************** --


on get_spaces_enabled()
	tell application "System Events" to tell spaces preferences of expose preferences to return (get spaces enabled)
end get_spaces_enabled

on set_spaces_enabled(enable_spaces)
	tell application "System Events" to tell spaces preferences of expose preferences to set spaces enabled to enable_spaces
end set_spaces_enabled

on toggle_spaces_enabled()
	my set_spaces_enabled(not (my get_spaces_enabled()))
end toggle_spaces_enabled

on get_spaces_rows()
	tell application "System Events" to tell spaces preferences of expose preferences to return (get spaces rows)
end get_spaces_rows

on set_spaces_rows(row_count)
	tell application "System Events" to tell spaces preferences of expose preferences to set spaces rows to row_count
end set_spaces_rows

on get_spaces_columns()
	tell application "System Events" to tell spaces preferences of expose preferences to return (get spaces columns)
end get_spaces_columns

on set_spaces_columns(column_count)
	tell application "System Events" to tell spaces preferences of expose preferences to set spaces columns to column_count
end set_spaces_columns

on get_spaces_count()
	return ((my get_spaces_rows()) * (my get_spaces_columns()))
end get_spaces_count

on show_all_spaces()
	try
		tell application "Finder" to set spaces_app_path to (application file id "com.apple.spaceslauncher") as string
		do shell script "open " & quoted form of POSIX path of spaces_app_path
	end try
end show_all_spaces

on get_spaces_application_bindings()
	tell application "System Events" to tell spaces preferences of expose preferences to return (get application bindings)
end get_spaces_application_bindings

on set_spaces_application_bindings(new_bindings)
	tell application "System Events" to tell spaces preferences of expose preferences to set application bindings to new_bindings
end set_spaces_application_bindings

on collect_application_in_current_space(application_bundle_id)
	set application_bundle_id to my make_lowercase(application_bundle_id)
	set app_bindings to my get_spaces_application_bindings()
	my set_spaces_application_bindings((run script "{|" & application_bundle_id & "|:65544}") & app_bindings)
	my set_spaces_application_bindings(app_bindings)
end collect_application_in_current_space

on set_spaces_application_binding_for_application(application_bundle_id, chosen_space)
	set application_bundle_id to my make_lowercase(application_bundle_id)
	if chosen_space is in {0, "None"} then
		my remove_spaces_application_binding(application_bundle_id)
	else
		if chosen_space = "All" then set chosen_space to 65544
		my set_spaces_application_bindings((run script "{|" & application_bundle_id & "|: " & chosen_space & "}") & (my get_spaces_application_bindings()))
	end if
end set_spaces_application_binding_for_application

on get_space_binding_for_application(application_bundle_id)
	set application_bundle_id to my make_lowercase(application_bundle_id)
	set app_bindings to my get_spaces_application_bindings()
	try
		get app_bindings as string
	on error error_string
		set app_bindings to my string_to_list(text 13 thru -20 of error_string, ", ")
	end try
	repeat with i from 1 to (count app_bindings)
		if item i of app_bindings starts with ("|" & application_bundle_id & "|:") then return (item 2 of (my string_to_list(item i of app_bindings, ":"))) as number
	end repeat
	return 0
end get_space_binding_for_application

on choose_space_for_current_application()
	return my choose_space_for_application(bundle identifier of (info for (path to frontmost application)))
end choose_space_for_current_application

on choose_space_for_application(application_bundle_id)
	set application_bundle_id to my make_lowercase(application_bundle_id)
	if (not my get_spaces_enabled()) then if (not my display_spaces_not_enabled_error()) then return false
	try
		tell application "Finder" to set app_path to (application file id application_bundle_id) as string
		set app_name to short name of (info for (app_path as alias))
		set the_choices to {"None", "All"}
		repeat with i from 1 to (my get_spaces_count())
			set end of the_choices to i
		end repeat
		set default_space to my get_space_binding_for_application(application_bundle_id)
		if default_space = 0 then
			set default_space to "None"
		else if default_space = 65544 then
			set default_space to "All"
		end if
		set chosen_space to (choose from list the_choices default items {default_space} with title "Spaces Assigner" with prompt "Assign " & app_name & " to Space:" OK button name "Assign" without multiple selections allowed and empty selection allowed)
		if chosen_space = false then return false
		my set_spaces_application_binding_for_application(application_bundle_id, item 1 of chosen_space)
		return true
	on error e number n
		log "choose_space_for_application (" & application_bundle_id & ") Error (" & n & "): " & e
		return false
	end try
end choose_space_for_application

on remove_spaces_application_binding(application_bundle_id)
	set application_bundle_id to my make_lowercase(application_bundle_id)
	set app_bindings to my get_spaces_application_bindings()
	try
		get app_bindings as string
	on error error_string
		set app_bindings to my string_to_list(text 13 thru -20 of error_string, ", ")
	end try
	set new_bindings to {}
	repeat with i in app_bindings
		set i to contents of i
		if i does not start with "|" & application_bundle_id & "|:" then set end of new_bindings to i
	end repeat
	my set_spaces_application_bindings(run script "{" & (my list_to_string(new_bindings, ", ")) & "}")
end remove_spaces_application_binding

on get_spaces_arrow_key_modifiers()
	tell application "System Events" to tell spaces preferences of expose preferences to return (get key modifiers of (get properties of arrow key modifiers))
end get_spaces_arrow_key_modifiers

on get_spaces_numbers_key_modifiers()
	tell application "System Events" to tell spaces preferences of expose preferences to return (get key modifiers of (get properties of numbers key modifiers))
end get_spaces_numbers_key_modifiers

on switch_to_space(space_number)
	if not my gui_scripting_check() then return
	set key_modifiers to my get_spaces_numbers_key_modifiers()
	tell application "System Events"
		set key_modifier_list to {}
		if key_modifiers contains command then set end of key_modifier_list to "command down"
		if key_modifiers contains control then set end of key_modifier_list to "control down"
		if key_modifiers contains option then set end of key_modifier_list to "option down"
		if key_modifiers contains shift then set end of key_modifier_list to "shift down"
		set key_modifier_list to my list_to_string(key_modifier_list, ", ")
		if key_modifier_list ≠ "" then set key_modifier_list to " using {" & key_modifier_list & "}"
	end tell
	run script ("tell application \"System Events\" to keystroke \"" & space_number & "\"" & key_modifier_list)
end switch_to_space

on open_spaces_preferences()
	tell application "System Preferences"
		activate
		tell pane id "com.apple.preference.expose" to reveal anchor "Spaces"
	end tell
end open_spaces_preferences

on display_spaces_not_enabled_error()
	beep
	activate
	if ((button returned of (display dialog "Spaces is not enabled. Would You like to enable it now?" with title "Spaces Error" buttons {"Keep Disabled", "Enable"} default button 2 with icon 0)) = "Keep Disabled") then return false
	my set_spaces_enabled(true)
	return true
end display_spaces_not_enabled_error

on list_to_string(l, d)
	tell (a reference to my text item delimiters)
		set {o, contents} to {contents, d}
		set {l, contents} to {"" & l, o}
	end tell
	return l as Unicode text
end list_to_string

on string_to_list(s, d)
	tell (a reference to my text item delimiters)
		set {o, contents} to {contents, d}
		set {s, contents} to {s's text items, o}
	end tell
	return s
end string_to_list

on gui_scripting_check()
	tell application "System Events" to set gui_scripting_enabled to UI elements enabled
	if not gui_scripting_enabled then
		tell application "System Preferences"
			activate
			set current pane to pane "com.apple.preference.universalaccess"
			display dialog "This application utilizes the built-in Graphic User Interface Scripting architecture of Mac OS X which is currently disabled." & return & return & "You can activate GUI Scripting by selecting the checkbox \"Enable access for assistive devices\" in the Universal Access preference pane." with icon 2 buttons {"OK"} default button 1
		end tell
	end if
	return gui_scripting_enabled
end gui_scripting_check

on make_lowercase(the_string)
	return do shell script "echo " & quoted form of the_string & " | /usr/bin/perl -pe 'use encoding utf8; s/(\\w)/\\L$1/gi'"
end make_lowercase

Welcome back, Jonn8. Great library of tools.

The following 2 handlers do not work. They always return the control key as the “key modifier” no matter what the user has set his key modifier to be in the Expose & Spaces preference pane… at least this is the case in 10.5.5.
get_spaces_arrow_key_modifiers()
get_spaces_numbers_key_modifiers()

And because they do not work, this one doesn’t work either:
switch_to_space(space_number)

As such I created a gui scripting way to switch spaces as follows…

switch_space(2)

on switch_space(spaceNumber)
	enable_GUI_scripting()
	tell application "System Events" to tell process "SystemUIServer"
		set menuExtras to value of attribute "AXChildren" of menu bar 1
		repeat with i from 1 to count of menuExtras
			try
				if (value of attribute "AXDescription" of item i of menuExtras) is "spaces menu extra" then
					click (menu bar item i of menu bar 1)
					repeat spaceNumber times
						keystroke (ASCII character 31)
					end repeat
					keystroke space
					return true
					exit repeat
				end if
			end try
		end repeat
	end tell
	tell me
		activate
		display dialog "The Spaces menu bar item is required for this handler to work. Please enable the check box to \"Show Spaces in menu bar\" in the \"Expose & Spaces\" System Preference" & return & return & "Would you like me to open the preference so you can enable the menu bar item for Spaces?" buttons {"Cancel", "Open Pref"} default button 2 with icon stop
		open_spaces_preferences()
	end tell
	return false
end switch_space

on open_spaces_preferences()
	tell application "System Preferences"
		activate
		tell pane id "com.apple.preference.expose" to reveal anchor "Spaces"
	end tell
end open_spaces_preferences

on enable_GUI_scripting()
	if (system version of (system info)) < "10.5" then display dialog "This script requires the installation of Mac OS X 10.5 or higher." buttons {"Cancel"} default button 1 with icon 2
	tell application "System Events" to if not UI elements enabled then
		tell me to display dialog "This script requires the built-in Graphic User Interface Scripting architecture of Mac OS X, which is currently disabled." & return & return & "Enable GUI Scripting now? (You may be asked to enter your password.)" buttons {"Cancel", "Enable"} default button 2 with icon 2
		set UI elements enabled to true
		if not UI elements enabled then error number -128
	end if
end enable_GUI_scripting

Spaces in Panther and Tiger is pretty useless scnr :wink:

Good point! :smiley:
I fixed that handler in my post above.

doing math with a string value is not very reliable,
I’d prefer


if ((system attribute "sysv") mod 4096 div 16) < 5 then .

mod 4096 div 16 filters xx.5.x

Hello!

I think I’ll explain for people who wants to figure out programatically which space they are in, what they should do to make the code below work. This should at least work for Leopard and Snow Leopard.

To let AppleScript know whichc space you are in, you must have GUI Scripting enabled, and you must have the spaces menu extra enabled from the Spaces preference pane. Be sure that the spaces menu extra menu item is as far to the right as you can get it, by cmd-dragging it, until it can come no further.

Since you are using a language other than English, the name of the menu won’t be spaces menu extra.
That is why I have put in a log statement.
when you run the code, the name of something similiar to spaces-menuextra but in your language should pop up.
Now, put that in as a replacement for the spaces menu extra in the code, and you should be good. :slight_smile:


on currentSpaceNumber()
	-- http://hints.macworld.com/article.php?story=20080227075244778
	set xxVar to 0
	tell application "System Events"
		tell process "SystemUIServer"
			set xVar to value of attribute "AXChildren" of menu bar 1
			set cVar to count of xVar
			repeat with iVar from 1 to cVar
				set zVar to value of attribute "AXDescription" of item iVar of xVar
				log zVar ” REMOVE THIS when you have found the description, in your language!
				try
					if zVar = "spaces menu extra" then ”> paste in what you get from the log window here!
                                            ” "spaces-menytillegg" (Norwegian) 
						set xxVar to iVar
						exit repeat
					end if
				end try
			end repeat
		end tell
	end tell
	if xxVar = 0 then
		display dialog "Spaces Menu Extra not installed"
		set theCurrentSpace to null
	else
		tell application "System Events"
			tell process "SystemUIServer"
				set theCurrentSpace to value of menu bar item xxVar of menu bar 1
			end tell
		end tell
		
		-- Do what you want with "theCurrentSpace" variable starting here
		get theCurrentSpace as number
		-- Do what you want with "theCurrentSpace" variable ending here
		
	end if
	return theCurrentSpace
end currentSpaceNumber

Here are some notes on how spaces works with the different settings!

Spaces

The nature of a program is what decides how you assign it to a space.
some programs, you will use for just one space, for one very specific task at a time.

Other apps are omnipresent through out your spaces and projects, like Finder.

Settings that defines your user experience regarding spaces

  • An app can be assigned

    • To a space

    • To all spaces

    • unassigned

  • The defaults setting autoswoosh can be turned on and off by a script.

  • The check box for “Switch to a space that has open windows for the program”,
    can be checked off or on by the user.

The experience of changing space assignments for an app

  • An app will:

  • Gather all its windows in a space when the assignement changes, to the
    space you are in when removing an assignment.

  • Gather all its windows in the space you are in, when assign it to all
    spaces

  • Gather all its windows in the space you have specified, when assigning
    it to a space.

Conclusion

The experience of unassigning an app, and assigning it to all spaces will be the same.

The experience of using an app with spaces

App behaviour isnt regardless of autoswoosh when making new window manually

Apps assigned to every space, will come to you regardless, and isn’t a part of this.

apps unassigned.

autoswoosh off

  • If the app is unassigned to a space, it will open the window in the space
    you are in, regardless of if you are having any windows there or not.

autoswoosh on: checked on:

you will be taken to a space before managing to create a window
-- men kan bypasse ved å aktivere v.h.a applescript først og så lage.

autoswoosh on: checked off:

you will be allowed to active it, and create a window in your current space.

Apps assigned to a space

autoswoosh off

  • It will open the window in the space it is assigned to.

autoswoosh on: checked on:

  • you will be in the space it is assigned to before managing to create.

autoswoosh on: checked off:

  • It will open the window in the space it is assigned to

The behaviour of an app when activating it with regards to auto-swoosh

Autoswoosh set checkbox in spaces pref panel turned on

  • An app unassigned to a space will take you to that space.

  • An app assigned to a space, will take you to that space.

  • An app assigned to all spaces will come right to you. Regardless.

Autoswoosh set checkbox in spaces pref panel turned off

  • An app unassigned to a space will leave you in your current space.

  • An app assigned to a space, will leave you in that space.

  • An app assigned to all spaces will come right to you. Regardless.

Autoswoosh not set (regardless of checkbox )

  • An app assigned to all spaces will come right to you.

  • Apps unassigned, or assigned to a space, will stay where they are.

The behaviour of an app when activating a window with regards to auto-swoosh

  • An app assigned to all spaces is not affected, and will give you the window
    you want.

Autoswoosh set checkbox in spaces pref panel turned on

  • With unassigned and assigned to a space, you will have been brought to the
    space it resides in or last opened a window, before you can managae to activate a window.

  • If the app is assigned to all spaces, then as usual you will get all the
    windows before you can manage to activate a window.

Autoswoosh set checkbox in spaces pref panel turned off

  • If the app is unassigned to a space, you will be taken to the window**

  • If the app is assigned to a space, you will be taken to the window**

  • If the app is assigned to all spaces, then as usual you will get all the
    windows before you can manage to activate a window.

** May not work perfectly well with older apps.

The behaviour of an app when opening a new window

This is also without regards to the check box marked, as opening a window,
also activates it. But the behaviour differs when you open the window programatically, as usual. Then you will not “Activate the window”, merely open it, and leave you in the same space as you were when the code executed.

The behaviour of an app assigned to all space when opening of new windows

  • Regardless of the autoswoosh variable, you get the window right there

The behaviour of an app unassigned to a space when opening of new windows

  • When the autoswoosh variable is set, you get the window in a space where
    you last dealt with a window from that app.

The behaviour of an app assigned to a space when opening of new windows

  • When the autoswoosh variable is set, you get the window in a space where
    you last dealt with a window from that app.

How does the settings work when dealing with windows manually

Autoswoosh set

When autoswoosh is on, and the checkbox unchecked, then you deal with windows, more than applicatons, as it is the activation of a window that will take you to a space.

For an unassigned app, if auto-swoosh is on by default, and uncrossed in
the spaces preferences panel, then you will be taken to the window.

if autoswoosh is on by the defaults, and crossed in the preferesences panel.
then you will be getting to the space directly.

For an assigned app, if auto-swoosh is on by default, and uncrossed in
the spaces preferences panel, then you will be taken to the window.

Autoswoosh not set

  • When the app is assigned to a space, nothing will happen.

  • If the app is unassigned to a space, nothing will happen.

  • The variable does not inflict upon apps assigned to all spaces.

How does the settings work when dealing with windows programtically

If the app is assigned to all spaces, then it will come to you when you activate it, regardless of the autoswoosh variable.

If it is unassigned and the autoswoosh is off, then you will not be able to summon its windows to your space, But it will be the front application process in your space.

If you create a new window, that window will be opened in your space.

Finder unassigned

If it is unassigned and the autoswoosh is on, but checkbox off

  • you can just activate it, without anything particular happening.

  • New windows will be made in your current space.

  • if you activate the app and then the window, you will be brought to it.

  • Just activating the window won’t work.

If it is unassigned and the autoswoosh is on, and checkbox on

  • if you activate it, you will taken to a space it has open windows in (last
    accessed)

  • New windows will be made in your current space. (Interesting)

  • if you activate the app and then the window, you will be brought to it.
    (The same as above, and interesting, it seems like it hasn’t any effects on windows then, this setting, just on the app.)

  • Just activating the window won’t work.

If it is unassigned and the autoswoosh is off, disregarding the checkbox

  • if you activate it, it will activate, but you will stay put, if you have any
    finder windows in that space, they will become active.

  • New windows will be made in your current space.

  • if you activate the app and then the window, nothing will happen, unless the
    window is in your space.

  • Just activating the window won’t work.

Finder assigned to a space

If it is assigned and the autoswoosh is on, but checkbox off

  • you can just activate it, without anything particular happening.

  • New windows will be made in the space it is assigned to, (Differs both from the unassigned app, and how this setting works when dealing with windows manually, to the better, from a programming point of view. )

  • if you activate the app and then the window, you will be brought to it.

  • Just activating the window won’t work.

If it is assigned and the autoswoosh is on, but checkbox on

  • you can just activate it, you will be brought to the space! Differ

  • New windows will be made in the space it is assigned to, (Differs both from the unassigned app, and how this setting works when dealing with windows manually, to the better, from a programming point of view. )

  • if you activate the app and then the window, you will be brought to it.

  • Just activating the window won’t work.

If it is assigned and the autoswoosh is off, disregarding the checkbox

  • if you activate it, it will activate, but you will stay put, if you have any
    finder windows in that space, they will become active.

  • New windows will be made in the space it is assigned to, (Differs both from the unassigned app, and how this setting works when dealing with windows manually, to the better, from a programming point of view. )

  • if you activate the app and then the window, nothing will happen, unless the
    window is in your space.

  • Just activating the window won’t work.

Finder assigned to every space

Then all of this is meaningless, it will happen all of it, in your space.


Conclusion

The autoswoosh variable doesn’t affect apps that are assigned to all spaces.
It affects apps that are either assigned to none, or a space.

The behaviour is then, that the app will open a window, in the space you last used one of its windows in.

If windows are opened by Applescript, then you are unaffected by the autoswoosh.

if you open a window manually, you will be lead into the space where a window last was openened.

Here there are differences between unassigned apps and assigned.

  • you can’t activate an assigned app from a space it has no windows in.
  • you can’t activate an unassigned app from a space it has no windows in.

you can activate an unassigned app from a space it has no windows in, but when you make the window manually, you will be taken into the space where it resides.

  • When openeing windows from applescript you are pretty unaffected by this.

Programtically opening windows by Applescript aren’t affected by the auto-swoosh variable as long as you abstain from activating an app. It will open any windows, regarding the rules above, but will leave you in the space, from which the script was executed.

When the autoswoosh variable is unset

  • The autswoosh value set in the expose preferences

when autoswoosh is turned on in the defaults system then the granularity is regarding windows, in the way that you are not moved to a space, when activating the app, but you are taken to the space where you dealt with a window last time, when activating a window.

if you make a new window with an unassigned app, then it will turn up
in the space you are currently in, and the other windows will reside
where you put them.

The value for an unassigned app is zero.