1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2026-01-30 07:45:45 +01:00

Basic Songbird 1.9a support.

--HG--
rename : xulmus/AUTHORS => melodactyl/AUTHORS
rename : xulmus/Makefile => melodactyl/Makefile
rename : xulmus/NEWS => melodactyl/NEWS
rename : xulmus/TODO => melodactyl/TODO
rename : xulmus/chrome.manifest => melodactyl/chrome.manifest
rename : xulmus/components/commandline-handler.js => melodactyl/components/commandline-handler.js
rename : xulmus/components/protocols.js => melodactyl/components/protocols.js
rename : xulmus/content/config.js => melodactyl/content/config.js
rename : xulmus/content/dactyl.dtd => melodactyl/content/dactyl.dtd
rename : xulmus/content/library.js => melodactyl/content/library.js
rename : xulmus/content/logo.png => melodactyl/content/logo.png
rename : xulmus/content/xulmus.xul => melodactyl/content/melodactyl.xul
rename : xulmus/content/player.js => melodactyl/content/player.js
rename : xulmus/content/xulmus.svg => melodactyl/content/xulmus.svg
rename : xulmus/contrib/vim/Makefile => melodactyl/contrib/vim/Makefile
rename : xulmus/contrib/vim/ftdetect/xulmus.vim => melodactyl/contrib/vim/ftdetect/melodactyl.vim
rename : xulmus/contrib/vim/mkvimball.txt => melodactyl/contrib/vim/mkvimball.txt
rename : xulmus/contrib/vim/syntax/xulmus.vim => melodactyl/contrib/vim/syntax/melodactyl.vim
rename : xulmus/defaults/preferences/dactyl.js => melodactyl/defaults/preferences/dactyl.js
rename : xulmus/install.rdf => melodactyl/install.rdf
rename : xulmus/locale/en-US/all.xml => melodactyl/locale/en-US/all.xml
rename : xulmus/locale/en-US/autocommands.xml => melodactyl/locale/en-US/autocommands.xml
rename : xulmus/locale/en-US/browsing.xml => melodactyl/locale/en-US/browsing.xml
rename : xulmus/locale/en-US/dactyl.dtd => melodactyl/locale/en-US/dactyl.dtd
rename : xulmus/locale/en-US/gui.xml => melodactyl/locale/en-US/gui.xml
rename : xulmus/locale/en-US/intro.xml => melodactyl/locale/en-US/intro.xml
rename : xulmus/locale/en-US/player.xml => melodactyl/locale/en-US/player.xml
rename : xulmus/locale/en-US/tabs.xml => melodactyl/locale/en-US/tabs.xml
rename : xulmus/skin/icon.png => melodactyl/skin/icon.png
This commit is contained in:
Kris Maglione
2010-10-02 10:44:19 -04:00
parent c0a5a680b8
commit a59d88fee7
40 changed files with 84 additions and 79 deletions

View File

