A journaling stop watch for mundane tasks and time slips

A small sidenote. When opening a file with write permission the returned file descriptor can be wrong (like when a file can’t be opened due to previous run). So in the error block it’s better to try closing the file by it’s complete path than file descriptor to avoid to have a file opened forever, the whole point of the try block.

That is a very good point!

Thanks DJ! :slight_smile:

Hello.

Implemented the wantsNotifications property to enable disable properties, harnessed the append_to_file handler, Thanks to DJ Bazzie Wazzie. The one play you really or at least I really don’t want something to err, is in a “on error” block! :slight_smile:

Also removed a redundandt notification, and rewrote parts of the “preparateDocWindow” handler.

This works perfectly for me without any notification, as I can just invoke the StopWatch, see how much time has passed, and then hit Esc or command period to cancel the dialog from the keyboard.

Also, the File Read/Write commands can use a parameter constant called ‘eof’, which refers to the insertion point at the end of the file (ie. not to the last byte). So instead of .

-- Get the file length
set flen to (get eof fRef)

-- Append contents to file
write theData to fRef starting at (flen + 1) as «class utf8»

. you could write:

-- Append contents to file
write theData to fRef starting at eof as «class utf8»

Similarly, a command like .

read fRef from 1 to eof as «class utf8»

. would read from the first byte to the end of the file.

Hi,

as theFile is an expanded POSIX path anyway, this is sufficient


	on error
		try
			close access theFile
		end try
		return false
	end try

Hello.

Thanks to Nigel Garvey and StefanK.

I have implemented your changes, I didn’t see the changes Nigel suggested, and Stefan’s I didn’t want to try, I take his word for it to work.

Thanks a lot guys, and I hope you like it! :slight_smile:

I have posted the updated version, (2.8 by now!) along with some other improvements/changes:

Added the start time to the dialogs, so you can always see when a task started (more informative).

Added the option of opening the journal file from the first dialog, if you want quick access to the journal file, (or have forgotten where you stored it, and lothe to have to open the script to see where that was. I hope I did succeed to make an easygoing error message for the case that the journal file doesn’t exist, and the user starts off by clicking the “Open Journal” button. I removed some redundant code from displayJournalFile at the same time.

Hello.

Some very last additions:

1.) I have made the handler refresehJournal more “rational” in order to not disturb the Ui more than necessary, when TextEdit have been hidden.

If TextEdit isn’t visible when you have set JournalToFrontUnsolicited to false, then the journal stays invisible with any other documents, (after that it has been updated). (But, if JournalToFrontUnsolicited is set to true, then that trumps the visisbility of TextEdit.)

The rules that governs the notification that displays when a document is updated is now changed, it now only triggers when we don’t see the updated document: that is, when either TextEdit is hidden, or JournalToFrontUnsolicited is false.

2.) I am a bit paranoid, about saving the script manually after a dialog has timed out from the Service, or the Applet, so I have set a timeout of 320 seconds, and giving up after 319 seconds, if the script sees that the user gave up, then the scripts properties are reset, and we quit.

Hello.

I removed the emergency reset, and the try block from the run handler, as I figured that the best thing was to return buttons with value of cancel where that made sense and Ok when that made sense.

So, if the startDialog times out, then the timing is just cancelled, the two dialogs that can shift state from either running to pause or stop, or from pause to running and stop, just continues in the state they were in, like if the user just hit cancel to get an intermediary result. The endDialog returns Ok, which is fine, and then the journal displays, in this case everything is reset already anyway.

I have also removed some debris.

Hello.

I had to declare the variable gaveUp, up front in the dialog handlers, so that Automator don’t bark, when the script is run from the Service.

Hello.

Thought I’d just mention that in order to get the good looking icon on the StopWatch.app in the Dock:

  • You must open the icns file in the coreservices/resources folder in Preview.
    (The icns file I have in mind is specified in the StopWatch.scpt also in post #1).

  • You must then find the icon with the “right size” and copy it to the clipboard.

  • Open the info panel of the applet, click the applet icon so it turns “bluish”, and paste.

If it doesn’t update in the Dock, if you have put there already, then please remove the applet from the Dock and
then drag the applet with the now good-looking icon onto the Dock.

That’s all.

Thanks. :slight_smile:

Stop Watch Service

Hello

The Automator service I have provided so far, appears to never quit properly, that means, that some of the time, but not all of the time, the worflow service doesn’t quit, you may wish to enter a ‘w’ in the search field of Activity Monitor.

Thanks to Mark Hunte I have found a solution, that doesn’t involve an Automator Service, to create a dedicated StopWatchService with AppleScript.

This is not so complex, yet not totally straight forward, if you are a novice on the subject.

Prequisites: You need to have the StopWatch.app from post #1 on your disk.

1.) Open AppleScript Editor, or Script Editor.

