Posts Tagged ‘applescript’

Universal File Dialog, a first step

Tuesday, August 31st, 2010

I’m switching from Qt to straight Glut with AntTweakBar for my next project. I’m happy about getting rid of Qt. For my small prototyping projects it was too heavy (hard to get a target working on computers that don’t have Qt installed) and too much work to add simple ui for tweaking parameters and flipping flags. AntTweakBar doesn’t yet solve the deployable problem (I’m trying to figure that out too) but is miles better for prototyping. The only thing I miss from Qt is the native File Save/File Open Dialogs. Since Glut is fairly universal I have taken a stab at writing a small header that could be the start of a universal file dialog “library”. I’d only want this library to depend on standard includes and it only needs to implement get_file_save_path and get_file_open_path.

Here’s what I put in FileDialog.h:


#include <stdio.h>
#define FILE_DIALOG_MAX_BUFFER 1024

// Sets buffer to a path to an existing file
// buffer[0]=0 on cancel
//
// Usage:
//   char buffer[FILE_DIALOG_MAX_BUFFER];
//   get_open_file_path(buffer);
void get_open_file_path(char buffer[]){
#ifdef __APPLE__
  // For apple use applescript hack
  FILE * output = popen(
    "osascript -e \""
    "   tell application \\\"System Events\\\"\n"
    "           activate\n"
    "           set existing_file to choose file\n"
    "   end tell\n"
    "   set existing_file_path to (POSIX path of (existing_file))\n"
    "\" 2>/dev/null | tr -d '\n' ","r");
  while ( fgets(buffer, FILE_DIALOG_MAX_BUFFER, output) != NULL ){
  }
#else
  // For every other machine type
  printf("Please enter a file path: ");
  gets(buffer);
#endif
}

// Sets buffer to a path to a new/existing file
// buffer[0]=0 on cancel
//
// Usage:
//   char buffer[FILE_DIALOG_MAX_BUFFER];
//   get_save_file_path(buffer);
void get_save_file_path(char buffer[]){
#ifdef __APPLE__
  // For apple use applescript hack
  // There is currently a bug in Applescript that strips extensions off
  // of chosen existing files in the "choose file name" dialog
  // I'm assuming that will be fixed soon :-)
  FILE * output = popen(
    "osascript -e \""
    "   tell application \\\"System Events\\\"\n"
    "           activate\n"
    "           set existing_file to choose file name\n"
    "   end tell\n"
    "   set existing_file_path to (POSIX path of (existing_file))\n"
    "\" 2>/dev/null | tr -d '\n' ","r");
  while ( fgets(buffer, FILE_DIALOG_MAX_BUFFER, output) != NULL ){
  }
#else
  // For every other machine type
  printf("Please enter a file path: ");
  gets(buffer);
#endif
}

And here’s a sample program that calls both functions. Save it in test.c:


#include "FileDialog.h"
#include <stdio.h>

int main(void){
  char buffer[FILE_DIALOG_MAX_BUFFER];

  get_open_file_path(buffer);
  if(buffer[0] == 0)
    printf("Cancelled\n");
  else
    printf("Open file path: %s\n",buffer);

  get_save_file_path(buffer);
  if(buffer[0] == 0)
    printf("Cancelled\n");
  else
    printf("Save file path: %s\n",buffer);

  return 0;
};

Compile and run on a Mac with:


gcc -o test test.c;
./test

Build and run current xcode project from command line

Friday, June 18th, 2010

I edit my source code via the command line using vim. Then I need to go into xcode and push build/run to debug the code I’m working on. I’d rather just issue a command from vim. Here’s a cheap way to build and run via and applescript which can then be called by vim.
Save the following applescript as xcode_build_and_launch.scpt


tell application "Xcode"
  activate
  set targetProject to project of active project document

  if (build targetProject) starts with "Build succeeded" then
    launch targetProject
  end if
end tell

Then from vim you can issue:


:!osascript path/to/xcode_build_and_launch.scpt

