close 'duplicate' finder windows - just for fun

Hi,
maybe its of interest and help for someone. I wrote it in less than a bunch of minutes, more for fun.

tell application "Finder"
	set wins_nm to name of windows
	set wins_id to windows
	
	set sort_abc to my tagSort(wins_nm)
	set close_w to {}
	repeat with a from 1 to count wins_nm
		try
			set {after_f, c_nm} to {item (a + 1) of sort_abc, item a of sort_abc}
			if after_f is c_nm then copy after_f to end of close_w
		on error
			exit repeat
		end try
	end repeat
	
	set uniq_ls to {}
	repeat with a in wins_id
		set get_nm to name of a
		if get_nm is in close_w and get_nm is not in uniq_ls then
			copy get_nm to end of uniq_ls
			close a
		end if
	end repeat
end tell

to tagSort(L) -- a general purpose simple sort; not enough tags to get fancy.
	considering case
		set sl to {}
		set IL to {}
		repeat (count L) times
			set the low_item to ""
			repeat with i from 1 to (count L)
				if i is not in the IL then
					set this_item to item i of L as text
					if the low_item is "" then
						set the low_item to this_item
						set the low_item_index to i
					else if this_item comes before the low_item then
						set the low_item to this_item
						set the low_item_index to i
					end if
				end if
			end repeat
			set the end of sl to the low_item
			set the end of the IL to the low_item_index
		end repeat
	end considering
	return sl
end tagSort

Hello! I have spent some hours writing this, that doesn’t sort at all!

It can still be optimized further. And it considers the spaces, and Spotlight windows, that are not touched at all.


