/* * Papilio - open-source reimplementation of Bowtie APIs for iPhone OS * http://r-ch.net/iphone/ * * Copyright (c) 2009 Yanik Magnan * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * DEPENDENCIES */ dlopen("/System/Library/Frameworks/MediaPlayer.framework/MediaPlayer", RTLD_GLOBAL); /* * SETTINGS * Bowtie plugins' settings are saved in Info.plist; we only really care about artwork * size for now * * To simplify things for users who don't want to mess with compiling Cycript, the document * title MUST be the same as the Cydget folder's name. (SomeBowlet.cydget would have * SomeBowlet as its title) */ var cydgetName = document.title; var settingsDict = [NSDictionary dictionaryWithContentsOfFile:"/System/Library/LockCydgets/"+cydgetName+".cydget/Info.plist"]; var artworkW = [[settingsDict objectForKey:"BTArtworkWidth"] intValue]; var artworkH = [[settingsDict objectForKey:"BTArtworkHeight"] intValue]; var artworkSize = [artworkW, artworkH]; /* * LCD * Automatically fills up the LCD background so you don't have to handle it. * * NOTE: Cydget has a CYShowDateTime property you can set to make the system's LCD * appear. The caveat is that since the Web View is currently set to be black, you * don't actually see through to it. (Though, once this is fixed, I can get rid of * this class for good.) */ var lcd = new function InternalLCD() { this._dateFormatter = [new NSDateFormatter init]; [this._dateFormatter setLocale:[NSLocale currentLocale]]; [this._dateFormatter setDateStyle:4]; [this._dateFormatter setTimeStyle:0]; this.formattedDate = function() { return [this._dateFormatter stringFromDate:[NSDate date]]; } this._timeFormatter = [new NSDateFormatter init]; [this._timeFormatter setLocale:[NSLocale currentLocale]]; [this._timeFormatter setDateStyle:0]; [this._timeFormatter setTimeStyle:1]; [this._timeFormatter setAMSymbol:""]; [this._timeFormatter setPMSymbol:""]; this.formattedTime = function() { return [this._timeFormatter stringFromDate:[NSDate date]]; } this.update = function() { document.getElementById('lcd').innerHTML = "

"+this.formattedTime()+"

"+this.formattedDate()+"

"; setTimeout(this.update, 500); } } /* * TRACK * Currently only implements property; is there even something else out there? */ var singletonTrack = new function InternalTrack() { // INTERNAL this._track = [MPMusicPlayerController.iPodMusicPlayer nowPlayingItem]; this._oldTrack = this._track; this._hasChanged = function() { return ![this._track isEqual:this._oldTrack]; } this._refresh = function() { this._oldTrack = this._track; this._track = [MPMusicPlayerController.iPodMusicPlayer nowPlayingItem]; } // PUBLIC APIs this.property = function(p) { // 'album' and 'length' are not valid properties for the MPMusicPlayerController // APIs, so we convert them to their iPhone equivalents here if (p == "album") return [this._track valueForProperty:"albumTitle"]; if (p == "length") return [this._track valueForProperty:"playbackDuration"]; return [this._track valueForProperty:p]; } } function convertToLegacyRating(r) { // The iPod APIs return a rating value from 0-5; Bowtie themes expect it on a scale from 0-100. return r * 20; } /* * iTUNES * For play state and controlling. */ var iTunes = new function InternalITunes() { // INTERNAL this._player = MPMusicPlayerController.iPodMusicPlayer; this._isPlaying = function() { return this.playState() == 1; } // PUBLIC APIs this.playState = function() { return [this._player playbackState]; } this.playPause = function() { if (this._isPlaying()) { [this._player pause]; } else { [this._player play]; } } this.previousTrack = function() { [this._player skipToPreviousItem]; } this.nextTrack = function() { [this._player skipToNextItem]; } this.rating = function() { return convertToLegacyRating(singletonTrack.property('rating')); } this.playerPosition = function() { return [this._player currentPlaybackTime]; } this.setRating = function(r) { } } /* * BOWTIE * Curl was accessing these when I was porting it; my understanding is * so will any other theme that has snapping things. I'm unsure how the * snapping APIs are, so these are mostly here to return _something_ in * the format Bowlets expect it and for them not to crash. * * The [set]preferenceForKey is here mostly due to Rexcon2 calling it; * I was unsure whether or not these preferences were meant to persist * so I'm currently only keeping them in memory temporarily. If anyone * can clarify if these are saved to disk, let me know and I'll figure * it out. */ var Bowtie = new function InternalBowtie() { this._prefs = [NSMutableDictionary dictionary]; this.setFrame = function(a,b,c,d) { } this.frame = function() { return [0,0,320,480]; } this.currentFrame = function() { return [0,0,320,480]; } this.preferenceForKey = function(key) { return [this._prefs objectForKey:key]; } this.setPreferenceForKey = function(value, key) { return [this._prefs setObject:value forKey:key]; } } /* * ARTWORK URL * Bowtie passes in a URL to artwork to whatever the artwork callback function is. * This takes care of generating such URLs. * * Note: Names containing quotes may become problematic so I might want to add hashing to this. * Also, this only supports one cached album art per album; I figure this is a good tradeoff to * sloppy performance. (This means stuff like the Ghosts/The Slip album art will use whichever * one was loaded first.) */ function _artworkUpdateURL() { if (singletonTrack._track) { var filename = "/tmp/AA-"+singletonTrack.property('album')+".png"; if (![[NSFileManager defaultManager] fileExistsAtPath:filename]) { var artwork = [singletonTrack.property('artwork') imageWithSize:artworkSize]; [UIImagePNGRepresentation(artwork) writeToFile:filename atomically:YES]; } return [NSURL fileURLWithPath:filename]; } return ""; } /* * REFRESH CYCLE * All of the actions that are refreshed regularly. Refresh cycles are (currently) every 1100 seconds. * This may change once I figure out how to intercept notifications from Cycript. */ var firstTimeDone = false; function refreshCycle() { singletonTrack._refresh(); if (singletonTrack._hasChanged() || !firstTimeDone) { // This is a not-so-pretty way of only calling the track update // and artwork update callbacks when there is an actual track // change; was needed for Curl to stop acting up in my case. trackUpdate(singletonTrack); artworkUpdate(_artworkUpdateURL()); firstTimeDone = true; } playerUpdate(); // Could have done the same thing here, but apparently it'd be inconsistent. :/ setTimeout(refreshCycle, 1100); } refreshCycle();