Add the base of a highly experimental gui for NixOS.

svn path=/nixos/trunk/; revision=17455
This commit is contained in:
Nicolas Pierron 2009-09-26 23:15:19 +00:00
parent a701637f94
commit 5bf3abba14
10 changed files with 615 additions and 0 deletions

19
gui/README Normal file
View file

@ -0,0 +1,19 @@
This file should become a nix expression.
you need to:
- download the latest jQuery from:
http://jqueryjs.googlecode.com/files/jquery-1.3.2.js
SHA1 Checksum: f0b95e99225f314fbe37ccf6b74ce2f916c517de
- install 'xulrunner' with nix:
nix-env -i xulrunner
- install and add nix-intantiate in your path
- have /etc/nixos/nixpkgs
- have /etc/nixos/nixos
to run it, either:
- cd /etc/nixos/nixos/gui; ./nixos-gui
- xulrunner /etc/nixos/nixos/gui/application.ini

35
gui/application.ini Normal file
View file

@ -0,0 +1,35 @@
[App]
;
; This field specifies your organization's name. This field is recommended,
; but optional.
Vendor=NixOS
;
; This field specifies your application's name. This field is required.
Name=NixOS-gui
;
; This field specifies your application's version. This field is optional.
Version=0.0
;
; This field specifies your application's build ID (timestamp). This field is
; required.
BuildID=20090925
;
; This field specifies a compact copyright notice for your application. This
; field is optional.
;Copyright=
;
; This ID is just an example. Every XUL app ought to have it's own unique ID.
; You can use the microsoft "guidgen" or "uuidgen" tools, or go on
; irc.mozilla.org and /msg botbot uuid. This field is optional.
;ID=
[Gecko]
;
; This field is required. It specifies the minimum Gecko version that this
; application requires.
MinVersion=1.9a5
;
; This field is optional. It specifies the maximum Gecko version that this
; application requires. It should be specified if your application uses
; unfrozen interfaces.
;MaxVersion=1.9.0.*

View file

@ -0,0 +1 @@
content nixos-gui content/nixos-gui/

View file

@ -0,0 +1,74 @@
function makeTempFile(prefix)
{
var file = Components.classes["@mozilla.org/file/directory_service;1"]
.getService(Components.interfaces.nsIProperties)
.get("TmpD", Components.interfaces.nsIFile);
file.append(prefix || "xulrunner");
file.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0664);
return file;
}
function writeToFile(file, data)
{
// file is nsIFile, data is a string
var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
.createInstance(Components.interfaces.nsIFileOutputStream);
// use 0x02 | 0x10 to open file for appending.
foStream.init(file, 0x02 | 0x08 | 0x20, 0664, 0); // write, create, truncate
foStream.write(data, data.length);
foStream.close();
}
function readFromFile(file)
{
// |file| is nsIFile
var data = "";
var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
.createInstance(Components.interfaces.nsIFileInputStream);
var sstream = Components.classes["@mozilla.org/scriptableinputstream;1"]
.createInstance(Components.interfaces.nsIScriptableInputStream);
fstream.init(file, -1, 0, 0);
sstream.init(fstream);
var str = sstream.read(4096);
while (str.length > 0) {
data += str;
str = sstream.read(4096);
}
sstream.close();
fstream.close();
return data;
}
function runProgram(commandLine)
{
// create an nsILocalFile for the executable
var file = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
file.initWithPath("/bin/sh");
// create an nsIProcess
var process = Components.classes["@mozilla.org/process/util;1"]
.createInstance(Components.interfaces.nsIProcess);
process.init(file);
// Run the process.
// If first param is true, calling thread will be blocked until
// called process terminates.
// Second and third params are used to pass command-line arguments
// to the process.
var args = ["-c", commandLine];
process.run(true, args, args.length);
}
// only for testing...
function testIO()
{
var f = makeTempFile();
writeToFile(f, "essai\ntest");
alert(readFromFile(f));
runProgram("zenity --info");
}

View file