@@ -0,0 +1,308 @@
// Copyright (c) 2009 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2009 by Prathyush Thota <prathyushthota@gmail.com>
// Copyright (c) 2009 by Doug Kearns <dougkearns@gmail.com>
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
Components.utils.import("resource://gre/modules/utils.js"); // XXX
const Config = Module("config", ConfigBase, {
init: function init() {
init.supercall(this);
// TODO: mention this to SB devs, they seem keen to provide these
// functions to make porting from FF as simple as possible.
window.toJavaScriptConsole = function () {
toOpenWindowByType("global:console", "chrome://global/content/console.xul");
};
window.BrowserStop = function () {
SBGetBrowser().mCurrentBrowser.stop();
};
},
/*** required options, no checks done if they really exist, so be careful ***/
name: "Xulmus",
host: "Songbird",
/*** optional options, there are checked for existence and a fallback provided ***/
features: ["bookmarks", "hints", "marks", "history", "quickmarks", "session", "tabs", "player"],
defaults: {
guioptions: "mprb",
showtabline: 2,
titlestring: "Xulmus"
},
guioptions: {
m: ["Menubar", ["main-menubar"]],
T: ["Toolbar", ["nav-bar"]],
p: ["Player controls", ["player_wrapper"]]
},
get isPlayerWindow() SBGetBrowser().mCurrentTab == SBGetBrowser().mediaTab,
// focusContent() focuses this widget gSongbirdWindowController takes care of the focus.
get visualbellWindow() document.getElementById(this.mainWindowId),
styleableChrome: ["chrome://purplerain/content/xul/mainplayer.xul"],
autocommands: {
BookmarkAdd: "Triggered after a page is bookmarked",
ColorScheme: "Triggered after a color scheme has been loaded",
DOMLoad: "Triggered when a page's DOM content has fully loaded",
DownloadPost: "Triggered when a download has completed",
Fullscreen: "Triggered when the browser's fullscreen state changes",
LocationChange: "Triggered when changing tabs or when navigation to a new location",
PageLoadPre: "Triggered after a page load is initiated",
PageLoad: "Triggered when a page gets (re)loaded/opened",
ShellCmdPost: "Triggered after executing a shell command with :!cmd",
TrackChangePre: "Triggered before a playing track is changed",
TrackChange: "Triggered after a playing track has changed",
ViewChangePre: "Triggered before a sequencer view is changed",
ViewChange: "Triggered after a sequencer view is changed",
StreamStart: "Triggered after a stream has started",
StreamPause: "Triggered after a stream has paused",
StreamEnd: "Triggered after a stream has ended",
StreamStop: "Triggered after a stream has stopped",
Enter: "Triggered after Songbird starts",
LeavePre: "Triggered before exiting Songbird, just before destroying each module",
Leave: "Triggered before exiting Songbird",
},
// TODO: remove those which don't make sense, can't be provided.
dialogs: {
about: ["About Songbird",
function () { window.openDialog("chrome://songbird/content/xul/about.xul", "_blank", "chrome,dialog,modal,centerscreen"); }],
addons: ["Manage Add-ons",
function () { SBOpenPreferences("paneAddons"); }],
checkupdates: ["Check for updates",
function () { window.checkForUpdates(); }],
cleardata: ["Clear private data",
function () { Sanitizer.showUI(); }],
cookies: ["List your cookies",
function () { window.toOpenWindowByType("Browser:Cookies", "chrome://browser/content/preferences/cookies.xul", "chrome,dialog=no,resizable"); }],
console: ["JavaScript console",
function () { window.toJavaScriptConsole(); }],
dominspector: ["DOM Inspector",
function () { try { window.inspectDOMDocument(content.document); } catch (e) { dactyl.echoerr("DOM Inspector extension not installed"); } }],
downloads: ["Manage Downloads",
function () { window.toOpenWindowByType("Download:Manager", "chrome://mozapps/content/downloads/downloads.xul", "chrome,dialog=no,resizable"); }],
jumpto: ["Jump to a media item",
function () { onJumpToFileKey(); }],
newsmartplaylist: ["Open the file selector dialog",
function () { SBNewSmartPlaylist(); }],
openfile: ["Open the file selector dialog",
function () { SBFileOpen(); }],
pagesource: ["View page source",
function () { window.BrowserViewSourceOfDocument(content.document); }],
places: ["Places Organizer: Manage your bookmarks and history",
function () { PlacesCommandHook.showPlacesOrganizer(ORGANIZER_ROOT_BOOKMARKS); }],
preferences: ["Show Songbird preferences dialog",
function () { window.openPreferences(); }],
printsetup: ["Setup the page size and orientation before printing",
function () { PrintUtils.showPageSetup(); }],
print: ["Show print dialog",
function () { PrintUtils.print(); }],
saveframe: ["Save frame to disk",
function () { window.saveFrameDocument(); }],
savepage: ["Save page to disk",
function () { window.saveDocument(window.content.document); }],
searchengines: ["Manage installed search engines",
function () { window.openDialog("chrome://browser/content/search/engineManager.xul", "_blank", "chrome,dialog,modal,centerscreen"); }],
selectionsource: ["View selection source",
function () { buffer.viewSelectionSource(); }],
subscribe: ["Add a new subscription",
function () { SBSubscribe(); }]
},
focusChange: function () {
// Switch to -- PLAYER -- mode for Songbird Media Player.
if (config.isPlayerWindow)
dactyl.mode = modes.PLAYER;
else
dactyl.mode = modes.NORMAL;
},
hasTabbrowser: true,
// FIXME: unless I'm seeing double in in the wee small hours gBrowser is
// first set from getBrowser which they've deprecated in FF.
get tabbrowser() window.getBrowser(),
get browser() window.getBrowser(),
modes: [["PLAYER", { char: "p" }]],
removeTab: function (tab) {
if (config.tabbrowser.mTabs.length > 1)
config.tabbrowser.removeTab(tab);
else {
if (buffer.URL != "about:blank" || window.getWebNavigation().sessionHistory.count > 0) {
dactyl.open("about:blank", dactyl.NEW_BACKGROUND_TAB);
config.tabbrowser.removeTab(tab);
}
else
dactyl.beep();
}
},
scripts: [
"browser",
"bookmarks",
"history",
"quickmarks",
"tabs",
"player",
"library"
],
// FIXME: tab arg and media tab exception?
stop: function (tab) {
SBGetBrowser().mCurrentBrowser.stop();
}
}, {
/**
* Shows or hides the main service pane.
*
* @param {boolean} value Show the service pane if true, hide it if false.
*/
showServicePane: function (value) {
const key = "splitter.servicepane_splitter.was_collapsed";
gServicePane.open = value;
SBDataSetBoolValue(key, gServicePane.open);
},
/**
* Opens the display panel with the specified <b>id<b>.
*
* @param {string} id The ID of the display pane.
*/
openDisplayPane: function (id) {
if (id == "servicepane")
this.showServicePane(true);
else {
let pane = document.getElementById(id);
let manager = services.get("displayPaneManager");
let paneinfo = manager.getPaneInfo(pane._lastURL.stringValue);
if (!paneinfo)
paneinfo = manager.defaultPaneInfo;
pane.loadContent(paneinfo);
}
},
/**
* Closes the display panel with the specified <b>id</b>
*
* @param {string} id The ID of the display pane.
*/
closeDisplayPane: function (id) {
if (id == "servicepane")
this.showServicePane(false);
else
document.getElementById(id).hide();
},
// FIXME: best way to format these args? Hyphenated? One word like :dialog?
/**
* @property {object} A map of display pane command argument strings to
* panel element IDs.
*/
displayPanes: {
"service pane left": "servicepane",
"content pane bottom": "displaypane_contentpane_bottom",
"service pane bottom": "displaypane_servicepane_bottom",
"right sidebar": "displaypane_right_sidebar"
}
}, {
commands: function () {
commands.add(["dpcl[ose]"],
"Close a display pane",
function (args) {
let arg = args.literalArg;
if (arg in Config.displayPanes)
Config.closeDisplayPane(Config.displayPanes[arg]);
else
dactyl.echoerr("E475: Invalid argument: " + arg);
},
{
argCount: "1",
completer: function (context) completion.displayPane(context),
literal: 0
});
// TODO: this should accept a second arg to specify content
commands.add(["displayp[ane]", "dp[ane]", "dpope[n]"],
"Open a display pane",
function (args) {
let arg = args.literalArg;
if (arg in Config.displayPanes)
Config.openDisplayPane(Config.displayPanes[arg]);
// TODO: focus when we have better key handling of these extended modes
else
dactyl.echoerr("E475: Invalid argument: " + arg);
},
{
argCount: "1",
completer: function (context) completion.displayPane(context),
literal: 0
});
commands.add(["pref[erences]", "prefs"],
"Show " + config.host + " preferences",
function (args) {
if (args.bang) { // open Songbird settings GUI dialog
dactyl.open("about:config",
(options["newtab"] && options.get("newtab").has("all", "prefs"))
? dactyl.NEW_TAB : dactyl.CURRENT_TAB);
}
else
window.openPreferences();
},
{
argCount: "0",
bang: true
});
},
completion: function () {
completion.displayPane = function (context) {
context.title = ["Display Pane"];
context.completions = Config.displayPanes; // FIXME: useful description etc
};
},
modes: function () {
this.ignoreKeys = {
"<Return>": modes.NORMAL | modes.INSERT,
"<Space>": modes.NORMAL | modes.INSERT,
"<Up>": modes.NORMAL | modes.INSERT,
"<Down>": modes.NORMAL | modes.INSERT
};
},
options: function () {
// TODO: SB doesn't explicitly support an offline mode. Should we? --djk
options.add(["online"],
"Set the 'work offline' option",
"boolean", true,
{
setter: function (value) {
const ioService = services.get("io");
ioService.offline = !value;
options.setPref("browser.offline", ioService.offline);
return value;
},
getter: function () !services.get("io").offline
});
},
services: function () {
services.add("displayPaneManager", "@songbirdnest.com/Songbird/DisplayPane/Manager;1", Ci.sbIDisplayPaneManager);
services.add("mediaPageManager", "@songbirdnest.com/Songbird/MediaPageManager;1", Ci.sbIMediaPageManager);
services.add("propertyManager","@songbirdnest.com/Songbird/Properties/PropertyManager;1", Ci.sbIPropertyManager);
services.addClass("mutablePropertyArray", "@songbirdnest.com/Songbird/Properties/MutablePropertyArray;1",
Ci.sbIMutablePropertyArray);
}
});
// vim: set fdm=marker sw=4 ts=4 et:

View File

@@ -0,0 +1,18 @@
<!ENTITY % dactylBranding SYSTEM "chrome://branding/locale/brand.dtd">
%dactylBranding;
<!ENTITY dactyl.mainWindow "mainplayer">
<!ENTITY dactyl.commandContainer "&dactyl.mainWindow;">
<!ENTITY dactyl.name "xulmus">
<!ENTITY dactyl.appName "Xulmus">
<!ENTITY dactyl.idName "XULMUS">
<!ENTITY dactyl.host "&brandShortName;">
<!ENTITY dactyl.hostbin "songbird">
<!ENTITY dactyl.fileExt "xulmus">
<!ENTITY dactyl.statusBefore "statusbar-display">
<!ENTITY dactyl.statusAfter "">
<!ENTITY xmlns.dactyl "http://vimperator.org/namespaces/liberator">
<!ENTITY xmlns.html "http://www.w3.org/1999/xhtml">
<!ENTITY xmlns.xul "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

View File

@@ -0,0 +1,64 @@
// Copyright (c) 2009 by Prathyush Thota <prathyushthota@gmail.com>
// Copyright (c) 2009 by Doug Kearns <dougkearns@gmail.com>
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
// TODO: flesh this out
const Library = Module("library", {
init: function () {
this.MAIN_LIBRARY = LibraryUtils.mainLibrary;
},
/**
* Converts an XPCOM enumerator to a JavaScript array.
*
* @param {nsISimpleEnumerator|nsIStringEnumerator|nsIArray} enum The enumerator to convert.
* @returns {Array}
*/
_toJSArray: function _toJSArray(enum) ArrayConverter.JSArray(enum),
/**
* Returns an array of all the artist names in the main library.
*
* @returns {string[]}
*/
getArtists: function getArtists() this._toJSArray(this.MAIN_LIBRARY.getDistinctValuesForProperty(SBProperties.artistName)),
// FIXME: Prathyush do we really want to remove duplicates? If so, why not tracks too? --djk
/**
* Returns an array of all the album names for <b>artist</b> in the
* main library.
*
* @param {string} artist The artist's name.
* @returns {string[]}
*/
getAlbums: function getAlbums(artist) {
let albums = this._toJSArray(this.MAIN_LIBRARY.getItemsByProperty(SBProperties.artistName, artist))
.map(function (track) track.getProperty(SBProperties.albumName));
return util.Array.uniq(albums);
},
/**
* Returns an array of all the track names for <b>artist</b> and
* <b>album</b> in the main library.
*
* @param {string} artist The artist's name.
* @param {string} album The album's name.
* @returns {string[]}
*/
getTracks: function getTracks(artist, album) {
let properties = services.create("mutablePropertyArray");
properties.appendProperty(SBProperties.artistName, artist);
properties.appendProperty(SBProperties.albumName, album);
return this._toJSArray(this.MAIN_LIBRARY.getItemsByProperties(properties))
.map(function (track) track.getProperty(SBProperties.trackName));
}
}, {
}, {
});
// vim: set fdm=marker sw=4 ts=4 et:

BIN
melodactyl/content/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,52 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://xulmus/skin/xulmus.css" type="text/css"?>
<overlay id="xulmus"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:nc="http://home.netscape.com/NC-rdf#"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<menupopup id="viewSidebarMenu">
<menuitem observes="xulmus-viewAddonsSidebar" label="Add-ons" accesskey="A"/>
<menuitem observes="xulmus-viewConsoleSidebar" label="Console" accesskey="C"/>
<menuitem observes="xulmus-viewDownloadsSidebar" label="Downloads" accesskey="D"/>
<menuitem observes="xulmus-viewPreferencesSidebar" label="Preferences" accesskey="P"/>
</menupopup>
<broadcasterset id="mainBroadcasterSet">
<broadcaster id="xulmus-viewAddonsSidebar"
autoCheck="false"
type="checkbox"
group="sidebar"
sidebarurl="chrome://mozapps/content/extensions/extensions.xul"
sidebartitle="Add-ons"
oncommand="toggleSidebar('xulmus-viewAddonsSidebar');"/>
<broadcaster id="xulmus-viewConsoleSidebar"
autoCheck="false"
type="checkbox"
group="sidebar"
sidebarurl="chrome://global/content/console.xul"
sidebartitle="Console"
oncommand="toggleSidebar('xulmus-viewConsoleSidebar');"/>
<broadcaster id="xulmus-viewDownloadsSidebar"
autoCheck="false"
type="checkbox"
group="sidebar"
sidebarurl="chrome://mozapps/content/downloads/downloads.xul"
sidebartitle="Downloads"
oncommand="toggleSidebar('xulmus-viewDownloadsSidebar');"/>
<broadcaster id="xulmus-viewPreferencesSidebar"
autoCheck="false"
type="checkbox"
group="sidebar"
sidebarurl="about:config"
sidebartitle="Preferences"
oncommand="toggleSidebar('xulmus-viewPreferencesSidebar');"/>
</broadcasterset>
</overlay>
<!-- vim: set fdm=marker sw=4 ts=4 et: -->

View File