” © McUsr and put into Public Domain Parts © Nigel Garvey
script pruneDupFWins
	
	
	-- tested 07/09/12
	-- Enhanced to consider Spotlight windows 09/09/12
	-- Optimized for speed 10/09/12
	-- © McUsr 2012 Parts made by Nigel Garvey
	-- 28/09/12 *Totally* Debugged and improved.
	property parent : AppleScript
	property scriptTitle : "Close Duplicate Windows"
	property FinderIcon : a reference to file ((path to library folder from system domain as text) & "CoreServices:Finder.app:Contents:Resources:Finder.icns")
	on run
		local wCount, i, res, res2, fail, prevApp
		script o
			property wlist : {{}, {}}
			property slist : {}
			property klist : {}
			property dlist : {}
		end script
		set fail to false
		try
			tell application id "com.apple.systemevents"
				set prevApp to (name of every process whose frontmost is true and visible is true) as text
				tell application process "Finder" to set o's slist to name of every window
			end tell
			tell application id "com.apple.finder" to activate
			set sCount to count o's slist
			if sCount = 0 then return 0
			
			
			tell application id "com.apple.finder" to set o's wlist to {name, id} of (every window)
			
			repeat with i from 1 to count o's slist
				if item i of o's slist = missing value then set item i of o's slist to "_mcusr_"
			end repeat
			
			repeat with i from 1 to length of item 1 of o's wlist
				if item i of item 1 of o's wlist = "" then set item i of item 1 of o's wlist to "slight"
			end repeat
			
			set wCount to count item 1 of o's wlist
			set o's wlist to reverse of my transposeList(wCount, item 1 of o's wlist, item 2 of o's wlist)
			set o's slist to reverse of o's slist
			set i to 1
			repeat sCount times
				set res to my getSingelton(o's wlist, item i of o's slist)
				if res = null then # Spotlight windows goes here!
				else
					if o's klist ≠ {} then
						set res2 to my indexOfItemN(item i of o's slist, o's klist, 1)
						if res2 = 0 then
							set end of o's klist to item i of o's slist
						else
							set end of o's dlist to res
						end if
					else
						set end of o's klist to item i of o's slist
					end if
					
					set j to indexOfItemN(item i of o's slist, o's wlist, 2)
					if j = 1 then
						set o's wlist to rest of o's wlist
					else if j < length of o's wlist then
						set o's wlist to items 1 thru (j - 1) of o's wlist & items (j + 1) thru -1 of o's wlist
					else
						set o's wlist to items 1 thru -2 of o's wlist
					end if
				end if
				set i to i + 1
			end repeat
			
			set wcl to count o's dlist
			repeat with i from 1 to wcl
				tell application id "com.apple.finder" to close Finder window id (item i of o's dlist as integer)
			end repeat
			
			
		on error e number n
			-- Chris Stone
			set {cr, sep} to {return, "------------------------------------------"}
			set errmsg to sep & cr & "Error: " & e & cr & sep & cr & "Error 
		Number: " & n & cr & sep
			tell application id "com.apple.systemuiserver"
				activate
				try
					display dialog errmsg with title my scriptTitle buttons {"Ok"} default button 1
				end try
			end tell
			set fail to true
			set wcl to 0
		end try
		
		if not fail then
			if wcl > 0 then
				if wcl = 1 then
					set msgText to "I closed " & wcl & " window!"
				else
					set msgText to "I closed " & wcl & " windows!"
				end if
			else
				set msgText to "Nothing to do!"
			end if
			
			tell application id "com.apple.systemuiserver"
				activate
				try
					display dialog msgText with title my scriptTitle buttons {"Ok"} default button 1 with icon my FinderIcon giving up after 1.2
				end try
			end tell
		end if
		tell application prevApp to activate
		return wcl
	end run
	
	to transposeList(ctr, list1, list2)
		-- tested 05/09/12
		script o
			property newL : {}
			property m : list1
			property n : list2
		end script
		local i
		set i to 1
		repeat ctr times
			set end of o's newL to {contents of item i of o's m, contents of item i of o's n}
			set i to i + 1
		end repeat
		return o's newL
	end transposeList
	
	on indexOfItemN(item_a, the_list, n)
		local astid, p
		set astid to AppleScript's text item delimiters
		-- Nigel Garvey's with a name change
		set AppleScript's text item delimiters to return
		set the_list_as_string to return & the_list & return
		set AppleScript's text item delimiters to return & item_a & return
		if (the_list_as_string contains result) then
			set p to (count paragraphs of text item 1 of the_list_as_string)
			set AppleScript's text item delimiters to astid
			if (p is 0) then return 1 -- Catch modern paragraph count for empty text
			return (p div n + p mod n)
		else
			set AppleScript's text item delimiters to astid
			return 0
		end if
	end indexOfItemN
	
	
	on getSingelton(the_list, item_a)
		set astid to AppleScript's text item delimiters
		-- Nigel Garvey's with a name change
		set AppleScript's text item delimiters to return
		set the_list_as_string to return & the_list & return
		set AppleScript's text item delimiters to return & item_a & return
		if (the_list_as_string contains result) then
			set p to (count paragraphs of text item 1 of the_list_as_string)
			if (p is 0) then set p to 1 -- Catch modern paragraph count for empty text.
			set p to p mod 2
			try
				set otherItem to paragraph (p * 2 - 1) of text item (p + 1) of the_list_as_string
			on error
				return null
			end try
			set AppleScript's text item delimiters to astid
			
			return otherItem
		else
			return null
		end if
	end getSingelton
	
end script
tell pruneDupFWins to run

Hi MacUsr!

I remember last time when you said that everyone uses its own coding style. Or something close to this meaning.
Thats in substance the interesting factor looking at other scripters!
But sorry if I can’t follow you in the example you posted- and i’ll explain:
which is technically interesting but too time- consuming for such a little task when it comes the opportunity to close duplicate windows only.

I reworked my snippet and got a shorter code than before, considering spotlight searches too. I’m still unsure what you meant with “.considering spaces”. Duplicates are duplicates and as such they should be removed in all spaces, following the idea for which I conceived such a script. I don’t think that there are such many differences to simply close windows on OS 10.6.x or 10.8.x.

tell application "Finder"
	set {wins_nm, wins_id} to {name, id} of windows
	set {uniq_ls, nm_ls, cc} to {{}, {}, count wins_nm}
	
	#controlling names is super-fast
	repeat with a from 1 to cc
		if item a of wins_nm is not in nm_ls then
			copy item a of wins_nm to end of nm_ls
			copy item a of wins_id to end of uniq_ls
		end if
	end repeat

	#nothing found :)
	if uniq_ls is {} then return
	#close windows except spotlight searches
	set ls to {}
	repeat with a in wins_id
		if a is not in uniq_ls and target of window id a is not "" then close window id a
	end repeat
end tell

It only removes from the current space, since I believe it only should remove things you have control over. Say if you have split a project over several spaces. There may be a duplicate Finder window there that makes sense.

I understand what you mean, But at times with high intensity, I may end up with around 30 windows, and then the script pays off when removing duplicate windows, as it may take a conceived long time to do so without any form of optimization.

That is me. Each to their own. :slight_smile:

Hi,
i understand what you mean too. Being not a big fan of Spaces, but having nevertheless lots of windows on screen some times, i prefer to organize myself using the Dock and Applescripts. :slight_smile:
My intention wasn’t to degrade your efforts of who is able to resolve the same problem with less code.

It also keeps the windows in the correct layering, deleting the oldest of the same windows.

:smiley:

Well, you have made me think, I know I can optimize it some further, as the date is not to be reused, so maybe I’ll have a another go, just for the hell of it. :wink:

Hi,

I read McUsr’s hint on macosxhint last month and I came to similar conclusions as Joy did.

So I posted my version in the comments there. I also posted it here (updated from the one in the comment on macosxhints) with a fuller explanation of what I thought. And the pit falls of just using the windows Name.

set keepWindowList to {}
set closeWindowCount to 0
property scriptTitle : "Close Duplicate Windows"
property FinderIcon : a reference to file ((path to library folder from system domain as text) & "CoreServices:Finder.app:Contents:Resources:Finder.icns")
(* icon to use with end dialogue *)
tell application "Finder"
	set theWindowList to every Finder window (* get the list of finder windows*)
	repeat with i from 1 to number of items in theWindowList (* iterate through each winodow *)
		set this_item to item i of theWindowList (* get a winodow  in the list*)
		set windowID to id of this_item (* get the windows  unique id*)
		set windowTarget to target of this_item (* get the windows  target*)
		set windowName to name of this_item (* get the windows name *)
		
		if target of this_item is computer container then (* Check if window is a  computer container *)
			if windowName is in keepWindowList then (* Check if the window name is in the keepWindowList *)
				set closeWindowCount to closeWindowCount + 1 (* add 1 to the closeWindowCount if the window name is in the list *)
				close this_item (* close the Window *)
			else
				copy windowName to end of keepWindowList (* The Window name was not in the list so add it to the list*)
			end if
			
		else if windowName contains "Searching " then (* The Window was not a computer container so check if its a Search window*)
			
			if windowName is in keepWindowList then (* Check if the window name is in the keepWindowList *)
				set closeWindowCount to closeWindowCount + 1 (* add 1 to the closeWindowCount if the window name is in the list *)
				close this_item (* close the Window *)
			else
				copy windowName to end of keepWindowList (* The Window name was not in the list so add it to the list*)
			end if
		else (* The Window was not a computer container or a Search window so it should be a normal finder window*)
			set windowNTargets to POSIX path of (windowTarget as alias) (*Convert the target to an alias path from a file system item reference and then  get its Posix path*)
			
			if windowNTargets is in keepWindowList then (* Check if the window Target is in the keepWindowList *)
				set closeWindowCount to closeWindowCount + 1 (* add 1 to the closeWindowCount if the window Target is in the list *)
				close this_item (* close the Window *)
			else
				copy windowNTargets to end of keepWindowList (* The Window target was not in the list so add it to the list*)
			end if
			
		end if
		
	end repeat
	
end tell

Hello.

There are something that has been overlooked by me in this, I should have stated, and put with the hack for showing full pathnames of the windows. As that is my reality, and then the problem with identical filenames works.

The reason for the complexity, is to just deliver the names in the current space. At least under some configurations, Finder will deliver all the windows for all spaces, whereas System Events only gives the windows for the current space.

Given the reality above, with pathnames as windows name, at least parts of the complexity can be justified. But it is still possible to optimize it further. Your approach on the other hand will as far as I can see close windows in all spaces, which was what I wanted to avoid in the first place.

Here is a walkthrough of how to make Finder display pathnames in the title bar: Show Full Directory Path in Mac OS X Finder Window Title Bars

Hi MacUsr, mark hunte,
i never thought that somebody would take this topic so seriously. :smiley:
Nice, so noting becomes trivial in scripting. In the meantime i modified my script, after reading the blog post of mark and following the suggestions of MacUsr. Now the script closes finder windows in the current space only, considering locations and excluding spotlight search windows

#consider finder windows in current space only
tell application "System Events" to set spaces_win to name of windows of process "Finder"

tell application "Finder"
	#sort unique paths and get duplicate locations, exclude spotlight searches
	set {id_ls, pt_ls} to {{}, {}}
	repeat with a in spaces_win
		set {the_id, the_pt} to {id, target} of window a
		set the_pt to (the_pt as text)
		if the_pt is not in pt_ls then
			copy the_pt to end of pt_ls
		else
			if the_pt is not "" then copy the_id to end of id_ls
		end if
	end repeat
	if id_ls is {} then return
	
	#finally, close finder windows
	repeat with b in id_ls
		close window id b
	end repeat
end tell

Hi Joy,

I’m sure both scripts could be joined up with a dialog to ask if to close in a single space or all.

the simplest way is to put both scripts in a handler each. And then use…

display dialog "Close Duplicates in." buttons {"Current Space only", "All Spaces", "Cancel"} default button 3
set the button_pressed to the button returned of the result
if the button_pressed is "Current Space only" then

	my joyScript()

else if the button_pressed is "All Spaces" then
	
	my markScript()
	
end if

:D:D

Hello Joy.

I wanted to time your script against mine, but for some odd reason the line with id, target fails.

@ Mark. I hope you update your blog, given the information above. And your script fails to, I get the message: “Can’t make «class alia» “” of application “Finder” into type alias.”

It is the line:

			set windowNTargets to POSIX path of (windowTarget as alias) (*Convert the target to an alias path from a file system item reference and then get its Posix path*)

That fails for some odd reason. I think the reason to be, but aren’t sure it is because I have bundles open.

(On my machine, my script uses around 2 seconds to close 18 duplicate windows.)

Hi,
i wrote a final version.
This one should definitively work:

-added a dialog as mark hunte’s suggested.
-ignore spotlight windows, *Clipping- windows
-consider locations
-consider spaces

tell application "Finder" to set finder_icon to path to resource "Finder.icns"

try
	set button_pressed to button returned of (display dialog "Close window Duplicates in." buttons {"Current Space only", "All Spaces", "Cancel"} default button 3 with icon finder_icon)
on error number the err
	if err is -128 then return
end try

if button_pressed is "Current Space only" then
	tell application "System Events" to set spaces_win to name of windows of process "Finder"
else
	set spaces_win to 0
end if

tell application "Finder"
	set { pt_ls, open_wins} to { {}, windows}
	repeat with a in open_wins
		if name of a does not end with "Clipping" then
			set {the_id, the_pt} to {id, target} of a
			set the_pt to (the_pt as text)
			
			if the_pt is not in pt_ls then
				copy the_pt to end of pt_ls
			else
				if spaces_win is 0 then
					if the_pt is not "" then close a
				else
					if name of a is in spaces_win and the_pt is not "" then close a
				end if
			end if
		end if
	end repeat
end tell


Hello:

I get “Can’t make «class alia» “” of application “Finder” into type text.”
from this line:

set the_pt to (the_pt as text)

i’m sorry, but the script compiles well and runs smootly on my machine. I’ll encourage you to debug the conflicting line as it is necessary for the continuation of the script.
try to split the finder window values in two lines:


    set the_id to id of a
    set the_pt to target of a as text

instead of

set {the_id, the_pt} to {id, target} of a set the_pt to (the_pt as text)

Does it work well on your machine when you have open Finder windows into applications and bundles?

It is no big deal really, I just wanted to time them, out of curiousity. It is just that I have such windows open all the time. :slight_smile:

Edit: Are you using 10.8? Because it doesn’t work for me with 10.6, with regular finder windows, containing regular folders, when I run from within Script Debugger 4.5.7.

Hi Joy,

hmm, you new script is not considering spaces when chosen on my Mac 10.8, finder options:Assign to:none

Hello Mark.

Your script fails in Mac Os X 10.6 as I noted above.

Sorry about that, I will test it on 10.6 tomorrow. But I cannot do that right now as I do not have a 10.6 build to hand.

But should be simple enough to figure out if it is that line.

Cheers.

Hi mark,
i did my best to help us all, first. The question is more, if the script working for you ?
The script i wrote considers and plays with window names simply.

   tell application "System Events" to set spaces_win to name of windows of process "Finder"

Given the script i wrote, everybody should be able to adjust the variables and lines he/she dislikes. Other solutions aren’t that far, i think.

greets,
Jo

Hello Mark.

Since your script doesn’t do what mine does, I wonder if you can adjust your blog-post a tad. :slight_smile:

You should also speed-test it. If I have say 30 windows open, and then opens 20 windows that are alike, my script closes the duplicate windows in 2 secs, on my machine.