Added graphical configuration, at this point only for the grey theme but others will follow soon. (This has been nearly done for two weeks or more but was on hold due to the bugs with multithreading)
--- a/functions.php Tue Sep 23 23:26:15 2008 -0400
+++ b/functions.php Tue Sep 23 23:26:18 2008 -0400
@@ -221,6 +221,11 @@
{
global $httpd;
static $smarty = array();
+ if ( $theme_id === '__free__' )
+ {
+ $smarty = array();
+ return false;
+ }
if ( !isset($smarty[$theme_id]) )
{
$smarty[$theme_id] = new Smarty();
@@ -233,3 +238,61 @@
}
return $smarty[$theme_id];
}
+
+/**
+ * Implementation of the "which" command in native PHP.
+ * @param string command
+ * @return string path to executable, or false on failure
+ */
+
+function which($executable)
+{
+ $path = ( isset($_ENV['PATH']) ) ? $_ENV['PATH'] : ( isset($_SERVER['PATH']) ? $_SERVER['PATH'] : false );
+ if ( !$path )
+ // couldn't get OS's PATH
+ return false;
+
+ $win32 = ( PHP_OS == 'WINNT' || PHP_OS == 'WIN32' );
+ $extensions = $win32 ? array('.exe', '.com', '.bat') : array('');
+ $separator = $win32 ? ';' : ':';
+ $paths = explode($separator, $path);
+ foreach ( $paths as $dir )
+ {
+ foreach ( $extensions as $ext )
+ {
+ $fullpath = "$dir/{$executable}{$ext}";
+ if ( file_exists($fullpath) && is_executable($fullpath) )
+ {
+ return $fullpath;
+ }
+ }
+ }
+ return false;
+}
+
+/**
+ * Reload the config.
+ */
+
+function grey_reload_config()
+{
+ global $httpd;
+ status('reloading the config');
+
+ if ( file_exists('./greyhound-config.php') )
+ {
+ require('./greyhound-config.php');
+ }
+ else
+ {
+ // ignore this, it allows using a different config file when a Mercurial repository
+ // exists in Greyhound's root directory (to allow the devs to have their own config
+ // separate from the default)
+
+ if ( @is_dir(GREY_ROOT . '/.hg') )
+ require(GREY_ROOT . '/config.dev.php');
+ else
+ require(GREY_ROOT . '/config.php');
+ }
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/config.js Tue Sep 23 23:26:18 2008 -0400
@@ -0,0 +1,157 @@
+var userlist = [];
+
+function register_user(username)
+{
+ userlist.push(username);
+}
+
+function draw_user(ul, username)
+{
+ var inp = document.createElement('input');
+ inp.type = 'hidden';
+ inp.name = 'users[]';
+ inp.value = username;
+ ul.parentNode.appendChild(inp);
+ var li = document.createElement('li');
+ li.username = username;
+ li.userinput = inp;
+ var delbtn = document.createElement('a');
+ delbtn.style.color = '#ff0000';
+ delbtn.href = '#';
+ delbtn.onclick = function()
+ {
+ delete_user(this);
+ return false;
+ }
+ delbtn.appendChild(document.createTextNode('[X]'));
+ li.appendChild(delbtn);
+
+ li.appendChild(document.createTextNode(' ' + username));
+ ul.appendChild(li);
+}
+
+function draw_new_user(username, password)
+{
+ for ( var i = 0; i < userlist.length; i++ )
+ {
+ if ( userlist[i] == username )
+ {
+ alert('The username you entered is already in the list, please delete the current user and re-add it if you want to change the password.');
+ return false;
+ }
+ }
+
+ userlist.push(username);
+
+ var ul = document.getElementById('userlist');
+
+ var inp = document.createElement('input');
+ inp.type = 'hidden';
+ inp.name = 'users_add[' + username + ']';
+ inp.value = password;
+ ul.parentNode.appendChild(inp);
+ var li = document.createElement('li');
+ li.username = username;
+ li.userinput = inp;
+ var delbtn = document.createElement('a');
+ delbtn.style.color = '#ff0000';
+ delbtn.href = '#';
+ delbtn.onclick = function()
+ {
+ delete_user(this);
+ return false;
+ }
+ delbtn.appendChild(document.createTextNode('[X]'));
+ li.appendChild(delbtn);
+
+ li.appendChild(document.createTextNode(' ' + username));
+ ul.appendChild(li);
+
+ return true;
+}
+
+function userlist_init()
+{
+ var ul = document.getElementById('userlist');
+ for ( var i = 0; i < userlist.length; i++ )
+ {
+ draw_user(ul, userlist[i]);
+ }
+}
+
+function delete_user(a)
+{
+ var li = a.parentNode;
+ var username = li.username;
+ li.parentNode.parentNode.removeChild(li.userinput);
+ li.parentNode.removeChild(li);
+
+ for ( var i = 0; i < userlist.length; i++ )
+ {
+ if ( userlist[i] == username )
+ {
+ delete(userlist[i]);
+ break;
+ }
+ }
+}
+
+function add_user_form()
+{
+ var ul = document.getElementById('userlist');
+
+ if ( ul.parentNode.getElementsByTagName('form').length > 0 )
+ {
+ return false;
+ }
+
+ var theform = document.createElement('form');
+ theform.action = 'javascript:void(0);';
+ theform.onsubmit = function()
+ {
+ if ( this.username.value == '' || this.password.value == '' )
+ {
+ alert('Please enter a username and password.');
+ return false;
+ }
+ if ( draw_new_user(this.username.value, this.password.value) )
+ {
+ this.parentNode.removeChild(this);
+ }
+ else
+ {
+ this.username.focus();
+ }
+
+ return false;
+ }
+
+ theform.appendChild(document.createTextNode('user: '));
+
+ var i_user = document.createElement('input');
+ i_user.type = 'text';
+ i_user.name = 'username';
+ i_user.size = '12';
+ theform.appendChild(i_user);
+
+ theform.appendChild(document.createTextNode(' pass: '));
+
+ var i_pass = document.createElement('input');
+ i_pass.type = 'password';
+ i_pass.name = 'password';
+ i_pass.size = '12';
+ theform.appendChild(i_pass);
+
+ theform.appendChild(document.createTextNode(' '));
+
+ var i_sub = document.createElement('input');
+ i_sub.type = 'submit';
+ i_sub.value = 'Add';
+ theform.appendChild(i_sub);
+
+ ul.parentNode.appendChild(theform);
+
+ i_user.focus();
+}
+
+window.onload = userlist_init;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/grey/config.tpl Tue Sep 23 23:26:18 2008 -0400
@@ -0,0 +1,194 @@
+{**
+ * Template file for default Funky Monkey theme
+ * Web control interface script for Amarok
+ * Written by Dan Fuhry - 2008
+ *
+ * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *}
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html>
+ <head>
+ <title>Greyhound: Configuration</title>
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+ <link rel="stylesheet" type="text/css" href="/themes/{$theme|escape}/style.css" />
+ <link rel="favorite icon" type="image/ico" href="/favicon.ico" />
+ <script type="text/javascript" src="/scripts/config.js"></script>
+ </head>
+ <body>
+
+ <div class="greylogo"></div>
+ <a class="backlink" href="/index">« Return to playlist</a>
+ <div class="clearer"></div>
+
+ <form action="/config" method="post">
+
+ <div class="tblholder">
+ <table border="0" cellspacing="1" cellpadding="4" style="width: 100%;">
+ <tr>
+ <th colspan="2">Greyhound configuration</th>
+ </tr>
+
+ {if $tried}
+ <tr>
+ <td class="row3" colspan="2">
+ {if $success}
+ <p class="success">{$success|nl2br}</p>
+ {else}
+ <p class="error">{$error|nl2br}</p>
+ {/if}
+ </td>
+ </tr>
+ {/if}
+
+ <!-- field - current config pass -->
+ {if $needpass}
+ <tr>
+ <td class="row2" style="width: 50%;">
+ <b>Current configuration password:</b><br />
+ <small>Please input the current configuration password to make changes below.</small>
+ </td>
+ <td class="row1" style="width: 50%;">
+ <input type="password" name="configpass" />
+ </td>
+ </tr>
+ {/if}
+
+ <!-- field - public IP -->
+ <tr>
+ <td class="row2" style="width: 50%;">
+ <label for="chk_public"><b>Publicly accessible IP</b></label><br />
+ <small>
+ This controls whether Greyhound can be accessed from other computers on the network interface level. For the nerds, this
+ means that Greyhound will bind to 127.0.0.1 if unchecked or 0.0.0.0 if checked.
+ </small>
+ </td>
+ <td class="row1" style="width: 50%;">
+ <input type="checkbox"
+ name="public"
+ id="chk_public"
+ {if $public}checked="checked"{/if}
+ />
+ </td>
+ </tr>
+
+ <!-- field - allow control -->
+ <tr>
+ <td class="row2" style="width: 50%;">
+ <label for="chk_allowcontrol"><b>Allow control of Amarok</b></label><br />
+ <small>
+ Tick this option to enable full playback controls (recommended). Otherwise, Greyhound will just be a playlist viewer and
+ won't be able to do anything to control what Amarok is doing.
+ </small>
+ </td>
+ <td class="row1" style="width: 50%;">
+ <input type="checkbox"
+ name="allowcontrol"
+ id="chk_allowcontrol"
+ {if $allowcontrol}checked="checked"{/if}
+ />
+ </td>
+ </tr>
+
+ <!-- field - allow multi-process -->
+ <tr>
+ <td class="row2" style="width: 50%;">
+ <label for="chk_allow_fork"><b>Enable multi-process webserving</b></label><br />
+ <small>
+ If this is enabled, Greyhound will use fork() to allow serving multiple HTTP requests at the same time. It is recommended
+ that you enable this to improve performance. In some rare cases, this results in a large number of zombie processes and
+ sometimes locks up the server, so disable this if you're experiencing problems.
+ </small>
+ </td>
+ <td class="row1" style="width: 50%;">
+ <input type="checkbox"
+ name="allow_fork"
+ id="chk_allow_fork"
+ {if $allow_fork}checked="checked"{/if}
+ />
+ </td>
+ </tr>
+
+ <!-- section - auth config -->
+ <tr>
+ <th colspan="2">
+ Authentication
+ </th>
+ </tr>
+
+ <!-- field - enable auth -->
+ <tr>
+ <td class="row2" style="width: 50%;">
+ <label for="chk_use_auth"><b>Require login</b></label><br />
+ <small>
+ Check this box to require a login to Greyhound's interface. The logins are cookie based, so make sure your browser supports
+ cookies and has them enabled before you enable this. You have to create at least one user for this to work.
+ </small>
+ </td>
+ <td class="row1" style="width: 50%;">
+ <input type="checkbox"
+ name="use_auth"
+ id="chk_use_auth"
+ {if $use_auth}checked="checked"{/if}
+ />
+ </td>
+ </tr>
+
+ <!-- field - user list -->
+ <tr>
+ <td class="row2" style="width: 50%;">
+ <b>User list</b><br />
+ <small>
+ This is the list of valid users for Greyhound. Passwords aren't encrypted in the config file to make editing easier.
+ </small>
+ </td>
+ <td class="row1" style="width: 50%;">
+ {strip}
+ {foreach key=username item=password from=$users}
+ <script type="text/javascript">register_user('{$username|escape:'quotes'}');</script>
+ {/foreach}
+ {/strip}
+ <ul id="userlist">
+ <li><a href="#" onclick="add_user_form(); return false;">add user</a></li>
+ </ul>
+ </td>
+ </tr>
+
+ <!-- field - config PW -->
+ <tr>
+ <td class="row2" style="width: 50%;">
+ <b>Config password</b><br />
+ <small>
+ Enter a password here to protect your configuration from being modified.
+ </small>
+ </td>
+ <td class="row1" style="width: 50%;">
+ <input type="password" name="newconfigpass" value="____________________" />
+ </td>
+ </tr>
+
+ <!-- submit -->
+ <tr>
+ <th colspan="2">
+ <input type="hidden" name="submit" value="true" />
+ <input type="submit" value="Save changes" />
+ </th>
+ </tr>
+
+ </table>
+ </div>
+
+ </form>
+
+ <a class="backlink" href="/index">« Return to playlist</a>
+
+ <div class="poweredby">
+ {include file="footer.tpl"}
+ </div>
+ </body>
+</html>
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/themes/grey/footer.tpl Tue Sep 23 23:26:18 2008 -0400
@@ -0,0 +1,7 @@
+ Powered by <a onclick="window.open(this.href); return false;" href="http://greyhound.enanocms.org/">Greyhound</a> v{$greyhound_version}
+ –
+ <a href="/config">config</a>
+ {if $use_auth}
+ –
+ <a href="/logout">log out</a>
+ {/if}
--- a/themes/grey/style.css Tue Sep 23 23:26:15 2008 -0400
+++ b/themes/grey/style.css Tue Sep 23 23:26:18 2008 -0400
@@ -10,6 +10,9 @@
background-color: #262626;
color: #ffffff;
padding: 0 8px;
+}
+
+body.playlist {
background-image: url(images/playbar-shadow.gif);
background-repeat: repeat-x;
}
@@ -18,6 +21,7 @@
padding: 1px;
background-color: #b0b0b0;
border: 1px solid #000000;
+ color: #c6c6c6;
}
div.tblholder table {
@@ -28,14 +32,18 @@
background-color: #505050;
}
-tr.row1 td {
+tr.row1 td, td.row1 {
background-color: #383838;
}
-tr.row2 td {
+tr.row2 td, td.row2 {
background-color: #424242;
}
+tr.row3 td, td.row3 {
+ background-color: #3d3d3d;
+}
+
tr.current td {
background-color: #303030;
color: #ffff00;
@@ -144,3 +152,48 @@
div.poweredby a {
color: #57608a;
}
+
+ul#userlist a {
+ color: #ffff00;
+}
+
+p.success, p.error {
+ text-align: center;
+ font-weight: bold;
+}
+
+p.success {
+ color: #23a600;
+}
+
+p.error {
+ color: #c71700;
+}
+
+a.backlink {
+ display: block;
+ width: 150px;
+ color: #a0a0a0;
+ background-color: #404040;
+ padding: 5px;
+ margin: 10px 0 5px 0;
+ text-decoration: none;
+}
+
+a.backlink:hover {
+ color: #c0c0c0;
+ background-color: #505050;
+}
+
+div.greylogo {
+ width: 120px;
+ height: 33px;
+ background-image: url(images/greylogo.png);
+ background-repeat: no-repeat;
+ float: right;
+ margin: 5px 0;
+}
+
+div.clearer {
+ clear: both;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/uiconfig.php Tue Sep 23 23:26:18 2008 -0400
@@ -0,0 +1,176 @@
+<?php
+
+/**
+ * Greyhound - real web management for Amarok
+ * Copyright (C) 2008 Dan Fuhry
+ *
+ * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ */
+
+// rebooting doesn't work at this point
+define('REBOOT_SUPPORT', false);
+
+function greyhound_config($httpd, $socket)
+{
+ global $use_auth;
+
+ // validate the session
+ if ( !session_check() )
+ {
+ $httpd->header('HTTP/1.1 307 Temporary Redirect');
+ $httpd->header('Location: /login');
+
+ return;
+ }
+
+ $tried = false;
+ $success = false;
+ $needpass = false;
+ $error = false;
+
+ // if a config password is set, make sure it matches.
+ $authed = false;
+ if ( empty($GLOBALS['configpass']) )
+ {
+ $authed = true;
+ }
+ else
+ {
+ $needpass = true;
+ if ( isset($_POST['configpass']) && sha1($_POST['configpass']) === $GLOBALS['configpass'] )
+ {
+ $authed = true;
+ $tried = true;
+ }
+ else if ( isset($_POST['configpass']) )
+ {
+ $error = 'You didn\'t enter the right configuration password, so the changes weren\'t saved.';
+ $tried = true;
+ }
+ }
+
+ if ( $authed && isset($_POST['submit']) )
+ {
+ $need_reboot = ( isset($_POST['public']) !== $GLOBALS['public'] ||
+ isset($_POST['allow_fork']) !== $GLOBALS['allow_fork']);
+
+ // compile the new config file
+ $public = ( isset($_POST['public']) ) ? 'true' : 'false';
+ $allowcontrol = ( isset($_POST['allowcontrol']) ) ? 'true' : 'false';
+ $allow_fork = ( isset($_POST['allow_fork']) ) ? 'true' : 'false';
+ $use_auth = ( isset($_POST['use_auth']) ) ? 'true' : 'false';
+
+ // for auth_data, we're going to merge the data from POST with the current auth_data array.
+ $auth_data = $GLOBALS['auth_data'];
+ $auth_changed = false;
+ foreach ( $auth_data as $user => $pass )
+ {
+ if ( !in_array($user, $_POST['users']) )
+ {
+ $auth_changed = true;
+ unset($auth_data[$user]);
+ }
+ }
+ if ( isset($_POST['users_add']) )
+ {
+ foreach ( $_POST['users_add'] as $user => $pass )
+ {
+ $auth_changed = true;
+ $auth_data[$user] = $pass;
+ }
+ }
+ $auth_data = var_export_string($auth_data);
+
+ $new_configpass = ( is_string($_POST['newconfigpass']) && $_POST['newconfigpass'] != '____________________' ) ? sha1($_POST['newconfigpass']) : $GLOBALS['configpass'];
+
+ $config_file = <<<EOF
+<?php
+
+//
+// GREYHOUND CONFIGURATION
+//
+
+// Listen on all interfaces. If this is false, it will only listen on
+// 127.0.0.1 (the loopback interface)
+\$GLOBALS['public'] = $public;
+
+// Allow control of playback. By default this is turned on but if you
+// set this to false, it will only display the playlist.
+\$GLOBALS['allowcontrol'] = $allowcontrol;
+
+// The default theme. This should be a name of a directory in ./themes.
+\$GLOBALS['theme'] = 'grey';
+
+// Allow forking when an HTTP request is received. This has advantages
+// and disadvantages. If this experimental option is enabled, it will
+// result in faster responses and load times but more memory usage.
+\$GLOBALS['allow_fork'] = $allow_fork;
+
+// set to true to enable authentication
+// this uses cookies, so make sure they're enabled in your browser
+\$GLOBALS['use_auth'] = $use_auth;
+
+// valid users and passwords
+\$GLOBALS['auth_data'] = $auth_data;
+
+// password for site configuration
+\$GLOBALS['configpass'] = '$new_configpass';
+
+EOF;
+ $error = 'Couldn\'t write to the config file (./greyhound-config.php).';
+
+ $fp = @fopen('./greyhound-config.php', 'w');
+ if ( $fp )
+ {
+ fwrite($fp, $config_file);
+ fclose($fp);
+
+ $passblurb = $auth_changed ? "\n<small>If you changed your own password, you've been logged out so you'll need to log back in again.</small>" : '';
+ if ( REBOOT_SUPPORT )
+ {
+ $rebootblurb = $need_reboot ? "\n<small>Since you changed some core configuration values, the Greyhound server has been restarted.</small>\n<small>If Greyhound doesn't respond, wait 20 seconds and then restart it using Amarok's script manager.</small>" : '';
+ }
+ else
+ {
+ $rebootblurb = $need_reboot ? "\n<small>Please restart Greyhound using Amarok's script manager for public IP and multithreading options to take effect.</small>" : '';
+ }
+ $success = "Configuration changes successful.{$passblurb}{$rebootblurb}";
+ $httpd->threader->ipc_send(array('action' => 'reloadconfig', 'propagate' => true));
+
+ if ( $need_reboot && REBOOT_SUPPORT )
+ {
+ $addr = $GLOBALS['public'] ? '0.0.0.0' : '127.0.0.1';
+ $fork = $GLOBALS['allow_fork'];
+ $httpd->reboot($addr, null, $fork);
+ }
+ }
+ }
+
+ global $theme;
+ $iphone = ( ( strpos($_SERVER['HTTP_USER_AGENT'], 'iPhone') ||
+ strpos($_SERVER['HTTP_USER_AGENT'], 'iPod') ||
+ strpos($_SERVER['HTTP_USER_AGENT'], 'BlackBerry') ||
+ isset($_GET['m']) )
+ && !isset($_GET['f'])
+ );
+ $theme_id = ( $iphone ) ? 'iphone' : $theme;
+ $smarty = load_theme($theme_id);
+
+ $smarty->assign('theme', $theme_id);
+ $smarty->assign('greyhound_version', GREY_VERSION);
+ $smarty->assign('tried', $tried);
+ $smarty->assign('success', $success);
+ $smarty->assign('needpass', $needpass);
+ $smarty->assign('use_auth', $use_auth);
+ $smarty->assign('public', $GLOBALS['public']);
+ $smarty->assign('allowcontrol', $GLOBALS['allowcontrol']);
+ $smarty->assign('allow_fork', $GLOBALS['allow_fork']);
+ $smarty->assign('use_auth', $GLOBALS['use_auth']);
+ $smarty->assign('users', $GLOBALS['auth_data']);
+ $smarty->assign('error', $error);
+ $smarty->display('config.tpl');
+}