2.) We are going to make a new Cocoa-AppleScript Applet, (File->New From Template)

3.) Select everything in it, and paste in the code below, overwriting everything that was there.
(Change the path to were StopWatch.scpt resides, and any spaces in the filename should be quoted with a ''


-- main.scpt
-- Cocoa-AppleScript Applet
--
-- 2015 McUsr. 
-- Big thanks to Mark Hunte, it is totally stolen from him. Any faults are mine.

property NSWorkspace : class "NSWorkspace"

tell current application's NSApp to setServicesProvider:me
NSUpdateDynamicServices()

my runAService()
on runAService()
		tell me
		do shell script "/usr/bin/osascript /Path/to/StopWatch.scpt >/dev/null 2>&1 &"
		quit
	end tell
end runAService

4.) Compile, then save it as StopWatchService.

5.) Open the sidebar pane, and give it a bundle identifier of

6.) Save again.

7.) Click on the gear icon on the side bar and select “Reveal in Finder”.

8.) Launch Terminal

9.) Enter [b]defaults write /b then drag the info.plist file into the terminal window, so the posix path of it follows what you just typed.

10.) replace the .plist part of the filename, with a space and write: NSServices -array-add ‘{NSMenuItem={default=“Launch StopWatch”;}; NSMessage=“runAService”; NSSendTypes=();}’, then hit enter.

The whole line should look something like this before you hit enter:

11.) Activate the Finder window, press cmd up arrow twice, so you can see the StopWatchService.app. Then execute it once, by doubleclicking, nothing should happen.

  1. ) Open the Services tab of the shortcuts tab of the keyboard preferences pane. Somewhere there, you should now see an item named StopWatchService. Assign this service to a shortcut.

13.) Try the Shortcut.

14.) Feel free to comment on anything unclear about this procedure, or if it didn’t work for you.

Marks Huntes original post resides here in case you want to look it up, and it has nice pictures. :slight_smile:

Hello.

I have corrected a typo, the name of the service should be Launch StopWatch, and not Launch Terminal.

I have also changed the contents of the service, for the hope of gaining some of the speed back, when we lost the memory leakage. I now use an osascript to run the script from the service, and this seems to speed up things a little.

I doesn’t match the WorkflowRunner, but the advantage of not leaking memory, by having many WorkflowServices zombies in the memory trumps that in my opinion.

Hello.

I have updated the applet in post #1, the new applet uses osascript to execute the shell script much like the StopWatchService does. The reason for this is, that the applet hangs if the helpviewer was the most frontmost app.

As it is now, you’ll still not see the dialog if HelpViewer is frontmost, but if you shift application by hitting command tab once, to switch app, and then hits command-tab to switch app once again, you’ll have it right in front of you.

This is a kluge, but I haven’t found any way around this, because HelpViewer, is a background application, with no support for UI scripting, which makes it really hard to detect. At least I haven’t found a way to detect it, maybe someone else has.

I have also bumped the version of the script to “3.1.1”, but all I have done, has really been adding elipsis (.) where the “Stop” button will invoke a new dialog, -just to be conformant. :slight_smile:

Hello.

I have updated the StopWatch script in post #1.

I have asserted that time strings will allways show up in 24h format, which is logical when we deal with stopwatches really, and besides that, makes the code simpler, with respect to formatting, since the length of time strings varies. I have added some other formatting to the dialogs as well, with the hope that they are more informative, besides prettier.
I have also changed the wording of the dialogs, with the hope that the new wording is better.

Enjoy.

Hello.

I had forgotton that there weren’t any cancel dialog in the endbutton, when I made and implemented the dialog3button handler…

That is now fixed.

Hello.

I have completely rewritten the documentation, after a couple of days, I am going to delete this whole thread, and post the first post in new and pristine conditions. :slight_smile:

Hello.

I have tried to make the StopWatchService as wee bit snappier. First of all I have added an item specifiying a port name, that you may wish to paste into the Services array of the info.plist file of StopWatchService, with TextWrangler:

If you have the tools for it, then the the human readable form of the name is “Incoming service port name”, and it should have a value of StopWatchService.

The next step is to replace the CocoaAppletAppDelegate script in the Resouces folder of StopWatchService with the code below, and then make it run only ( Export as Run-Only from the File menu of Script Editor).

Warning:
Your script editor may most probably hang of this step, I think it usually goes well, that all files get to be restored after you have force quit Script Editor, but then again, it may not, so be sure to have saved any unsaved files, before you try to overwrite the existing CocoaAppleAppDelegate.


script CocoaAppletAppDelegate
	property parent : class "NSObject"
	property mainScript : missing value -- the applet's main.scpt
	property isQuitting : false -- re-entrancy guard: true = in the process of quitting
	
	on applicationWillFinishLaunching:aNotification
		-- Insert code here to initialize your application before any files are opened 
		
		-- Emulate an OSA Applet: Load the main script from the Scripts resource folder.
		try
			set my mainScript to load script (path to resource "main.scpt" in directory "Scripts")
		on error errMsg number errNum
			-- Perhaps this should silently fail if it can't load the script; that way, a Cocoa applet
			-- can just have Cocoa classes and no main.scpt.
			display alert "Could not load main.scpt" message errMsg & " (" & errNum & ")" as critical
		end try
	end applicationWillFinishLaunching:
	
	on applicationDidFinishLaunching:aNotification
		-- Insert code here to do startup actions after your application has initialized
		
		if mainScript is missing value then return
		
		-- Emulate an OSA Applet: Invoke the "run" handler.
		
		-- If we have already opened files during startup, don't invoke the run handler.
		
		try
			tell mainScript to run
		on error errMsg number errNum
			if errNum is not -128 then
				display alert "An error occurred while running" message errMsg & " (" & errNum & ")" as critical
			end if
		end try
		
		-- TODO: Read the applet's "stay open" flag and quit if it's false or unspecified.
		-- For now, all Cocoa Applets stay open and require the run handler to explicitly quit,
		-- which is arguably more correct for a Cocoa application, anyway.
		(* if not shouldStayOpen then
			quit
		end if *)
	end applicationDidFinishLaunching:
	
	on applicationShouldTerminate:sender
		-- Insert code here to do any housekeeping before your application quits 
		
		-- Guard against re-entrancy.
		if not isQuitting and mainScript is not missing value then
			set isQuitting to true
			
			-- Emulate an OSA Applet: Invoke the "quit" handler; if the handler returns, it has fully
			-- handled the quit message and we should not quit, otherwise, it calls "continue quit",
			-- which returns error -10000.
			try
				tell mainScript to quit
				set isQuitting to false
				
				return current application's NSTerminateCancel
			on error errMsg number errNum
				-- -128 means there is no quit handler
				-- -10000 means the handler did "continue quit"
				if errNum is not -128 and errNum is not -10000 then
					display alert "An error occurred while quitting" message errMsg & " (" & errNum & ")" as critical
				end if
			end try
			
			set isQuitting to false
		end if
		
		return current application's NSTerminateNow
	end applicationShouldTerminate:
	
end script

The last step is to have the main script open in script editor, and export that too, (from the File menu) so that it overwrites the main.scpt that resides in StopWatchService/Contents/Resources/Scripts.

I am really sorry that that I came up with this so late, but it was only later that I realized I had to make it a tad snappier. I really recommend you do the modding, because the snappiness, also decreases the chance of a port time out, if the app that is in foreground are under a heavy load, when you hit the short cut key.
(Services are routed through the foreground app.)

And I am even more sorry for having forgotten to warn you about a hanging ScriptEditor, when you save the CocoAplletAppDelegate script. I have added a warning about that in the post above.

Hello.

I have removed a small bug in the fmt24HTime handler: I tested for hours less than 12, I should of course have tested for hours less than 10. I am sorry for the inconvenience.

Hello.

I have written a script to configure the info.plist file of an app, in order to configure it as a service, I have added both! properties for making it into a background only app, and a user agent. This for the hope that at least one of them, sees to, that launchd skips the part of the launch process that loads UI.