This will activate xcode and build the current project, and if that succeeds then it will launch the current executable target.

Note: I stripped the above out of code from a question on stackoverflow. Apparently this will not work for iPhone simulator projects.

Update: I like to call it from vim with the bash time command and then I have an idea of how long the compile took.


:!time osascript path/to/xcode_build_and_launch.scpt

Ad blocking one website at a time

Monday, June 7th, 2010

I wrote a small piece of Applescript code that allows me to zap ads from web pages that I browse using Safari. It uses javascript to remove the ads or containing objects from the page directly. It doesn’t use intelligent ad recognition in any way. Rather my idea was much simpler. Most people spend 90% of there time on the web looking at a small number of websites: facebook, new york times, twitter, google, wikipedia, gmail, youtube, etc. See Pareto principle. If I can block 100% of the ads on 90% of the pages I look at then my ad blocker is effectively 90% efficient.

The ads on these big sites are usually in the same place on the page making them very easy to remove from the html with javascript. I organized my applescript so that adding a new site on which to block ads is very simple (just a line or two) and blocking a certain type of ad on that site is just another line.

I use the magnifying glass tool, in the Safari Developer tool, Web Inspector, to select the ad object on the page. Inevitably the website has used a class or id which then I feed into my script. Often the containing object is as simple as “ad” or “bigAd”.

screenshot of web inspector, selecting ad

When I have my blocker running in the background, it usually takes up 2% of the CPU. If I crank down the delay in the script so that the ads disappear even faster then it can get as high as 10% or so, but it’s not really worth it.

There is no reason why you couldn’t easily port this style of ad blocker to grease monkey, opera, or anything that runs client-side javascript.

Here is the applescript, it creates a few useful javascript methods and sets up the blocker to run in the background (with a little error checking).


-- Safari AdBlocker Applescript
-- Author: Alec Jacobson http://alecjacobson.com
--