@@ -0,0 +1,799 @@
// Copyright (c) 2009 by Prathyush Thota <prathyushthota@gmail.com>
// Copyright (c) 2009 by Doug Kearns <dougkearns@gmail.com>
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
const Player = Module("player", {
init: function init() {
this._lastSearchString = "";
this._lastSearchIndex = 0;
this._lastSearchView = this._currentView; //XXX
// Get the focus to the visible playlist first
//window._SBShowMainLibrary();
gMM.addListener(this._mediaCoreListener);
},
destroy: function destroy() {
gMM.removeListener(this._mediaCoreListener);
},
/**
* Moves the track position <b>interval</b> milliseconds forwards or
* backwards.
*
* @param {number} interval The time interval (ms) to move the track
* position.
* @param {boolean} direction The direction in which to move the track
* position, forward if true otherwise backwards.
* @private
*/
_seek: function _seek(interval, direction) {
let position = gMM.playbackControl ? gMM.playbackControl.position : 0;
player.seekTo(position + (direction ? interval : -interval));
},
/**
* Listens for media core events and in response dispatches the appropriate
* autocommand events.
* @private
*/
_mediaCoreListener: {
onMediacoreEvent: function (event) {
switch (event.type) {
case Ci.sbIMediacoreEvent.BEFORE_TRACK_CHANGE:
dactyl.log("Before track changed: " + event.data);
autocommands.trigger("TrackChangePre", { track: event.data });
break;
case Ci.sbIMediacoreEvent.TRACK_CHANGE:
autocommands.trigger("TrackChange", { track: event.data });
break;
case Ci.sbIMediacoreEvent.BEFORE_VIEW_CHANGE:
dactyl.log("Before view changed: " + event.data);
autocommands.trigger("ViewChangePre", { view: event.data });
break;
case Ci.sbIMediacoreEvent.VIEW_CHANGE:
dactyl.log("View changed: " + event.data);
autocommands.trigger("ViewChange", { view: event.data });
break;
case Ci.sbIMediacoreEvent.STREAM_START:
dactyl.log("Track started: " + gMM.sequencer.currentItem);
autocommands.trigger("StreamStart", { track: gMM.sequencer.currentItem });
break;
case Ci.sbIMediacoreEvent.STREAM_PAUSE:
dactyl.log("Track paused: " + gMM.sequencer.currentItem);
autocommands.trigger("StreamPause", { track: gMM.sequencer.currentItem });
break;
case Ci.sbIMediacoreEvent.STREAM_END:
dactyl.log("Track ended: " + gMM.sequencer.currentItem);
autocommands.trigger("StreamEnd", { track: gMM.sequencer.currentItem });
break;
case Ci.sbIMediacoreEvent.STREAM_STOP:
dactyl.log("Track stopped: " + gMM.sequencer.currentItem);
autocommands.trigger("StreamStop", { track: gMM.sequencer.currentItem });
break;
}
}
},
/** @property {sbIMediaListView} The current media list view. @private */
get _currentView() SBGetBrowser().currentMediaListView,
/**
* @property {number} The player volume in the range 0.0-1.0.
*/
get volume() gMM.volumeControl.volume,
set volume(value) {
gMM.volumeControl.volume = value;
},
/**
* Focuses the specified media item in the current media list view.
*
* @param {sbIMediaItem} mediaItem The media item to focus.
*/
focusTrack: function focusTrack(mediaItem) {
SBGetBrowser().mediaTab.mediaPage.highlightItem(this._currentView.getIndexForItem(mediaItem));
},
/**
* Plays the currently selected media item. If no item is selected the
* first item in the current media view is played.
*/
play: function play() {
// Check if there is any selection in place, else play first item of the visible view.
// TODO: this approach, or similar, should be generalised for all commands, PT? --djk
if (this._currentView.selection.count != 0)
gMM.sequencer.playView(this._currentView,
this._currentView.getIndexForItem(this._currentView.selection.currentMediaItem));
else
gMM.sequencer.playView(SBGetBrowser().currentMediaListView, 0);
this.focusTrack(gMM.sequencer.currentItem);
},
/**
* Stops playback of the currently playing media item.
*/
stop: function stop() {
gMM.sequencer.stop();
},
/**
* Plays the next media item in the current media view.
*/
next: function next() {
["cmd_control_next", "cmd_find_current_track"].forEach(gSongbirdWindowController.doCommand);
},
/**
* Plays the previous media item in the current media view.
*/
previous: function previous() {
["cmd_control_previous", "cmd_find_current_track"].forEach(gSongbirdWindowController.doCommand);
},
/**
* Toggles the play/pause status of the current media item.
*/
togglePlayPause: function togglePlayPause() {
["cmd_control_playpause", "cmd_find_current_track"].forEach(gSongbirdWindowController.doCommand);
},
/**
* Toggles the shuffle status of the sequencer.
*/
toggleShuffle: function toggleShuffle() {
if (gMM.sequencer.mode != gMM.sequencer.MODE_SHUFFLE)
gMM.sequencer.mode = gMM.sequencer.MODE_SHUFFLE;
else
gMM.sequencer.mode = gMM.sequencer.MODE_FORWARD;
},
// FIXME: not really toggling (depending on your definition) - good enough for now.
/**
* Toggles between the sequencer's three repeat modes: Repeat-One,
* Repeat-All and Repeat-None.
*/
toggleRepeat: function toggleRepeat() {
switch (gMM.sequencer.repeatMode) {
case gMM.sequencer.MODE_REPEAT_NONE:
gMM.sequencer.repeatMode = gMM.sequencer.MODE_REPEAT_ONE;
break;
case gMM.sequencer.MODE_REPEAT_ONE:
gMM.sequencer.repeatMode = gMM.sequencer.MODE_REPEAT_ALL;
break;
case gMM.sequencer.MODE_REPEAT_ALL:
gMM.sequencer.repeatMode = gMM.sequencer.MODE_REPEAT_NONE;
break;
default:
gMM.sequencer.repeatMode = gMM.sequencer.MODE_REPEAT_NONE;
break;
}
},
/**
* Seeks forward <b>interval</b> milliseconds in the currently playing
* track.
*
* @param {number} interval The time interval (ms) to advance the
* current track.
*/
seekForward: function seekForward(interval) {
this._seek(interval, true);
},
/**
* Seeks backwards <b>interval</b> milliseconds in the currently
* playing track.
*
* @param {number} interval The time interval (ms) to rewind the
* current track.
*/
seekBackward: function seekBackward(interval) {
this._seek(interval, false);
},
/**
* Seeks to a specific position in the currently playing track.
*
* @param {number} The new position (ms) in the track.
*/
seekTo: function seekTo(position) {
// FIXME: if not playing
if (!gMM.playbackControl)
this.play();
let min = 0;
let max = gMM.playbackControl.duration - 5000; // TODO: 5s buffer like cmus desirable?
gMM.playbackControl.position = util.Math.constrain(position, min, max);
},
/**
* Increases the volume by 5% of the maximum volume.
*/
increaseVolume: function increaseVolume() {
this.volume = util.Math.constrain(this.volume + 0.05, 0, 1);
},
/**
* Decreases the volume by 5% of the maximum volume.
*/
decreaseVolume: function decreaseVolume() {
this.volume = util.Math.constrain(this.volume - 0.05, 0, 1);
},
// TODO: Document what this buys us over and above cmd_find_current_track
/**
* Focuses the currently playing track.
*/
focusPlayingTrack: function focusPlayingTrack() {
this.focusTrack(gMM.sequencer.currentItem);
},
/**
* Searches the current media view for <b>str</b>
*
* @param {string} str The search string.
*/
searchView: function searchView(str) {
let search = _getSearchString(this._currentView);
let searchString = "";
if (search != "") // XXX
searchString = str + " " + search;
else
searchString = str;
this._lastSearchString = searchString;
let searchView = LibraryUtils.createStandardMediaListView(this._currentView.mediaList, searchString);
if (searchView.length) {
this._lastSearchView = searchView;
this._lastSearchIndex = 0;
this.focusTrack(searchView.getItemByIndex(this._lastSearchIndex));
}
else
dactyl.echoerr("E486 Pattern not found: " + searchString, commandline.FORCE_SINGLELINE);
},
/**
* Repeats the previous view search.
*
* @param {boolean} reverse Search in the reverse direction to the previous
* search.
*/
searchViewAgain: function searchViewAgain(reverse) {
function echo(str) {
this.timeout(function () {
commandline.echo(str, commandline.HL_WARNINGMSG, commandline.APPEND_TO_MESSAGES | commandline.FORCE_SINGLELINE);
}, 0);
}
if (reverse) {
if (this._lastSearchIndex == 0) {
this._lastSearchIndex = this._lastSearchView.length - 1;
echo("Search hit TOP, continuing at BOTTOM");
}
else
this._lastSearchIndex = this._lastSearchIndex - 1;
}
else {
if (this._lastSearchIndex == (this._lastSearchView.length - 1)) {
this._lastSearchIndex = 0;
echo("Search hit BOTTOM, continuing at TOP");
}
else
this._lastSearchIndex = this._lastSearchIndex + 1;
}
// TODO: Implement for "?" --ken
commandline.echo("/" + this._lastSearchString, null, commandline.FORCE_SINGLELINE);
this.focusTrack(this._lastSearchView.getItemByIndex(this._lastSearchIndex));
},
/**
* The search dialog keypress callback.
*
* @param {string} str The contents of the search dialog.
*/
onSearchKeyPress: function onSearchKeyPress(str) {
if (options["incsearch"])
this.searchView(str);
},
/**
* The search dialog submit callback.
*
* @param {string} str The contents of the search dialog.
*/
onSearchSubmit: function onSearchSubmit(str) {
this.searchView(str);
},
/**
* The search dialog cancel callback.
*/
onSearchCancel: function onSearchCancel() {
// TODO: restore the view state if altered by an 'incsearch' search
},
/**
* Returns an array of all available playlists.
*
* @returns {sbIMediaList[]}
*/
getPlaylists: function getPlaylists() {
let mainLibrary = LibraryUtils.mainLibrary;
let playlists = [mainLibrary];
let listener = {
onEnumerationBegin: function () { },
onEnumerationEnd: function () { },
onEnumeratedItem: function (list, item) {
// FIXME: why are there null items and duplicates?
if (!playlists.some(function (list) list.name == item.name) && item.name != null)
playlists.push(item);
return Ci.sbIMediaListEnumerationListener.CONTINUE;
}
};
mainLibrary.enumerateItemsByProperty("http://songbirdnest.com/data/1.0#isList", "1", listener);
return playlists;
},
/**
* Plays the media item at <b>index</b> in <b>playlist</b>.
*
* @param {sbIMediaList} playlist
* @param {number} index
*/
playPlaylist: function playPlaylist(playlist, index) {
gMM.sequencer.playView(playlist.createView(), index);
},
/**
* Returns an array of all available media pages.
*
* @returns {sbIMediaPageInfo[]}
*/
getMediaPages: function getMediaPages() {
let list = SBGetBrowser().currentMediaPage.mediaListView.mediaList;
let pages = services.get("mediaPageManager").getAvailablePages(list);
return ArrayConverter.JSArray(pages).map(function (page) page.QueryInterface(Ci.sbIMediaPageInfo));
},
/**
* Loads the the specified media page into <b>view</b> with the given
* <b>list</b> of media items.
*
* @param {sbIMediaPage} page
* @param {sbIMediaList} list
* @param {sbIMediaView} view
*/
loadMediaPage: function loadMediaPage(page, list, view) {
services.get("mediaPageManager").setPage(list, page);
SBGetBrowser().loadMediaList(list, null, null, view, null);
},
/**
* Applys the specified <b>rating<b> to <b>mediaItem<b>.
*
* @param {sbIMediaItem} mediaItem The media item to rate.
* @param {number} rating The star rating (1-5).
*/
rateMediaItem: function rateMediaItem(mediaItem, rating) {
mediaItem.setProperty(SBProperties.rating, rating);
},
// TODO: add more fields, and generate the list dynamically. PT should the
// available fields reflect only the visible view fields or offer others? --djk
/**
* Sorts the current media view by <b>field</b> in the order specified by
* <b>ascending</b>.
*
* @param {string} field The sort field.
* @param {boolean} ascending If true sort in ascending order, otherwise in
* descending order.
*/
sortBy: function sortBy(field, ascending) {
let order = ascending ? "a" : "d";
let properties = services.create("mutablePropertyArray");
properties.strict = false;
switch (field) {
case "title":
properties.appendProperty(SBProperties.trackName, order);
break;
case "time":
properties.appendProperty(SBProperties.duration, order);
break;
case "artist":
properties.appendProperty(SBProperties.artistName, order);
break;
case "album":
properties.appendProperty(SBProperties.albumName, order);
break;
case "genre":
properties.appendProperty(SBProperties.genre, order);
break;
case "rating":
properties.appendProperty(SBProperties.rating, order);
break;
default:
properties.appendProperty(SBProperties.trackName, order);
break;
}
this._currentView.setSort(properties);
}
}, {
}, {
commandline: function () {
commandline.registerCallback("change", modes.SEARCH_VIEW_FORWARD, this.closure.onSearchKeyPress);
commandline.registerCallback("submit", modes.SEARCH_VIEW_FORWARD, this.closure.onSearchSubmit);
commandline.registerCallback("cancel", modes.SEARCH_VIEW_FORWARD, this.closure.onSearchCancel);
},
commands: function () {
commands.add(["f[ilter]"],
"Filter tracks based on keywords {genre/artist/album/track}",
function (args) {
let library = LibraryUtils.mainLibrary;
let view = LibraryUtils.createStandardMediaListView(LibraryUtils.mainLibrary, args.literalArg);
if (view.length == 0)
dactyl.echoerr("No Tracks matching the keywords");
else {
SBGetBrowser().loadMediaList(LibraryUtils.mainLibrary, null, null, view,
"chrome://songbird/content/mediapages/filtersPage.xul");
// TODO: make this this.focusTrack work ?
this.focusTrack(view.getItemByIndex(0));
}
},
{
argCount: "1",
literal: 0
//completer: function (context, args) completion.tracks(context, args);
});
commands.add(["load"],
"Load a playlist",
function (args) {
let arg = args.literalArg;
if (arg) {
// load the selected playlist/smart playlist
let playlists = player.getPlaylists();
for ([i, list] in Iterator(playlists)) {
if (util.compareIgnoreCase(arg, list.name) == 0) {
SBGetBrowser().loadMediaList(playlists[i]);
this.focusTrack(this._currentView.getItemByIndex(0));
return;
}
}
dactyl.echoerr("E475: Invalid argument: " + arg);
}
else {
// load main library if there are no args
_SBShowMainLibrary();
}
},
{
argCount: "?",
completer: function (context, args) completion.playlist(context, args),
literal: 0
});
// TODO: better off as a single command (:player play) or cmus compatible (:player-play)? --djk
commands.add(["playerp[lay]"],
"Play track",
function () { player.play(); });
commands.add(["playerpa[use]"],
"Pause/unpause track",
function () { player.togglePlayPause(); });
commands.add(["playern[ext]"],
"Play next track",
function () { player.next(); });
commands.add(["playerpr[ev]"],
"Play previous track",
function () { player.previous(); });
commands.add(["players[top]"],
"Stop track",
function () { player.stop(); });
commands.add(["see[k]"],
"Seek to a track position",
function (args) {
let arg = args[0];
// intentionally supports 999:99:99
if (!/^[+-]?(\d+[smh]?|(\d+:\d\d:|\d+:)?\d{2})$/.test(arg))
return void dactyl.echoerr("E475: Invalid argument: " + arg);
function ms(t, m) Math.abs(parseInt(t, 10) * { s: 1000, m: 60000, h: 3600000 }[m])
if (/:/.test(arg)) {
let [seconds, minutes, hours] = arg.split(":").reverse();
hours = hours || 0;
var value = ms(seconds, "s") + ms(minutes, "m") + ms(hours, "h");
}
else {
if (!/[smh]/.test(arg.substr(-1)))
arg += "s"; // default to seconds
value = ms(arg.substring(arg, arg.length - 1), arg.substr(-1));
}
if (/^[-+]/.test(arg))
arg[0] == "-" ? player.seekBackward(value) : player.seekForward(value);
else
player.seekTo(value);
},
{ argCount: "1" });
commands.add(["mediav[iew]"],
"Change the current media view",
function (args) {
// FIXME: is this a SB restriction? --djk
if (!SBGetBrowser().currentMediaPage)
return void dactyl.echoerr("Exxx: Can only set the media view from the media tab"); // XXX
let arg = args[0];
if (arg) {
let pages = player.getMediaPages();
for ([, page] in Iterator(pages)) {
if (util.compareIgnoreCase(arg, page.contentTitle) == 0) {
player.loadMediaPage(page, SBGetBrowser().currentMediaListView.mediaList,
SBGetBrowser().currentMediaListView);
return;
}
}
dactyl.echoerr("E475: Invalid argument: " + arg);
}
},
{
argCount: "1",
completer: function (context) completion.mediaView(context),
literal: 0
});
commands.add(["sort[view]"],
"Sort the current media view",
function (args) {
let order = args["-order"] || "up";
player.sortBy(args[0], order == "up");
},
{
argCount: "1",
completer: function (context) completion.mediaListSort(context),
options: [[["-order", "-o"], commands.OPTION_STRING,
function (arg) /^(up|down)$/.test(arg),
function () [["up", "Sort in ascending order"], ["down", "Sort in descending order"]]]]
});
// FIXME: use :add -q like cmus? (not very vim-like are it's multi-option commands) --djk
commands.add(["qu[eue]"],
"Queue tracks by artist/album/track",
function (args) {
let properties = services.create("mutablePropertyArray");
// args
switch (args.length) {
case 3:
properties.appendProperty(SBProperties.trackName, args[2]);
case 2:
properties.appendProperty(SBProperties.albumName, args[1]);
case 1:
properties.appendProperty(SBProperties.artistName, args[0]);
break;
default:
break;
}
let library = LibraryUtils.mainLibrary;
let mainView = library.createView();
gMM.sequencer.playView(mainView,
mainView.getIndexForItem(library.getItemsByProperties(properties).queryElementAt(0, Ci.sbIMediaItem)));
player.focusPlayingTrack();
},
{
argCount: "+",
completer: function (context, args) completion.song(context, args)
});
// TODO: maybe :vol! could toggle mute on/off? --djk
commands.add(["vol[ume]"],
"Set the volume",
function (args) {
let arg = args[0];
if (!/^[+-]?\d+$/.test(arg))
return void dactyl.echoerr("E488: Trailing characters");
let level = parseInt(arg, 10) / 100;
if (/^[+-]/.test(arg))
level = player.volume + level;
player.volume = util.Math.constrain(level, 0, 1);
},
{ argCount: "1" });
},
completion: function () {
completion.song = function song(context, args) {
// TODO: useful descriptions?
function map(list) list.map(function (i) [i, ""]);
let [artist, album] = [args[0], args[1]];
if (args.completeArg == 0) {
context.title = ["Artists"];
context.completions = map(library.getArtists());
}
else if (args.completeArg == 1) {
context.title = ["Albums by " + artist];
context.completions = map(library.getAlbums(artist));
}
else if (args.completeArg == 2) {
context.title = ["Tracks from " + album + " by " + artist];
context.completions = map(library.getTracks(artist, album));
}
};
completion.playlist = function playlist(context, args) {
context.title = ["Playlist", "Type"];
context.keys = { text: "name", description: "type" };
context.completions = player.getPlaylists();
};
completion.mediaView = function mediaView(context) {
context.title = ["Media View", "URL"];
context.anchored = false;
context.keys = { text: "contentTitle", description: "contentUrl" };
context.completions = player.getMediaPages();
};
completion.mediaListSort = function mediaListSort(context) {
context.title = ["Media List Sort Field", "Description"];
context.anchored = false;
context.completions = [["title", "Track name"], ["time", "Duration"], ["artist", "Artist name"],
["album", "Album name"], ["genre", "Genre"], ["rating", "Rating"]]; // FIXME: generate this list dynamically - see #sortBy
};
},
mappings: function () {
mappings.add([modes.PLAYER],
["x"], "Play track",
function () { player.play(); });
mappings.add([modes.PLAYER],
["z"], "Previous track",
function () { player.previous(); });
mappings.add([modes.PLAYER],
["c"], "Pause/unpause track",
function () { player.togglePlayPause(); });
mappings.add([modes.PLAYER],
["b"], "Next track",
function () { player.next(); });
mappings.add([modes.PLAYER],
["v"], "Stop track",
function () { player.stop(); });
mappings.add([modes.PLAYER],
["Q"], "Queue tracks by artist/album/track",
function () { commandline.open(":", "queue ", modes.EX); });
mappings.add([modes.PLAYER],
["f"], "Loads current view filtered by the keywords",
function () { commandline.open(":", "filter ", modes.EX); });
mappings.add([modes.PLAYER],
["i"], "Select current track",
function () { gSongbirdWindowController.doCommand("cmd_find_current_track"); });
mappings.add([modes.PLAYER],
["s"], "Toggle shuffle",
function () { player.toggleShuffle(); });
mappings.add([modes.PLAYER],
["r"], "Toggle repeat",
function () { player.toggleRepeat(); });
mappings.add([modes.PLAYER],
["h", "<Left>"], "Seek -10s",
function (count) { player.seekBackward(Math.max(1, count) * 10000); },
{ count: true });
mappings.add([modes.PLAYER],
["l", "<Right>"], "Seek +10s",
function (count) { player.seekForward(Math.max(1, count) * 10000); },
{ count: true });
mappings.add([modes.PLAYER],
["H", "<S-Left>"], "Seek -1m",
function (count) { player.seekBackward(Math.max(1, count) * 60000); },
{ count: true });
mappings.add([modes.PLAYER],
["L", "<S-Right>"], "Seek +1m",
function (count) { player.seekForward(Math.max(1, count) * 60000); },
{ count: true });
mappings.add([modes.PLAYER],
["=", "+"], "Increase volume by 5% of the maximum",
function () { player.increaseVolume(); });
mappings.add([modes.PLAYER],
["-"], "Decrease volume by 5% of the maximum",
function () { player.decreaseVolume(); });
mappings.add([modes.PLAYER],
["/"], "Search forward for a track",
function (args) { commandline.open("/", "", modes.SEARCH_VIEW_FORWARD); });
mappings.add([modes.PLAYER],
["n"], "Find the next track",
function () { player.searchViewAgain(false);});
mappings.add([modes.PLAYER],
["N"], "Find the previous track",
function () { player.searchViewAgain(true);});
for (let i in util.range(0, 6)) {
let (rating = i) {
mappings.add([modes.PLAYER],
["<C-" + rating + ">"], "Rate the current media item " + rating,
function () {
let item = gMM.sequencer.currentItem || this._currentView.selection.currentMediaItem; // XXX: a bit too magic
if (item)
player.rateMediaItem(item, rating);
else
dactyl.beep();
}
);
};
}
},
options: function () {
options.add(["repeat"],
"Set the playback repeat mode",
"number", 0,
{
setter: function (value) gMM.sequencer.repeatMode = value,
getter: function () gMM.sequencer.repeatMode,
completer: function (context) [
["0", "Repeat none"],
["1", "Repeat one"],
["2", "Repeat all"]
],
validator: Option.validateCompleter
});
options.add(["shuffle"],
"Play tracks in shuffled order",
"boolean", false,
{
setter: function (value) gMM.sequencer.mode = value ? gMM.sequencer.MODE_SHUFFLE : gMM.sequencer.MODE_FORWARD,
getter: function () gMM.sequencer.mode == gMM.sequencer.MODE_SHUFFLE
});
}
});
// vim: set fdm=marker sw=4 ts=4 et:

View File

@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="162"
height="40"
id="svg2"
sodipodi:version="0.32"
inkscape:version="0.46"
version="1.0"
sodipodi:docbase="/home/maxauthority/code/pentadactyl"
sodipodi:docname="xulmus.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape"
inkscape:export-filename="/home/maxauthority/code/pentadactyl/xulmus.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
id="defs4">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 29 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="185 : 29 : 1"
inkscape:persp3d-origin="92.5 : 19.333333 : 1"
id="perspective2392" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
gridtolerance="10000"
guidetolerance="10"
objecttolerance="10"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="4.04"
inkscape:cx="84.114858"
inkscape:cy="55.052209"
inkscape:document-units="px"
inkscape:current-layer="layer1"
width="185px"
height="58px"
showgrid="false" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-5.4392018,-9.6624603)">
<text
xml:space="preserve"
style="font-size:24px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:DejaVu Sans Mono"
x="6.2673268"
y="29.896835"
id="text2229"><tspan
sodipodi:role="line"
id="tspan2231"
x="6.2673268"
y="29.896835">xulmus_</tspan></text>
<text
xml:space="preserve"
style="font-size:24px;font-style:normal;font-weight:normal;fill:#00ff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:DejaVu Sans Mono"
x="6.4079518"
y="53.183945"
id="text2233"><tspan
sodipodi:role="line"
id="tspan2235"
x="6.4079518"
y="53.183945">~</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB