Copiar la ruta del fichero seleccionado en Finder usando Ruby

septiembre 11, 2011

Tras hablar sobre varios temas, en la última @nscoder_mad acabamos hablando del famoso lenguaje AppleScript. Personalmente es un lenguaje que he intentado varias veces aprender, pero nunca acaba de gustarme. Una de las conclusiones a las que llegamos es que es un lenguaje para no programadores. Quizás uno de los aspectos que no me gusta del lenguaje es que no veo a primera vista los diferentes elementos que conforman el código escrito con otros lenguajes como por ejemplo las palabras reservadas o incluso las propias sentencias en si mismo.

Un ejemplo de Applescript podría ser algo así:

tell application "Finder"
	set theFolder to make new folder at desktop
end tell

Sin duda algo que no se puede negar es su “verbosidad”, cualquiera que lo lea, mas o menos sabe lo que hace este mini-script. Pero, desde mi punto de vista, aunque muy literario, no me resulta cómodo leer código escrito de esta manera.

Pese a que a priori, puede parecer una opción muy asequible para gente que no ha programado en otros lenguajes, lo cierto es que incluso para ellos, crear algo nuevo en Applescript no es tan sencillo, en el fondo es un lenguaje de programación, y no entiende cualquier frase inglesa que podamos escribir.

Sin duda uno de los grandes beneficios de Applescript es su integración dentro de OSX, y como es capaz de interactuar con el sistema y el resto de aplicaciones de una forma muy sencilla.

De hecho, @j4n0 me comentaba que el tenía un script para copiar al portapapeles la ruta del fichero seleccionado en la ventana del Finder, cosa que es bastante útil.

Según lo expuesto hasta ahora, nuestra situación es que pese a que Applescript no parece un lenguaje muy atractivo pero es una “joya” a la hora de interactuar con el sistema operativo.

Por suerte hay una alternativa, gracias al ScriptingBridge, y como hablamos aquí y aquí, podemos usar otros lenguajes de programación como Ruby o Python para acceder a los mismos recursos que Applescript.

Actualmente mi situación es, me gustaría aprender Applescript por curiosidad, pero ¿para qué?. Lo mismo se puede hacer con Ruby, y es un lenguaje que ya conozco 🙂

Continuando con el caso anterior, el código en Ruby para copiar al portapapeles el fichero seleccionado en el Finder sería algo como:

require 'osx/cocoa'
include OSX
OSX.require_framework 'ScriptingBridge'

finder = SBApplication.applicationWithBundleIdentifier_ "com.apple.finder"
fileURL = NSURL.URLWithString_ finder.selection.get[0].URL
system "echo #{fileURL.path} | pbcopy"

Seguramente el código en Applescript ocupa menos lineas, pero al menos para mi, la versión en Ruby es mucho mas clara.

Como breve resumen al código, con la función applicationWithBundleIdentifier_ obtenemos la referencia al objeto “Finder” y usando el comando de consola pbcopy introducimos los datos al clipboard.

Pese a que funciona sin problemas desde la linea de comandos, este script es sobre todo útil cuando lo tenemos en la propia ventana del finder, para ello necesitamos encapsular el script en un bundle de Aplicación que OSX sepa como ejecutar.

Para ello, y aunque podamos hacerlo a mano, existe una utilidad llamada Platypus que nos automatizará el proceso. Una vez que tengamos el bundle solo nos queda arrastrarlo a la barra superior del finder y usarlo siempre que queramos.


Construyendo un Servicio en OSX

enero 16, 2011

Uno de los aspectos que pasan mas desapercibidos en OSX son los “Servicios”. Seguramente por el nombre pensareis que es un programa que se ejecuta de fondo en nuestro ordenador.

Si bien ese pensamiento no es del todo incorrecto, los servicios en OSX es algo mas. Con servicios me refiero a los elementos del menú que sale cuando pinchamos sobre el nombre del programa activo en la barra de menús.

La localización de dicho menú ha ido cambiando con el tiempo, en versiones anteriores ocupaban una posición mas destacada, pero poco a poco han ido pasando a un plano mas “oculto”.

Sin embargo, la posición de esta funcionalidad no nos tiene que despistar, ya que la utilidad que prestan es bastante buena.

Para comprobar su funcionamiento, solo tenemos que seleccionar algo de texto, e ir al menú Servicios. Desde dicho menú se nos dará la opción de hacer diversas cosas, como por ejemplo, crear una nota. Además no solo los servicios están accesibles desde este menú, si no que podremos acceder usando atajos de teclado. Por ejemplo, si pulsamos SHIFT+ ⌘+Y, crearemos la nota al instante.

Una de las grandes ventajas de los servicios es que están disponibles para todo el sistema, esto es, independientemente de la aplicación donde seleccionemos el texto, si pulsamos el atajo de teclado, crearemos una nota con el texto seleccionado.

Como podemos leer en la propia documentación de Apple, posibles servicios serian, reconocedores de texto en una imagen, cifrado de texto o incluso, aunque no venga en la guía de Apple, sería útil un servicio para insertar una cita de Chuck Norris en el documento actual 🙂

Desde el punto de vista de la programación, se pueden distinguir dos elementos, por un lado el propio servicio, y por otro la aplicación que interactua con él. La pieza que intercomunica ambas partes es el clipboard. Cuando pinchamos en un servicio, la aplicación pone los datos necesarios en el clipboard, e invoca al servicio. El servicio por su parte, recoge dichos datos, los procesa y los vuelve a poner en el clipboard. Finalmente, la aplicación recoge lo que haya en el clipboard y hace lo que considere oportuno con dichos datos.

En la entrada vamos a crear un servicio, que seleccionado texto, lo consulte en la wikipedia usando Safari.

El código que atiende al servicio, es sencillo, a grandes rasgos, los pasos que hay que dar son por un lado, crear el método al que se llamará, y por otro, registrar el servicio en el sistema.

Desafortunadamente, xcode no tiene plantillas para crear los servicios, por lo que tendremos que empezar desde un proyecto vacío que cree un ejecutable.

Una vez que tenemos el proyecto creado en el XCode, debemos añadir (si no lo estaba antes), el framework de Cocoa y Foundation, y añadir la clase que implementará el método que atiende el servicio.

En nuestro caso, dicha clase es WikipediaService


==== WikipediaService.h

#import <Cocoa/Cocoa.h>


@interface WikipediaService : NSObject {

}

- (void) wikipediaSearch: (NSPasteboard*) pboard 
				userData:(NSString*) theUserData 
				   error:(NSString**) theError;

@end

==== WikipediaService.m

@implementation WikipediaService

- (void) wikipediaSearch: (NSPasteboard*) pboard userData:(NSString*) theUserData error:(NSString**) theError
{
	// Test for strings on the pasteboard.
	NSArray *classes = [NSArray arrayWithObject:[NSString class]];
	NSDictionary *options = [NSDictionary dictionary];
	
	if (![pboard canReadObjectForClasses:classes options:options]) {
		*theError = NSLocalizedString(@"Error recuperando datos del pboard", 
									  @"pboard couldn't give string.");
		return;
	}
	
	// Get and encrypt the string.
	NSString *pboardString = [pboard stringForType:NSPasteboardTypeString];
	NSString *wikipediaString = [NSString stringWithFormat:@"http://en.wikipedia.org/wiki/%@", pboardString];
	NSURL *url = [[NSURL alloc] initWithString: wikipediaString];
	[[NSWorkspace sharedWorkspace] openURL:url];
	
	[pboard clearContents];
}

@end

Como vemos, el clipboard es la parte que comunica ambos procesos. Con el mensaje canReadObjectForClasses primero comprobamos si el clipboard contiene datos de tipo cadena, y luego con el mensaje stringForType del “pasteboard” obtenemos dicha cadena.

Por otro lado, en nuestro punto de entrada de la aplicación, debemos situar el código que registra el servicio en el sistema.

int main(int argc, char *argv[])
{

	NSRegisterServicesProvider([WikipediaService new], @"WikipediaService");
	NSUpdateDynamicServices();
	[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
	return 0;
}

Antes de ejecutar y probar nuestro servicio, nos queda una parte muy importante. En el fichero plist de la aplicación, debemos indicar los datos referentes al servicio. Que indicará al sistema como llamar y como mostrar la información asociada.

	<key>NSServices</key>
	<array>
		<dict>
			<key>NSMenuItem</key>
			<dict>
				<key>default</key>
				<string>Wikipedia Search</string>
			</dict>
			
			<key>NSKeyEquivalent</key>
            <dict>
                <key>default</key>
                <string>R</string>
            </dict>
			
			<key>NSMessage</key>
			<string>wikipediaSearch</string>
			
			<key>NSPortName</key>
			<string>WikipediaService</string>
			
			<key>NSSendTypes</key>
			<array>
				<string>NSStringPboardType</string>
				<string>NSPasteboardTypeString</string>
			</array>			
		</dict>
	</array>

Si os fijáis, en el campo, NSKeyEquivalent, podemos asignar una tecla, que sumada a Command, sera el shortcut para llamar a nuestro servicio. Si dicha letra esta en mayúsculas, ademas necesitaremos pulsar Shift.

Con todo ya terminado, solo nos queda instalarlo, para ello debemos copiar el “Bundle” que ha generado nuestro proyecto en XCode a ~/Library/Services. Para que el servicio sea reconocido, debemos cerrar sesion y volverla a inciar, pero existe una forma manual para forzar al sistema para que reconstruya el indice de servicios.

Esto es ejecutando el siguiente comando: /System/Library/CoreServices/pbs

Una vez que tenemos nuestro servicio instalado, solo tenemos que seleccionar cualquier texto para buscarlo en la wikipedia.