-- this might not be necessary...
-- because safari already has getelementbyclassname...
-- but perhaps without localization?
-- and perhaps not older safari?
set getElementsByClass to "
/*
  Developed by Robert Nyman, http://www.robertnyman.com
  Code/licensing: http://code.google.com/p/getelementsbyclassname/
*/
var getElementsByClassName = function (className, tag, elm){
  if (document.getElementsByClassName) {
    getElementsByClassName = function (className, tag, elm) {
      elm = elm || document;
      var elements = elm.getElementsByClassName(className),
        nodeName = (tag)? new RegExp(\"\\b\" + tag + \"\\b\", \"i\") : null,
        returnElements = [],
        current;
      for(var i=0, il=elements.length; i<il; i+=1){
        current = elements[i];
        if(!nodeName || nodeName.test(current.nodeName)) {
          returnElements.push(current);
        }
      }
      return returnElements;
    };
  }
  else if (document.evaluate) {
    getElementsByClassName = function (className, tag, elm) {
      tag = tag || \"*\";
      elm = elm || document;
      var classes = className.split(\" \"),
        classesToCheck = \"\",
        xhtmlNamespace = \"http://www.w3.org/1999/xhtml\",
        namespaceResolver = (document.documentElement.namespaceURI === xhtmlNamespace)? xhtmlNamespace : null,
        returnElements = [],
        elements,
        node;
      for(var j=0, jl=classes.length; j<jl; j+=1){
        classesToCheck += \"[contains(concat(' ', @class, ' '), ' \" + classes[j] + \" ')]\";
      }
      try  {
        elements = document.evaluate(\".//\" + tag + classesToCheck, elm, namespaceResolver, 0, null);
      }
      catch (e) {
        elements = document.evaluate(\".//\" + tag + classesToCheck, elm, null, 0, null);
      }
      while ((node = elements.iterateNext())) {
        returnElements.push(node);
      }
      return returnElements;
    };
  }
  else {
    getElementsByClassName = function (className, tag, elm) {
      tag = tag || \"*\";
      elm = elm || document;
      var classes = className.split(\" \"),
        classesToCheck = [],
        elements = (tag === \"*\" && elm.all)? elm.all : elm.getElementsByTagName(tag),
        current,
        returnElements = [],
        match;
      for(var k=0, kl=classes.length; k<kl; k+=1){
        classesToCheck.push(new RegExp(\"(^|\\s)\" + classes[k] + \"(\\s|$)\"));
      }
      for(var l=0, ll=elements.length; l<ll; l+=1){
        current = elements[l];
        match = false;
        for(var m=0, ml=classesToCheck.length; m<ml; m+=1){
          match = classesToCheck[m].test(current.className);
          if (!match) {
            break;
          }
        }
        if (match) {
          returnElements.push(current);
        }
      }
      return returnElements;
    };
  }
  return getElementsByClassName(className, tag, elm);
};"
set setElementsToEmpty to "function setElementsToEmpty(a){
  for ( var i=0, len=a.length; i<len; ++i ){
    a[i].innerHTML = '';
  }
};
"

set this_url to ""
repeat
  try
    repeat while appIsRunning("Safari")
      tell application "Safari"

        try
          set doc to front document
          set this_url to URL of doc
          do JavaScript getElementsByClass in doc
          do JavaScript setElementsToEmpty in doc
          if this_url starts with "http://www.google.com/search?" then
            -- Regular google search
            -------------------------------------------------------------
            --
            -- ZAP CONTAINING DIV BY ITS ID
            --
            -------------------------------------------------------------
            do JavaScript "document.getElementById('rhsline').innerHTML = ''" in doc
            do JavaScript "document.getElementById('tads').innerHTML = ''" in doc
          else if this_url starts with "http://search.yahoo.com/search" then
            -- Regular yahoo search
            do JavaScript "document.getElementById('east').innerHTML = ''" in doc
            -------------------------------------------------------------
            --
            -- ZAP DIVS OF CERTAIN CLASS WITHIN  CERTAIN CONTAINING DIV (COULD BE NULL)
            --
            -------------------------------------------------------------
            do JavaScript "setElementsToEmpty(getElementsByClassName('ads horiz',null,document.getElementById('main')))" in doc
          -------------------------------------------------------------
          --
          -- ADD OTHER SITES HERE
          --
          -------------------------------------------------------------
          end if
          -- set delay amount accordingly to manage how much CPU to devote to blocking ads
          -- recommended between 1.0 and 0.001 seconds
          delay 0.1
        on error errText number errNum
          -- if anything but doc changing before ads removed or safari open but no windows
          -- pause so that CPU isn't stolen
          delay 2
        end try
      end tell
    end repeat
  on error errText number errNum
    if errNum is equal to -128 or errNum is equal to -609 then
      -- safari no longer open
    else
      display dialog errText & " " & errNum
    end if

  end try
  delay 5
end repeat

-- from http://codesnippets.joyent.com/posts/show/1124
on appIsRunning(appName)
  tell application "System Events" to (name of processes) contains appName
end appIsRunning

Download the ad blocker with all the sites I’ve blocked ads on

Imagine how strong this ad blocker could be if an army of users were updating the site specific zaps. Imagine the cold war it would start.

Note: Some sites inject their ads deeper into the content of the site like youtube does with its flash videos. I haven’t come up with a way to single this one out yet…

Open terminal here (parent directory of drag-and-dropped file), applescript

Monday, April 12th, 2010

Here’s a little script I’ve been meaning to write for while. Often I’m roaming around in the Finder GUI and I want to switch to a terminal. This is not so hard if I already have a terminal window open I can type cd into the command line then drag the current folder I’m looking at in Finder onto the terminal window and I get the full absolute path to that file. Then I hit enter to navigate there on the command line.

To make this totally effortless and seemless if Terminal.app is not already running. I wrote the following applescript:


-- if not given files then just act like opening a terminal to the home screen. I.e. `cd`
tell application "Terminal"
  activate
  do script ""
end tell

-- if fiels are drag-and-dropped on this app
on open (these_files)
  -- just use first file if more than one
  set this_path to (quoted form of basedir(POSIX path of (first item of these_files)))
  tell application "Terminal"
    activate
    do script "cd " & this_path
  end tell
end open

on basedir(the_path)
  set last_occurrence to last_offset(the_path, "/")
  if last_occurrence is equal to 0 then
    return "."
  end if
  if last_occurrence is equal to 1 then
    return "/"
  end if
  return items 1 thru (last_occurrence) of the_path as string
end basedir

on last_offset(the_text, char)
  try
    set len to count of the_text
    set reversed to reverse of characters of the_text as string
    set last_occurrence to len - (offset of char in reversed) + 1
    if last_occurrence > len then
      return 0
    end if
  on error
    return 0
  end try
  return last_occurrence
end last_offset

Using some older functions I wrote:
last offset of a character in a string
base directory of a POSIX path

Then I save as an application and drag a shortcut onto my finder bar.

Vim applescript application

Sunday, March 14th, 2010

I wanted a quick way to open up files from finder in vim so I wrote a little applescript application. I use Script Editor.app to save this as an Application in a file called Vim:


on open (these_files)
  set names to ""
  repeat with this_file in these_files
    set names to names & (quoted form of POSIX path of this_file) & " "
  end repeat
  tell application "Terminal"
    activate
    do script "vim " & names
  end tell
end open

Download the vim applescript app

I would like to know how to make this application come up as a right-click, open with... option for every file type, but I haven't figured that out yet...

Hack infinite scroll javascript with infinite auto-scroll to bottom of page

Thursday, January 7th, 2010

This is a hack to have your browser load all search results when a page is using jQuery’s infinite scroll feature, like this site: http://instantwatcher.com/titles/all?infinite=1.
Here’s the client side javascript to keep auto-scrolling this page to the bottom, thus triggering infinite scroll to load more results. It runs until there are no more results to load:

    function scrollToBottom(){
      bottom = document.body.scrollHeight;
      current = window.innerHeight+ document.body.scrollTop;
      if((bottom-current) >0){
        window.scrollTo(0, bottom);
        setTimeout ( 'scrollToBottom()', 1000 );
      }
    };
    scrollToBottom();

I run this on Safari using this short applescript:


tell application "Safari"
  set doc to front document
  set this_url to URL of doc
  do JavaScript "
    function scrollToBottom(){
      bottom = document.body.scrollHeight;
      current = window.innerHeight+ document.body.scrollTop;
      if((bottom-current) >0){
        window.scrollTo(0, bottom);
        setTimeout ( 'scrollToBottom()', 1000 );
      }
    };
    scrollToBottom();
    " in doc
end tell

Try it!

Open an applescript display dialog via command line bash script

Thursday, December 17th, 2009

I wrote an rsync based backup script for my parents’ Mac. I set it up as a cron job that backs up their hard drive to an external once a week. The only problem is that if they don’t have the external plugged in then the script has no way of letting them know that no back up took place. Since the whole point of the script was to make backing up something that just happens in the background without necessary thought I can’t expect them to read a log file etc. Instead I came up with a way for my bash script to talk to them through the GUI. So here’s my little bash script that takes a single argument which gets turned into a display box using applescript.


#!/bin/bash
# ./display-dialog.sh "Hello, world."
# Opens dialog using applescript saying "Hello, world."
#

temp_app="/tmp/$(basename $0).$$.app"
osacompile -e "display dialog \"$1\"" -x -o $temp_app
open $temp_app

Know in my script or from the command line, I can issue:


./display-dialog.sh "Hey, the back up external drive is not plugged in!"

and my parents will see:


display dialog via bash command

Retrieve current user’s full name, Mac OS X

Saturday, December 5th, 2009

osascript is a command that allows you to execute applescript via the command line and in script. Here’s a short command that retrieves the current users full name.


osascript -e "long user name of (system info)"

Anyone know how to do this in pure bash/unix tools?

Update: Gmail Notifier corrupted my osascript so now I have to send the bogus errors to /dev/null like this:


osascript -e "long user name of (system info)" 2>/dev/null

Update:
Here’s another way I found the long user name:


system_profiler  | grep "User Name:" | sed "s/^      User Name: \([^(]*\) (.*/\1/g"

Update: On linux consider using this:


getent passwd $USER | cut -d ":" -f 5

Copy iTunes selection to a new album

Friday, December 4th, 2009

Now that nobody burns CDs for each other, lately when making mixes I go through a painful process:

  • make an iTunes playlist with tracks order how I want them
  • copy all those files individually, manually
  • reinsert them into iTunes
  • change all their album tags to my mix name
  • (super painful) change all their track number tags to reflect the track order in my mix
  • (so painful I usually skip) change all their file names to uniformly reflect my track order and album changes
  • zip up and email to friend

I set and found a few applescripts to handle automating one or two of the above tasks, but I thought I might as encapsulate the whole thing. So here’s my solution as an applescript:


(*
Copy selected tracks and re-add into itunes with album = to given text and track numbers according to selection order

Author: Alec Jacobson (alecjacobsonATgmailDOTcom)
 *)
-------------------------------------

-- check if iTunes is running
tell application "System Events"
  if (get name of every process) contains "iTunes" then set okflag to true
end tell
if okflag then

  tell application "iTunes"
    set mix_name to ""
    set prompt to "Mix name:"
    repeat while length of mix_name is equal to 0
      display dialog prompt default answer "My new mix" buttons {"OK", "Cancel"} default button 1
      copy the result as list to {text_returned, button_pressed}
      set mix_name to text_returned
      set prompt to "Mix name (must enter something):"
    end repeat

    set selected_tracks to selection
    if selected_tracks is {} then -- no selection
      display dialog "Select some tracks first..." buttons {"OK"} default button 1
    else
      set selected_songs to ""
      set track_number to 1
      set number_of_tracks to length of selected_tracks
      repeat with this_track in selected_tracks
        do shell script "cp " & (quoted form of POSIX path of (get this_track's location)) & " " & (quoted form of (POSIX path of (get this_track's location) & ".tmp.mp3"))
        set new_copy to add (POSIX file (POSIX path of (get this_track's location) & ".tmp.mp3")) as alias to source "Library"

        set errNum to -54 -- expect a file permission error (itunes takes a second...)
        repeat while (errNum is equal to -54)
          try
            set album of new_copy to mix_name
            set track number of new_copy to track_number
            set track count of new_copy to number_of_tracks
            set compilation of new_copy to true

            set track_number_string to track_number as string
            if (track_number < 10) then
              set track_number_string to "0" & track_number_string
            end if

            set new_path to location of new_copy
            tell application "Finder" to set name of new_path to "" & track_number_string & " " & (name of new_copy) & ".mp3"

            set errNum to 0
          on error errText number errNum
            if errNum is not equal to -54 then
              error errText number errNum
            end if
            delay 1.0E-4
          end try
        end repeat

        set track_number to track_number + 1
      end repeat
    end if
  end tell
end if

Open lines in clipboard as URLs in Safari

Saturday, November 14th, 2009

I used to use a script that took the lines of a text edit document and opened each as a url in Safari. This was useful when I used text-edit. Now I’m using Terminal and other apps to gather urls so its easier and more general to just copy the urls to the clipboard (CMD + C) and run this script. The applescript below opens each line of the copied text as a new safari window setting the line as the url.


set clipboard_contents to the clipboard as text
set urlList to paragraphs of clipboard_contents
set numURLs to (count urlList)
tell application "Safari"
	activate
	repeat with this_url in urlList
		if length of this_url is greater than 0 then
			set this_doc to make new document at end of documents
			set URL of this_doc to this_url
		end if
	end repeat
end tell

Note: There are lots of hacks to open a list of urls as tabs, find one and use it if your desire tabs. I didn’t post this for tabs because as far as I know opening a bunch of tabs always requires and awkward hack like using system events and keystrokes etc.