@ -0,0 +1,171 @@
var COPYCOL = 2;
var gOptionListView = new treeView(["opt-success","opt-name","opt-desc"],
COPYCOL);
// Run xulrunner application.ini -jsconsole -console, to see messages.
function log(str)
{
Components.classes['@mozilla.org/consoleservice;1']
.getService(Components.interfaces.nsIConsoleService)
.logStringMessage(str);
}
// return the DOM of the value returned by nix-instantiate
function dumpOptions(path)
{
var nixInstantiate = "nix-instantiate"; // "@nix@/bin/nix-instantiate";
var nixos = "/etc/nixos/nixos/default.nix"; // "@nixos@/default.nix";
var o = makeTempFile("nixos-options");
path = "eval.options" + (path? "." + path : "");
log("retrieve options from: " + path);
runProgram(nixInstantiate+" "+nixos+" -A "+path+" --eval-only --strict --xml 2>/dev/null | tr -d '' >" + o.path);
var xml = readFromFile(o);
o.remove(false);
// jQuery does a stack overflow when converting the XML to a DOM.
var dom = DOMParser().parseFromString(xml, "text/xml");
log("return dom");
return dom;
}
// Pretty print Nix values.
function nixPP(value, level)
{
function indent(level) { ret = ""; while (level--) ret+= " "; return ret; }
if (!level) level = 0;
var ret = "<no match>";
if (value.is("attrs")) {
var content = "";
value.children().each(function (){
var name = $(this).attr("name");
var value = nixPP($(this).children(), level + 1);
content += indent(level + 1) + name + " = " + value + ";\n";
});
ret = "{\n" + content + indent(level) + "}";
}
else if (value.is("list")) {
var content = "";
value.children().each(function (){
content += indent(level + 1) + "(" + nixPP($(this), level + 1) + ")\n";
});
ret = "[\n" + content + indent(level) + "]";
}
else if (value.is("bool"))
ret = (value.attr("value") == "true");
else if (value.is("string"))
ret = '"' + value.attr("value") + '"';
else if (value.is("path"))
ret = value.attr("value");
else if (value.is("int"))
ret = parseInt(value.attr("value"));
else if (value.is("derivation"))
ret = value.attr("outPath");
else if (value.is("function"))
ret = "<function>";
else {
var content = "";
value.children().each(function (){
content += indent(level + 1) + "(" + nixPP($(this), level + 1) + ")\n";
});
ret = "<!--" + value.selector + "--><!--\n" + content + indent(level) + "-->";
}
return ret;
}
// Function used to reproduce the select operator on the XML DOM.
// It return the value contained in the targeted attribute.
function nixSelect(attrs, selector)
{
var names = selector.split(".");
var value = $(attrs);
for (var i = 0; i < names.length; i++) {
log(nixPP(value) + "." + names[i]);
if (value.is("attrs"))
value = value.children("attr[name='" + names[i] + "']").children();
else {
log("Cannot do an attribute selection.");
break
}
}
log("nixSelect return: " + nixPP(value));
var ret;
if (value.is("attrs") || value.is("list"))
ret = value;
else if (value.is("bool"))
ret = value.attr("value") == "true";
else if (value.is("string"))
ret = value.attr("value");
else if (value.is("int"))
ret = parseInt(value.attr("value"));
else if (value.is("derivation"))
ret = value.attr("outPath");
else if (value.is("function"))
ret = "<function>";
return ret;
}
var gProgressBar;
function setProgress(current, max)
{
if (gProgressBar) {
gProgressBar.value = 100 * current / max;
log("progress: " + gProgressBar.value + "%");
}
else
log("unknow progress bar");
}
// fill the list of options
function setOptionList(optionDOM)
{
var options = $("attrs", optionDOM).filter(function () {
return $(this)
.children("attr[name='_type']")
.children("string[value='option']")
.length != 0;
});
var max = options.length;
log("Number of options: " + max);
setProgress(0, max);
gOptionListView.clear();
options.each(function (index){
var success = nixSelect(this, "config.success");
var name = nixSelect(this, "name");
var desc = nixSelect(this, "description");
if (success && name && desc) {
log("Add option '" + name + "' in the list.");
gOptionListView.addRow([success, name, desc]);
}
else
log("A problem occur while scanning an option.");
setProgress(index + 1, max);
});
}
function onload()
{
var optionList = document.getElementById("option-list");
gProgressBar = document.getElementById("progress-bar");
setProgress(0, 1);
optionList.view = gOptionListView;
// try to avoid blocking the rendering, unfortunately this is not perfect.
setTimeout(function (){
setOptionList(dumpOptions("hardware"));}
, 100);
}

View file

@ -0,0 +1,30 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<!DOCTYPE window>
<!-- To edit this file I recommend you to use:
http://xulfr.org/outils/xulediteur.xul
-->
<window
id = "nixos-gui"
title = "NixOS gui"
xmlns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script src="jquery-1.3.2.js"/>
<script src="treeView.js"/>
<script src="main.js"/>
<script src="io.js"/>
<tree flex="1" id="option-list" persist="height">
<treecols>
<treecol persist="hidden width" flex="1" id="opt-success" label="Success"/>
<splitter class="tree-splitter"/>
<treecol persist="hidden width" flex="30" id="opt-name" label="Option"/>
<splitter class="tree-splitter"/>
<treecol persist="hidden width" flex="50" id="opt-desc" label="Description"
primary="true"/>
</treecols>
<treechildren id="first-child" flex="1"/>
</tree>
<progressmeter id="progress-bar" value="0%"/>
</window>

View file

@ -0,0 +1,117 @@
// Taken from pageInfo.js
//******** define a js object to implement nsITreeView
function treeView(columnids, copycol)
{
// columnids is an array of strings indicating the names of the columns, in order
this.columnids = columnids;
this.colcount = columnids.length;
// copycol is the index number for the column that we want to add to
// the copy-n-paste buffer when the user hits accel-c
this.copycol = copycol;
this.rows = 0;
this.tree = null;
this.data = [ ];
this.selection = null;
this.sortcol = null;
this.sortdir = 0;
}
treeView.prototype = {
set rowCount(c) { throw "rowCount is a readonly property"; },
get rowCount() { return this.rows; },
setTree: function(tree)
{
this.tree = tree;
},
getCellText: function(row, column)
{
// row can be null, but js arrays are 0-indexed.
// colidx cannot be null, but can be larger than the number
// of columns in the array (when column is a string not in
// this.columnids.) In this case it's the fault of
// whoever typoed while calling this function.
return this.data[row][column.index] || "";
},
setCellValue: function(row, column, value)
{
},
setCellText: function(row, column, value)
{
this.data[row][column.index] = value;
},
addRow: function(row)
{
this.rows = this.data.push(row);
this.rowCountChanged(this.rows - 1, 1);
},
addRows: function(rows)
{
var length = rows.length;
for(var i = 0; i < length; i++)
this.rows = this.data.push(rows[i]);
this.rowCountChanged(this.rows - length, length);
},
rowCountChanged: function(index, count)
{
this.tree.rowCountChanged(index, count);
},
invalidate: function()
{
this.tree.invalidate();
},
clear: function()
{
if (this.tree)
this.tree.rowCountChanged(0, -this.rows);
this.rows = 0;
this.data = [ ];
},
handleCopy: function(row)
{
return (row < 0 || this.copycol < 0) ? "" : (this.data[row][this.copycol] || "");
},
performActionOnRow: function(action, row)
{
if (action == "copy") {
var data = this.handleCopy(row)
this.tree.treeBody.parentNode.setAttribute("copybuffer", data);
}
},
getRowProperties: function(row, prop) { },
getCellProperties: function(row, column, prop) { },
getColumnProperties: function(column, prop) { },
isContainer: function(index) { return false; },
isContainerOpen: function(index) { return false; },
isSeparator: function(index) { return false; },
isSorted: function() { },
canDrop: function(index, orientation) { return false; },
drop: function(row, orientation) { return false; },
getParentIndex: function(index) { return 0; },
hasNextSibling: function(index, after) { return false; },
getLevel: function(index) { return 0; },
getImageSrc: function(row, column) { },
getProgressMode: function(row, column) { },
getCellValue: function(row, column) { },
toggleOpenState: function(index) { },
cycleHeader: function(col) { },
selectionChanged: function() { },
cycleCell: function(row, column) { },
isEditable: function(row, column) { return false; },
isSelectable: function(row, column) { return false; },
performAction: function(action) { },
performActionOnCell: function(action, row, column) { }
};

154
gui/components/clh.js Normal file
View file