Other than that: You can only run this script once per propertylist file. And you should really duplicate the info.plist file first, so you have a backup, should something go wrong. (just duplicate it in Finder. Of course have read Mark Hunte’s post about this, -which I refer to in post nr 1.

Install

This script must be installed into the script menu, and you should change the name to “Script Editor” if on Yosemite.

Usage

See to that the application that you want to configure is the frontmost, and then run this script, after you have configured it with the values you want to use.

:slight_smile:

-- Copyright 2015 ©  McUsr
-- Copyright 2015 ©  McUsr
# You may not post, or print  this somewhere else, but refer to the link where you found it.
property scriptTitle : "Configure Applet as Service"
-- This script is, as it stands just able to be run on once, it doesn't change any already 
-- existing values, it probably will ruin the whole plist file, so it is a good thing to have
-- made a copy up front.

property serviceName : "Launch Mail"
-- Above is the name of the service as it should turn up in any services menu.
property serviceMessage : "runAService"
-- Above is the name of the handler in your applet that is the services run-handler.
property servicePortName : "LaunchMail"
-- above is the name of the service portname the service should be called with.
-- I think it wise to call it the same as the Application 
property dryRun : false


tell application "AppleScript Editor"
	if serviceName = "Launch Console" and servicePortName = "LaunchConsole" then
		display dialog "It appears that you haven't edited the original values of the script, are you sure you want to enter those values into an info.plist of an applet you intend to use as a service?" with title my scriptTitle with icon 2
	end if
	
	try
		-- Check to see if the front document has been saved as a bundle application
		try
			set the doc_properties to the (properties of the front document)
			set the script_path to the path of doc_properties
			set appletName to name of doc_properties
			set the plist_path to script_path & "/Contents/Info.plist"
			set the plist_file to plist_path as POSIX file as alias
		on error
			error "The front script document has not be saved as a Script bundle or as an Application bundle."
		end try
		
		
		-- Query the user
		
		set the dialog_message to "This scripts sets necessary for an application based on the cocoa-applet to be configured as a Service.
You have service name to : -> " & my serviceName & " <-,
     the service message to: -> " & my serviceMessage & " <-,
 the service port name to : -> " & my servicePortName & " <-" & return & "
Are those the properterties you want to use on: -> " & appletName & " <- , for it to become a Service?"
		set {button returned:btn} to (display dialog dialog_message with title my scriptTitle buttons {"No", "Yes"} cancel button 1 default button 2 with icon 2)
		
		if btn = "Yes" then
			tell application "System Events"
				-- create an empty property list dictionary item
				set the parent_dictionary to make new property list item with properties {kind:record}
				-- create new property list file using the empty dictionary list item as contents
				if my dryRun then
					set the plistfile_path to "~/Desktop/ServiceExample.plist"
					
					set this_plistfile to ¬
						make new property list file with properties {contents:parent_dictionary, name:plistfile_path}
				else
					set this_plistfile to property list file plist_path
				end if
				if (exists property list item "NSServices" of contents of this_plistfile) then
					display alert my scriptTitle message "The property list already contains an NSServices item, I quit" buttons {"Cancel"} cancel button 1
				end if
				make new property list item at end of property list items of contents of this_plistfile ¬
					with properties {kind:boolean, name:"LSUIElement", value:true}
				
				make new property list item at end of property list items of contents of this_plistfile ¬
					with properties {kind:string, name:"LSBackgroundOnly", value:"1"}
				
				
				set servicesArray to make new property list item at end of property list items of contents of this_plistfile ¬
					with properties {kind:list, name:"NSServices"}
				set servicesDict to make new property list item at end of property list items of contents of servicesArray ¬
					with properties {kind:record}
				set menuItemDict to make new property list item at end of property list items of contents of servicesDict ¬
					with properties {kind:record, name:"NSMenuItem"}
				make new property list item at end of property list items of contents of menuItemDict ¬
					with properties {kind:string, value:my serviceName, name:"default"}
				
				-- Inserting into Services dict
				make new property list item at end of property list items of contents of servicesDict ¬
					with properties {kind:string, value:my serviceMessage, name:"NSMessage"}
				make new property list item at end of property list items of contents of servicesDict ¬
					with properties {kind:string, value:my servicePortName, name:"NSPortName"}
				-- creating the empty array:
				make new property list item at end of property list items of contents of servicesDict ¬
					with properties {kind:list, name:"NSSendTypes"}
				
			end tell
		end if
		
		display notification appletName & " configured to be a Service." with title scriptTitle
		
	on error error_message number error_number
		if the error_number is not -128 then
			display dialog error_message with title scriptTitle buttons {"Cancel"} default button 1 with icon 2
		end if
	end try
end tell