@ -0,0 +1,154 @@
const nsIAppShellService = Components.interfaces.nsIAppShellService;
const nsISupports = Components.interfaces.nsISupports;
const nsICategoryManager = Components.interfaces.nsICategoryManager;
const nsIComponentRegistrar = Components.interfaces.nsIComponentRegistrar;
const nsICommandLine = Components.interfaces.nsICommandLine;
const nsICommandLineHandler = Components.interfaces.nsICommandLineHandler;
const nsIFactory = Components.interfaces.nsIFactory;
const nsIModule = Components.interfaces.nsIModule;
const nsIWindowWatcher = Components.interfaces.nsIWindowWatcher;
// CHANGEME: to the chrome URI of your extension or application
const CHROME_URI = "chrome://nixos-gui/content/myviewer.xul";
// CHANGEME: change the contract id, CID, and category to be unique
// to your application.
const clh_contractID = "@mozilla.org/commandlinehandler/general-startup;1?type=myapp";
// use uuidgen to generate a unique ID
const clh_CID = Components.ID("{2991c315-b871-42cd-b33f-bfee4fcbf682}");
// category names are sorted alphabetically. Typical command-line handlers use a
// category that begins with the letter "m".
const clh_category = "m-myapp";
/**
* Utility functions
*/
/**
* Opens a chrome window.
* @param aChromeURISpec a string specifying the URI of the window to open.
* @param aArgument an argument to pass to the window (may be null)
*/
function openWindow(aChromeURISpec, aArgument)
{
var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"].
getService(Components.interfaces.nsIWindowWatcher);
ww.openWindow(null, aChromeURISpec, "_blank",
"chrome,menubar,toolbar,status,resizable,dialog=no",
aArgument);
}
/**
* The XPCOM component that implements nsICommandLineHandler.
* It also implements nsIFactory to serve as its own singleton factory.
*/
const myAppHandler = {
/* nsISupports */
QueryInterface : function clh_QI(iid)
{
if (iid.equals(nsICommandLineHandler) ||
iid.equals(nsIFactory) ||
iid.equals(nsISupports))
return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
},
/* nsICommandLineHandler */
handle : function clh_handle(cmdLine)
{
openWindow(CHROME_URI, cmdLine);
cmdLine.preventDefault = true;
},
// CHANGEME: change the help info as appropriate, but
// follow the guidelines in nsICommandLineHandler.idl
// specifically, flag descriptions should start at
// character 24, and lines should be wrapped at
// 72 characters with embedded newlines,
// and finally, the string should end with a newline
helpInfo : " <filename> Open the file in the viewer\n",
/* nsIFactory */
createInstance : function clh_CI(outer, iid)
{
if (outer != null)
throw Components.results.NS_ERROR_NO_AGGREGATION;
return this.QueryInterface(iid);
},
lockFactory : function clh_lock(lock)
{
/* no-op */
}
};
/**
* The XPCOM glue that implements nsIModule
*/
const myAppHandlerModule = {
/* nsISupports */
QueryInterface : function mod_QI(iid)
{
if (iid.equals(nsIModule) ||
iid.equals(nsISupports))
return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
},
/* nsIModule */
getClassObject : function mod_gch(compMgr, cid, iid)
{
if (cid.equals(clh_CID))
return myAppHandler.QueryInterface(iid);
throw Components.results.NS_ERROR_NOT_REGISTERED;
},
registerSelf : function mod_regself(compMgr, fileSpec, location, type)
{
compMgr.QueryInterface(nsIComponentRegistrar);
compMgr.registerFactoryLocation(clh_CID,
"myAppHandler",
clh_contractID,
fileSpec,
location,
type);
var catMan = Components.classes["@mozilla.org/categorymanager;1"].
getService(nsICategoryManager);
catMan.addCategoryEntry("command-line-handler",
clh_category,
clh_contractID, true, true);
},
unregisterSelf : function mod_unreg(compMgr, location, type)
{
compMgr.QueryInterface(nsIComponentRegistrar);
compMgr.unregisterFactoryLocation(clh_CID, location);
var catMan = Components.classes["@mozilla.org/categorymanager;1"].
getService(nsICategoryManager);
catMan.deleteCategoryEntry("command-line-handler", clh_category);
},
canUnload : function (compMgr)
{
return true;
}
};
/* The NSGetModule function is the magic entry point that XPCOM uses to find what XPCOM objects
* this component provides
*/
function NSGetModule(comMgr, fileSpec)
{
return myAppHandlerModule;
}

View file

@ -0,0 +1,11 @@
pref("toolkit.defaultChromeURI", "chrome://nixos-gui/content/myviewer.xul");
pref("general.useragent.extra.myviewer", "NixOS gui/0.0");
/* debugging prefs */
pref("browser.dom.window.dump.enabled", true); // enable output to stderr
pref("javascript.options.showInConsole", true); // show javascript errors from chrome: files in the jsconsole
pref("javascript.options.strict", true); // show javascript strict warnings in the jsconsole
/* disable xul cache so that modifications to chrome: files apply without restarting xulrunner */
pref("nglayout.debug.disable_xul_cache", true);
pref("nglayout.debug.disable_xul_fastload", true);

3
gui/nixos-gui Executable file
View file

@ -0,0 +1,3 @@
#!/bin/sh
exec xulrunner "./application.ini"