Kimchi-devel
Threads by month
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
December 2013
- 20 participants
- 111 discussions
now we have support network get/create/delete/activate/deactivate
Now we need to support the attach/detach/
<https://github.com/kimchi-project/kimchi/wiki/customize-VM#organization-of-…>
a Network to a guest.
And get the info of Network attached to a guest.
libvirt supports several types of network interfaces:
'bridge','network','user','ethernet','direct','hostdev','mcast',
'server', 'client'
but there is not a unique "name" attribute for "interface" element.(more
libvirt network interfaces info
<http://libvirt.org/formatdomain.html#elementsNICS>)
That is not good to define our REST API.
But for "network" type resource, there is a "network" attribute of
"source" resource.
And the value of "network" attribute is the name of network that we
defined by /networks POST method.
the "network" type + "network" attribute is unique.
We can define the Network Interfaces like this to support "network" type
Interfaces:
GET /vms/*name*/Interfaces?type=network&network="default"
But this is not RESTFUL.
Maybe We can also name a Network Interface by the Interface mac.
The mac is unique.
GET /vms/*name*/Interfaces/mac
The best way for user is get the NICS name in guest. such as eth0, eth1....
GET /vms/*name*/Interfaces/eth0
GET /vms/*name*/Interfaces/eth1
But there is a problem, we need get the nic name and mac from guest.
and Make a map in kimchi DB.
also the nic name may change, we need make the map consistence with
guest nic name.
--
Sheldon Feng(???)<shaohef(a)linux.vnet.ibm.com>
IBM Linux Technology Center
2
3
From: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>
now kimchi support supports network get/create/delete/activate/deactivate
REST API.
Now we need to support the attach/detach/ a Network to a VM.
https://github.com/kimchi-project/kimchi/wiki/customize-VM#organization-of-…
And get the info of Network attached to a VM.
more libvirt network interfaces info:
http://libvirt.org/formatdomain.html#elementsNICS
Signed-off-by: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>
---
docs/API.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 49 insertions(+)
diff --git a/docs/API.md b/docs/API.md
index 9edc551..2d3ce11 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -127,6 +127,55 @@ Represents a snapshot of the Virtual Machine's primary monitor.
* volume: A volume name that contains the initial disk contents
+### Sub-Collection: Virtual Machine Network Interfaces
+
+**URI:** /vms/*:name*/interfaces
+
+Represents all network interfaces attached to a Virtual Machine.
+
+**Methods:**
+
+* **GET**: Retrieve a summarized list of all network interfaces attached to a Virtual Machine.
+
+* **POST**: attach a network interface to VM
+ * type: The type of VM network interface that libvirt supports.
+ It can be one of these types: 'network', 'bridge', 'user','ethernet',
+ 'direct', 'hostdev', 'mcast', 'server' and 'client'.
+ Now kimchi just supports 'network' type.
+ * network *(optional)*: the name of resource network, it is required when the
+ interface type is network.
+ * model *(optional)*: model of emulated network interface card. It can be one of these models:
+ ne2k_pci, i82551, i82557b, i82559er, rtl8139, e1000, pcnet and virtio.
+ when model is missing, but already some interfaces is attached to VM, then will choose
+ one of model of the exist interfaces.
+ if on interface is attached to VM, then 'virtio' is default.
+
+### Sub-Resource: Virtual Machine Network Interface
+
+**URI:** /vms/*:name*/interfaces/*:name*
+
+A interface represents available network interface on VM.
+
+**Methods:**
+
+* **GET**: Retrieve the full description of the VM network interface
+ * name: The identifier of the network interface.
+ * type: The type of VM network interface that libvirt supports.
+ It will be one of these types: 'network', 'bridge', 'user','ethernet',
+ 'direct', 'hostdev', 'mcast', 'server' and 'client'.
+ * network *(optional)*: the name of resource network, only be available when the
+ interface type is network.
+ * bridge *(optional)*: the name of resource bridge, only be available when the
+ interface type is bridge.
+ * mac: Media Access Control Address of the VM interface.
+
+* **DELETE**: detach the network interface from VM
+
+**Actions (POST):**
+
+*No actions defined*
+
+
### Resource: Template
**URI:** /templates/*:name*
--
1.8.4.2
4
4
10 Jan '14
Added federated hosts;
Added host repository management.
There is a bug in kimchi.widget.Grid widget: when there are more than one
Grid instances in the page, column resizing function will work incorrectly.
I'll send another patch to fix it.
Hongliang Wang (2):
Federated Hosts Management
Host Repositories Management UI
src/kimchi/model.py | 1 +
ui/css/theme-default/host-add.css | 29 +++++++
ui/css/theme-default/host-edit.css | 29 +++++++
ui/css/theme-default/host.css | 80 +++++++++++++++---
ui/css/theme-default/hosts.css | 24 ++++++
ui/css/theme-default/repository-edit.css | 29 +++++++
ui/css/theme-default/window.css | 6 +-
ui/images/theme-default/host-icon-sprite.png | Bin 1034 -> 1163 bytes
ui/js/src/kimchi.api.js | 110 +++++++++++++++++++++++++
ui/js/src/kimchi.grid.js | 2 +-
ui/js/src/kimchi.host.js | 93 +++++++++++++++++++++
ui/js/src/kimchi.host_add_main.js | 62 ++++++++++++++
ui/js/src/kimchi.host_edit_main.js | 62 ++++++++++++++
ui/js/src/kimchi.hosts.js | 116 +++++++++++++++++++++++++++
ui/js/src/kimchi.repository_edit_main.js | 62 ++++++++++++++
ui/pages/guest-add.html.tmpl | 2 +-
ui/pages/guest-edit.html.tmpl | 2 +-
ui/pages/host-add.html.tmpl | 66 +++++++++++++++
ui/pages/host-edit.html.tmpl | 66 +++++++++++++++
ui/pages/hosts.html.tmpl | 41 ++++++++++
ui/pages/i18n.html.tmpl | 16 ++++
ui/pages/login-window.html.tmpl | 2 +-
ui/pages/report-add.html.tmpl | 2 +-
ui/pages/repository-edit.html.tmpl | 66 +++++++++++++++
ui/pages/storagepool-add.html.tmpl | 2 +-
ui/pages/tabs/host.html.tmpl | 40 ++++++++-
ui/pages/template-add.html.tmpl | 2 +-
ui/pages/template-edit.html.tmpl | 2 +-
28 files changed, 989 insertions(+), 25 deletions(-)
create mode 100644 ui/css/theme-default/host-add.css
create mode 100644 ui/css/theme-default/host-edit.css
create mode 100644 ui/css/theme-default/hosts.css
create mode 100644 ui/css/theme-default/repository-edit.css
create mode 100644 ui/js/src/kimchi.host_add_main.js
create mode 100644 ui/js/src/kimchi.host_edit_main.js
create mode 100644 ui/js/src/kimchi.hosts.js
create mode 100644 ui/js/src/kimchi.repository_edit_main.js
create mode 100644 ui/pages/host-add.html.tmpl
create mode 100644 ui/pages/host-edit.html.tmpl
create mode 100644 ui/pages/hosts.html.tmpl
create mode 100644 ui/pages/repository-edit.html.tmpl
--
1.8.1.4
2
3
v1-v2 Fix some bug in this patch, make it working and retest
This patch is working adding a select box in create nfs pool,
when user don't want to input the server ip or name, he can
simply select "used the server I have used before" and choose
a server in a select box, this is more convinent.
Add a new jquery filter-select widget
In order to show server target I did a select box in UI, but sometimes
maybe the export path number is large. So I made a filter-select
widget to make user easier to choose the export path he want.
This is the first jquery widget. And let us make all common widget to
jquery widget from now on.
Signed-off-by: zhoumeina <zhoumein(a)linux.vnet.ibm.com>
---
ui/css/theme-default/button.css | 11 +--
ui/css/theme-default/form.css | 6 ++
ui/css/theme-default/storage.css | 4 +-
ui/js/Makefile.am | 4 +-
ui/js/src/kimchi.api.js | 26 ++++++-
ui/js/src/kimchi.storagepool_add_main.js | 73 +++++++++++++++--
ui/js/src/kimchi.utils.js | 11 +++
ui/js/widgets/filter-select.js | 137 ++++++++++++++++++++++++++++++
ui/js/widgets/select-menu.js | 86 +++++++++++++++++++
ui/pages/i18n.html.tmpl | 5 +-
ui/pages/storagepool-add.html.tmpl | 41 +++++++--
11 files changed, 376 insertions(+), 28 deletions(-)
create mode 100644 ui/js/widgets/filter-select.js
create mode 100644 ui/js/widgets/select-menu.js
diff --git a/ui/css/theme-default/button.css b/ui/css/theme-default/button.css
index c7ed3f6..f99679a 100644
--- a/ui/css/theme-default/button.css
+++ b/ui/css/theme-default/button.css
@@ -247,17 +247,16 @@
.btn-select {
display: inline-block;
position: relative;
- height: 38px;
+ height: 30px;
padding-right: 20px;
margin: 5px;
vertical-align: top;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
- border-radius: 5px;
- background: #eee;
+ background: #fff;
box-shadow: -1px -1px 1px #666, 1px 1px 1px #fff, 2px 2px 2px rgba(0, 0, 0, .15) inset;
font-size: 13px;
- line-height: 38px;
+ line-height: 30px;
text-align: left;
cursor: pointer;
}
@@ -269,8 +268,8 @@
.btn-select .arrow {
position: absolute;
width: 15px;
- height: 38px;
- line-height: 38px;
+ height: 30px;
+ line-height: 30px;
top: 0;
right: 5px;
background: url(../images/theme-default/arrow-down-black.png) no-repeat center center;
diff --git a/ui/css/theme-default/form.css b/ui/css/theme-default/form.css
index c24b277..9db4bba 100644
--- a/ui/css/theme-default/form.css
+++ b/ui/css/theme-default/form.css
@@ -45,3 +45,9 @@
line-height: 30px;
padding: 0 5px;
}
+
+.text-help {
+ font-size: 12px;
+ color: #333;
+ margin: 0 0 5px 5px;
+}
diff --git a/ui/css/theme-default/storage.css b/ui/css/theme-default/storage.css
index d81dc75..ae89f1b 100644
--- a/ui/css/theme-default/storage.css
+++ b/ui/css/theme-default/storage.css
@@ -529,7 +529,7 @@
width: 300px;
display: inline-block;
vertical-align: top;
- padding: 5px 5px 5px 20px;
+ padding: 5px 5px 5px 22px;
}
.storage-type-wrapper-controls input[type="text"] {
@@ -548,7 +548,7 @@
.storage-type-wrapper-controls > .dropdown {
margin: 5px 0 0 1px;
- width: 250px;
+ width: 150px;
}
.storage-type-wrapper-controls input[type="text"][disabled] {
diff --git a/ui/js/Makefile.am b/ui/js/Makefile.am
index 337e369..8dfd4d6 100644
--- a/ui/js/Makefile.am
+++ b/ui/js/Makefile.am
@@ -20,7 +20,7 @@
SUBDIRS = novnc
-EXTRA_DIST = src
+EXTRA_DIST = src widgets
jsdir = $(datadir)/kimchi/ui/js
@@ -32,7 +32,7 @@ dist_js_DATA = \
modernizr.custom.2.6.2.min.js \
$(NULL)
-kimchi.min.js: src/*.js
+kimchi.min.js: widgets/*.js src/*.js
cat $(sort $^) > $@
CLEANFILES = kimchi.min.js
diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js
index fbcf4a2..f6115a8 100644
--- a/ui/js/src/kimchi.api.js
+++ b/ui/js/src/kimchi.api.js
@@ -681,5 +681,29 @@ var kimchi = {
success : suc,
error : err
});
+ },
+
+ getStorageServers: function(type, suc, err) {
+ var url = kimchi.url + 'storageservers?target_type=' + type;
+ kimchi.requestJSON({
+ url : url,
+ type : 'GET',
+ contentType : 'application/json',
+ dataType : 'json',
+ success : suc,
+ error : err
+ });
+ },
+
+ getStorageTargets: function(server,type, suc, err) {
+ var url = kimchi.url + 'storageservers/' + server + '/storagetargets?target_type=' + type;
+ kimchi.requestJSON({
+ url : url,
+ type : 'GET',
+ contentType : 'application/json',
+ dataType : 'json',
+ success : suc,
+ error : err
+ });
}
-};
+};
\ No newline at end of file
diff --git a/ui/js/src/kimchi.storagepool_add_main.js b/ui/js/src/kimchi.storagepool_add_main.js
index b31610a..7955352 100644
--- a/ui/js/src/kimchi.storagepool_add_main.js
+++ b/ui/js/src/kimchi.storagepool_add_main.js
@@ -51,7 +51,72 @@ kimchi.initStorageAddPage = function() {
});
$('.host-partition').html(listHtml);
}
- kimchi.select('storagePool-list', options);
+ $('#storagePool-list').selectMenu();
+ $('#storagePool-list').selectMenu("setData", options);
+ kimchi.getStorageServers('netfs', function(data) {
+ var serverContent = [];
+ serverContent.push({
+ label : i18n['select_default'],
+ value : ''
+ });
+ if (data.length > 0) {
+ $.each(data, function(index, value) {
+ serverContent.push({
+ label : value,
+ value : value
+ });
+ });
+ }
+ $('#nfs-server-used').selectMenu();
+ $('#nfs-server-used').selectMenu("setData", serverContent);
+ $('#nfs-server-target').filterselect();
+ $('input[name=nfsServerType]').change(function() {
+ if ($(this).val() === 'input') {
+ $('#nfsServerInputDiv').removeClass('tmpl-html');
+ $('#nfsServerChooseDiv').addClass('tmpl-html');
+ } else {
+ $('#nfsServerInputDiv').addClass('tmpl-html');
+ $('#nfsServerChooseDiv').removeClass('tmpl-html');
+ }
+ });
+ $('#nfsServerSelect').change(function() {
+ $('#nfsserverId').val($(this).val());
+ $('#nfsserverId').trigger("keydown");
+ });
+ $('#nfsserverId').on("keydown",function() {
+ if ($(this).val() !== '' && kimchi.isServer($(this).val())) {
+ $('#nfspathId').removeAttr('disabled');
+ } else {
+ $('#nfspathId').attr('disabled','disabled');
+ }
+ $('#nfs-server-target').filterselect('clear');
+ });
+ $('#nfspathId').focus(function() {
+ var targetContent = [];
+ kimchi.getStorageTargets($('#nfsserverId').val(), 'netfs', function(data) {
+ if (data.length > 0) {
+ $.each(data, function(index, value) {
+ targetContent.push({
+ label : value.target,
+ value : value.target
+ });
+ });
+ } else {
+ targetContent.push({
+ label : i18n['msg.no.result'],
+ value : ''
+ });
+ }
+ $('#nfs-server-target').filterselect("setData", targetContent);
+ },function() {
+ targetContent.push({
+ label : i18n['msg.no.result'],
+ value : ''
+ });
+ $('#nfs-server-target').filterselect("setData", targetContent);
+ });
+ });
+ });
$('#poolType').change(function() {
if ($(this).val() === 'dir') {
$('.path-section').removeClass('tmpl-html');
@@ -88,7 +153,6 @@ kimchi.validateForm = function() {
} else {
return kimchi.validateLogicalForm();
}
-
};
kimchi.validateDirForm = function () {
@@ -116,11 +180,8 @@ kimchi.validateNfsForm = function () {
kimchi.message.error(i18n['msg.pool.edit.nfspath.blank']);
return false;
}
- var domain = "([0-9a-z_!~*'()-]+\.)*([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]\.[a-z]{2,6}"
- var ip = "(\\d{1,3}\.){3}\\d{1,3}"
- regex = new RegExp('^' + domain + '|' + ip + '$')
- if(!regex.test(nfsserver)) {
+ if(kimchi.isServer(nfsserver)) {
kimchi.message.error(i18n['msg.validate.pool.edit.nfsserver']);
return false;
}
diff --git a/ui/js/src/kimchi.utils.js b/ui/js/src/kimchi.utils.js
index 8af6a11..6439db6 100644
--- a/ui/js/src/kimchi.utils.js
+++ b/ui/js/src/kimchi.utils.js
@@ -163,3 +163,14 @@ kimchi.changetoProperUnit = function(numOrg, digits, base) {
kimchi.formatMeasurement = format;
})();
+
+kimchi.isServer = function(server) {
+ var domain = "([0-9a-z_!~*'()-]+\.)*([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]\.[a-z]{2,6}"
+ var ip = "(\\d{1,3}\.){3}\\d{1,3}"
+ regex = new RegExp('^' + domain + '|' + ip + '$');
+ if(!regex.test(server)) {
+ return false;
+ } else {
+ return true;
+ }
+};
\ No newline at end of file
diff --git a/ui/js/widgets/filter-select.js b/ui/js/widgets/filter-select.js
new file mode 100644
index 0000000..02fe5d9
--- /dev/null
+++ b/ui/js/widgets/filter-select.js
@@ -0,0 +1,137 @@
+/*
+ * Project Kimchi
+ *
+ * Copyright IBM, Corp. 2013
+ *
+ * Authors:
+ * zhoumeina <zhoumein(a)linux.vnet.ibm.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the 'License');
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function($) {
+ $.widget('kimchi.filterselect', {
+ options : {
+ key : 0,
+ value : 0
+ },
+
+ _create : function() {
+ this.listControl = this.element;
+ this.element.html('');
+ var targetId = this.listControl.data('target');
+ var labelId = this.listControl.data('label');
+ this.target = $('#' + targetId);
+ this.label = $('#' + labelId);
+ this.selectDiv = this.listControl.parent().parent();
+ this.selectDiv.addClass('btn-select dropdown popable');
+ this.listControl.parent().addClass('popover');
+ },
+
+ _setOption : function(key, value) {
+ },
+
+ setData : function(options) {
+ this.element.html('');
+ var that = this;
+ var value = this.target.val();
+ var selectedClass = 'active';
+ var itemTag = 'li';
+
+ that.listControl.on('click', itemTag, function(e) {
+ that.listControl.children().removeClass(selectedClass);
+ that.listControl.addClass(selectedClass);
+ that.target.text($(this).text());
+ var oldValue = that.target.val();
+ var newValue = $(this).data('value');
+ that.target.val(newValue);
+ if (oldValue !== newValue) {
+ that.target.change();
+ }
+ });
+
+ that.selectDiv.click(function(e) {
+ that.listControl.html('');
+ var items = that._dataList(options);
+ $.each(items, function(index, item) {
+ that.listControl.append(item);
+ })
+ var isOpen = that.selectDiv.hasClass('open');
+ that.selectDiv.removeClass('open');
+ if (!isOpen && items.length > 0) {
+ that.selectDiv.addClass('open');
+ }
+ e.preventDefault();
+ e.stopPropagation();
+ });
+
+ that.target.keyup(function(event) {
+ that.listControl.html('');
+ var items = that._dataList(options);
+ var temp = 0;
+ $.each(items, function(index, item) {
+ if (item.html().indexOf(that.target.val()) >= 0) {
+ that.listControl.append(item);
+ temp++;
+ }
+ });
+ if (that.listControl.html() === '') {
+ that.listControl.html(i18n['msg.storagepool.unvalid.path']);
+ }
+ if (temp > 0) {
+ that._open();
+ }
+ });
+ },
+
+ _dataList : function(options) {
+ var item;
+ var itemTag = 'li';
+ var selectedClass = 'active';
+ var items = [];
+ var that = this;
+ $.each(options, function(index, option) {
+ item = $('<' + itemTag + '></' + itemTag + '>');
+ item.text(option.label);
+ item.data('value', option.value);
+ if (option.value === that.target.val()) {
+ item.addClass(selectedClass);
+ }
+ items.push(item);
+ });
+ return items;
+ },
+
+ clear : function() {
+ this.target.val("");
+ },
+
+ _open : function() {
+ var isOpen = this.selectDiv.hasClass('open');
+ if (!isOpen) {
+ this.selectDiv.addClass('open');
+ }
+ },
+
+ _close : function() {
+ var isOpen = this.selectDiv.hasClass('open');
+ if (isOpen) {
+ this.selectDiv.removeClass('open');
+ }
+ },
+
+ destroy : function() {
+ // call the base destroy function
+ $.Widget.prototype.destroy.call(this);
+ }
+ });
+}(jQuery));
\ No newline at end of file
diff --git a/ui/js/widgets/select-menu.js b/ui/js/widgets/select-menu.js
new file mode 100644
index 0000000..4124096
--- /dev/null
+++ b/ui/js/widgets/select-menu.js
@@ -0,0 +1,86 @@
+/*
+ * Project Kimchi
+ *
+ * Copyright IBM, Corp. 2013
+ *
+ * Authors:
+ * zhoumeina <zhoumein(a)linux.vnet.ibm.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the 'License');
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function($) {
+ $.widget('kimchi.selectMenu', {
+ options: {
+ key: 0,
+ value: 0
+ },
+
+ _create : function() {
+ this.listControl = this.element;
+ this.element.html('');
+ var targetId = this.listControl.data('target');
+ var labelId = this.listControl.data('label');
+ this.target = $('#' + targetId);
+ this.label = $('#' + labelId);
+ this.selectDiv = this.listControl.parent().parent();
+ this.selectDiv.addClass('btn-select dropdown popable');
+ this.listControl.parent().addClass('popover');
+ var that = this;
+ },
+
+ _setOption : function(key,value) {},
+
+ setData : function (options) {
+ var that = this;
+ var value = this.target.val();
+ var selectedClass = 'active';
+ var itemTag = 'li';
+ $.each(options, function(index, option) {
+ item = $('<' + itemTag + '></' + itemTag + '>');
+ item.text(option.label);
+ item.data('value', option.value);
+ if(option.value === value) {
+ item.addClass(selectedClass);
+ that.label.text(option.label);
+ }
+ that.listControl.append(item);
+ });
+ that.listControl.on('click', itemTag, function() {
+ that.listControl.children().removeClass(selectedClass);
+ that.listControl.addClass(selectedClass);
+ that.label.text($(this).text());
+ var oldValue = that.target.val();
+ var newValue = $(this).data('value');
+ that.target.val(newValue);
+ if(oldValue !== newValue) {
+ that.target.change();
+ }
+ });
+
+ that.selectDiv.click(function(e) {
+ var isOpen = that.selectDiv.hasClass('open');
+ that.selectDiv.removeClass('open');
+ if (!isOpen) {
+ that.selectDiv.addClass('open');
+ }
+ e.preventDefault();
+ e.stopPropagation();
+ });
+ },
+
+ destroy : function() {
+ // call the base destroy function
+ $.Widget.prototype.destroy.call(this);
+ }
+ });
+}(jQuery));
\ No newline at end of file
diff --git a/ui/pages/i18n.html.tmpl b/ui/pages/i18n.html.tmpl
index c1fc3d1..c95da9b 100644
--- a/ui/pages/i18n.html.tmpl
+++ b/ui/pages/i18n.html.tmpl
@@ -121,7 +121,10 @@ var i18n = {
'action_create': "$_("Create")",
'msg_warning': "$_("Warning")",
'msg.logicalpool.confirm.delete': "$_("It will format your disk and you will loose any data in"
- " there, are you sure to continue? ")"
+ " there, are you sure to continue? ")",
+ 'select_default': "$_("Please choose")",
+ 'msg.storagepool.unvalid.path': "$_("This is not a valid path")",
+ 'msg.no.result' : "$_("No valid result")"
};
</script>
</body>
diff --git a/ui/pages/storagepool-add.html.tmpl b/ui/pages/storagepool-add.html.tmpl
index 5a2dd45..3a73390 100644
--- a/ui/pages/storagepool-add.html.tmpl
+++ b/ui/pages/storagepool-add.html.tmpl
@@ -27,7 +27,7 @@
<!DOCTYPE html>
<html>
<body>
- <div class="window" style="width: 600px; height: 600px;">
+ <div class="window" style="width: 800px; height: 650px;">
<header>
<h1 class="title">$_("Define a New Storage Pool")</h1>
<div class="close">X</div>
@@ -47,10 +47,10 @@
<section class="form-section">
<h2>2. $_("Storage Pool Type")</h2>
<div class="storage-type-wrapper-controls">
- <div class="btn dropdown popable">
+ <div>
<input id="poolType" name="type" type="hidden" value="dir"/>
<span class="text" id="pool-type-label"></span><span class="arrow"></span>
- <div class="popover" style="width: 100%">
+ <div style="width: 100%">
<ul class="select-list" id="storagePool-list" data-target="poolType" data-label="pool-type-label">
</ul>
</div>
@@ -74,22 +74,43 @@
<section class="form-section">
<h2>3. $_("NFS server IP")</h2>
<div class="field">
+ <input type="radio" id="nfsServerInput" value="input" name="nfsServerType" checked="true">
+ <label>$_("I want to input the server myself.")</label>
+ <input type="radio" id="nfsServerChoose" value="choose" name="nfsServerType">
+ <label>$_("I want to choose a server I used before.")</label>
+ </div>
+ <div id="nfsServerInputDiv" class="field">
<p class="text-help">
$_("NFS server IP or hostname. It should not be empty.")</p>
<input id="nfsserverId" type="text" class="text"
style="width: 300px">
</div>
+ <div id="nfsServerChooseDiv" class="field tmpl-html" style="overflow:visible">
+ <p class="text-help">
+ $_("Please choose the nfs server you want to create storage pool.")</p>
+ <div style="width: 285px">
+ <input id="nfsServerSelect" type="hidden"/>
+ <span class="text" id="nfs-server-label"></span><span class="arrow"></span>
+ <div style="width: 100%">
+ <ul class="select-list" id="nfs-server-used" data-target="nfsServerSelect" data-label="nfs-server-label">
+ </ul>
+ </div>
+ </div>
+ </div>
</section>
<section class="form-section">
<h2>4. $_("NFS Path")</h2>
- <div class="field">
+ <div class="field" style="overflow:visible">
<p class="text-help">$_("The nfs exported path on nfs server")</p>
- <input id="nfspathId" type="text" class="text"
- style="width: 300px">
- <input type="hidden" id="localpathId" class="text"
- value="none">
- </div>
- <div class="clear"></div>
+ <div style="width: 285px">
+ <input id="nfspathId" class="text" disabled="disabled" style="width:293px;"/>
+ <span class="text" id="nfs-target-label"></span><span class="arrow"></span>
+ <div style="width: 100%">
+ <ul class="select-list" id="nfs-server-target" data-target="nfspathId" data-label="nfs-target-label">
+ </ul>
+ </div>
+ </div>
+ </div>
</section>
</div>
<div class="logical-section tmpl-html">
--
1.7.1
3
6
v1-v2 modify some of translation messages
Include create storage pool UI and translation files.
zhoumeina (2):
Add UI support of iscsi
Add the ISCSI translation po files
po/en_US.po | 31 +++++++++++++----
po/kimchi.pot | 30 ++++++++++++----
po/pt_BR.po | 31 +++++++++++++----
po/zh_CN.po | 31 +++++++++++++----
ui/js/src/kimchi.storagepool_add_main.js | 54 ++++++++++++++++++++++++-----
ui/pages/i18n.html.tmpl | 7 ++--
ui/pages/storagepool-add.html.tmpl | 22 +++++++++++-
7 files changed, 159 insertions(+), 47 deletions(-)
mode change 100644 => 100755 po/kimchi.pot
4
9
From: Aline Manera <alinefm(a)br.ibm.com>
This patch cleans up pep8 style issue in distroloader.py
Signed-off-by: Aline Manera <alinefm(a)br.ibm.com>
---
Makefile.am | 1 +
src/kimchi/distroloader.py | 4 ++--
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index 0fd92c8..5460240 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -43,6 +43,7 @@ PEP8_WHITELIST = \
src/kimchi/cachebust.py \
src/kimchi/config.py.in \
src/kimchi/disks.py \
+ src/kimchi/distroloader.py \
src/kimchi/root.py \
src/kimchi/server.py \
plugins/__init__.py \
diff --git a/src/kimchi/distroloader.py b/src/kimchi/distroloader.py
index 6310644..98fd764 100644
--- a/src/kimchi/distroloader.py
+++ b/src/kimchi/distroloader.py
@@ -18,7 +18,7 @@
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
import glob
@@ -46,7 +46,7 @@ class DistroLoader(object):
data = json.load(f)
return data
except ValueError:
- msg = "DistroLoader: failed to parse json from distro file: %s" % fname
+ msg = "DistroLoader: failed to parse distro file: %s" % fname
kimchi_log.error(msg)
raise OperationFailed(msg)
--
1.7.10.4
2
2
From: Aline Manera <alinefm(a)br.ibm.com>
This patch cleans up pep8 style issue in exception.py
Signed-off-by: Aline Manera <alinefm(a)br.ibm.com>
---
Makefile.am | 1 +
src/kimchi/exception.py | 7 ++++++-
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/Makefile.am b/Makefile.am
index 0fd92c8..3b8e494 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -43,6 +43,7 @@ PEP8_WHITELIST = \
src/kimchi/cachebust.py \
src/kimchi/config.py.in \
src/kimchi/disks.py \
+ src/kimchi/exception.py \
src/kimchi/root.py \
src/kimchi/server.py \
plugins/__init__.py \
diff --git a/src/kimchi/exception.py b/src/kimchi/exception.py
index d7a2835..df9619f 100644
--- a/src/kimchi/exception.py
+++ b/src/kimchi/exception.py
@@ -18,19 +18,24 @@
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
class NotFoundError(Exception):
pass
+
class OperationFailed(Exception):
pass
+
class MissingParameter(Exception):
pass
+
class InvalidParameter(Exception):
pass
+
class InvalidOperation(Exception):
pass
--
1.7.10.4
2
2
From: Aline Manera <alinefm(a)br.ibm.com>
Aline Manera (6):
isoinfo: Add ignore_list paramter to main program
isoinfo: Use absolute path only for local ISO files
Move IsoFormatError() from isoinfo.py to exception.py
Move ISO path validation to IsoImage()
isoinfo: Move _probe_iso() to IsoImage()
pep8 cleanup for isoinfo.py
Makefile.am | 1 +
src/kimchi/exception.py | 4 ++
src/kimchi/isoinfo.py | 138 +++++++++++++++++++++++-----------------------
src/kimchi/model.py | 11 ++--
src/kimchi/scan.py | 5 +-
src/kimchi/vmtemplate.py | 8 ++-
6 files changed, 87 insertions(+), 80 deletions(-)
--
1.7.10.4
3
22
The current kimchi plugin subsystem implement a dynamic discovery and
import mechanism. It uses the Python builtin function __import__() to
actually import a plugin module by name. However it turns out it only
works when we start kimchi from the project source directory, not work
when it is started from the installed path.
This is because the __import__() accepts a "global" arugment and it
needs "__name__", "__path__" and "__package__" in this "global" dict to
determine the package context [1][2]. When in kimchi/utils.py it runs
"import plugins.XXX", it actually fetch the "__package__" from utils.py,
which is "kimchi", then it imports "kimchi.plugins.XXX" under the hood
[3].
The current import_module() function calls __import__() without giving a
global dict. When kimchid is started from /usr/bin, without the package
context, __import__() can not find "plugins.sample".
This patch provides the full package context to __import__() to mimic
the standard "import" statement behavior.
[1] http://docs.python.org/2/library/functions.html#__import__
[2] http://hg.python.org/cpython/file/990d7647ea51/Python/import.c
[3] http://docs.python.org/2/reference/simple_stmts.html#import
Signed-off-by: Zhou Zheng Sheng <zhshzhou(a)linux.vnet.ibm.com>
---
src/kimchi/utils.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/kimchi/utils.py b/src/kimchi/utils.py
index f7eda93..b6d84fd 100644
--- a/src/kimchi/utils.py
+++ b/src/kimchi/utils.py
@@ -83,4 +83,4 @@ def import_class(class_path):
def import_module(module_name):
- return __import__(module_name, fromlist=[''])
+ return __import__(module_name, globals(), locals(), [''])
--
1.7.11.7
4
6
This patch is working for make dropDown button a jquery
widget.
We can create a dropdown button simply with:
$('#buttonId').dropdownButton();
And in html:
<div id="buttonId">
<span class="text">$_("Button label")</span><span class="arrow"></span>
<div>
<button><span class="text">$_("dropdownbutton1")</span></button>
<button><span class="text">$_("dropdownbutton2")</span></button>
</div>
</div>
If we need some other style, you can add it to html, and bind action in js.
Not a perfect widget, but a beginning.
Signed-off-by: zhoumeina <zhoumein(a)linux.vnet.ibm.com>
---
ui/js/widgets/button-dropDown.js | 42 ++++++++++++++++++++++++++++++++++++++
1 files changed, 42 insertions(+), 0 deletions(-)
create mode 100644 ui/js/widgets/button-dropDown.js
diff --git a/ui/js/widgets/button-dropDown.js b/ui/js/widgets/button-dropDown.js
new file mode 100644
index 0000000..49259e3
--- /dev/null
+++ b/ui/js/widgets/button-dropDown.js
@@ -0,0 +1,42 @@
+/*
+ * Project Kimchi
+ *
+ * Copyright IBM, Corp. 2013
+ *
+ * Authors:
+ * zhoumeina <zhoumein(a)linux.vnet.ibm.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the 'License');
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function($) {
+ $.widget('kimchi.dropdownButton', {
+ options: {
+ key: 0,
+ value: 0
+ },
+
+ _create : function() {
+ this.actionDiv = this.element;
+ this.actionDiv.addClass('btn dropdown popable');
+ this.actionDiv.find('div').addClass('popover');
+ this.actionDiv.find('button').addClass('button-big');
+ },
+
+ _setOption : function(key,value) {},
+
+ destroy : function() {
+ // call the base destroy function
+ $.Widget.prototype.destroy.call(this);
+ }
+ });
+}(jQuery));
\ No newline at end of file
--
1.7.1
3
2
From: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>
Add vms field for network GET method
update API.md
add a function to get all vms attach to a network
update model
add vms field for Network Resource property
update controller
Now get the info of default network:
$ curl -u <user>:<password> -H 'Content-type: application/json' -H 'Accept:
application/json' -X GET http://localhost:8000/networks/default
{
"subnet":"192.168.122.0/24",
"connection":"nat",
"name":"default",
"autostart":true,
"state":"active",
"interface":"virbr0",
"dhcp":{
"start":"192.168.122.2",
"end":"192.168.122.254"
},
"vms":[
"rhel6",
"opensuse12"
]
}
we can see, there are two vms attched to this network
Signed-off-by: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>
---
docs/API.md | 1 +
src/kimchi/controller.py | 1 +
src/kimchi/model.py | 15 +++++++++++++++
3 files changed, 17 insertions(+)
diff --git a/docs/API.md b/docs/API.md
index 9edc551..a3e6c8a 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -348,6 +348,7 @@ A interface represents available interface on host.
* active: The Network is ready for use
* inactive: The Network is not available
* autostart: Network autostart onboot
+ * vms: all vms attached to this network
* subnet: Network segment in slash-separated format with ip address and prefix
* dhcp: DHCP services on the virtual network is enabled.
* start: start boundary of a pool of addresses to be provided to DHCP clients.
diff --git a/src/kimchi/controller.py b/src/kimchi/controller.py
index 2940278..63b0820 100644
--- a/src/kimchi/controller.py
+++ b/src/kimchi/controller.py
@@ -459,6 +459,7 @@ class Network(Resource):
@property
def data(self):
return {'name': self.ident,
+ 'vms': self.info['vms'],
'autostart': self.info['autostart'],
'connection': self.info['connection'],
'interface': self.info['interface'],
diff --git a/src/kimchi/model.py b/src/kimchi/model.py
index a6790b8..015eb46 100644
--- a/src/kimchi/model.py
+++ b/src/kimchi/model.py
@@ -546,6 +546,11 @@ class Model(object):
xpath = "/domain/devices/disk[@device='disk']/source/@file"
return xmlutils.xpath_get_text(xml, xpath)
+ def _vm_get_networks(self, dom):
+ xml = dom.XMLDesc(0)
+ xpath = "/domain/devices/interface[@type='network']/source/@network"
+ return xmlutils.xpath_get_text(xml, xpath)
+
def vm_delete(self, name):
if self._vm_exists(name):
conn = self.conn.get()
@@ -844,6 +849,15 @@ class Model(object):
conn = self.conn.get()
return sorted(conn.listNetworks() + conn.listDefinedNetworks())
+ def _get_vms_attach_to_a_network(self, network):
+ vms = []
+ conn = self.conn.get()
+ for dom in conn.listAllDomains():
+ networks = self._vm_get_networks(dom)
+ if network in networks:
+ vms.append(dom.name())
+ return vms
+
def network_lookup(self, name):
network = self._get_network(name)
xml = network.XMLDesc(0)
@@ -872,6 +886,7 @@ class Model(object):
'interface': interface,
'subnet': subnet,
'dhcp': dhcp,
+ 'vms': self._get_vms_attach_to_a_network(name),
'autostart': network.autostart() == 1,
'state': network.isActive() and "active" or "inactive"}
--
1.8.4.2
3
2
[RESEND PATCH V5 0/1] Open 8000 and 8001 port by default for distro packages
by taget@linux.vnet.ibm.com 03 Jan '14
by taget@linux.vnet.ibm.com 03 Jan '14
03 Jan '14
From: Eli Qiao <taget(a)linux.vnet.ibm.com>
V5 - V4 changes:
1. Add cover-letter. (Aline)
2. Move clean up rules into if condition. (Aline)
3. Use with_systemd condition to check if use firewalld rules. (Aline)
4. Fix typo (Aline)
V4 - V3 changes:
1 Fix typo in firewalld.xml (Rodrigo)
V3 - V2 changes:
1.Rename kimchid.xml to firewalld.xml (Mark)
2.Remove firewalld from serivce require (Mark)
3.Fix typo
V2 - V1 changes:
1.Add firewalld sevice configure file kimchid.xml to help open iptables port (Mark)
2.Add Ubuntu iptables rule (Royce)
Eli Qiao (1):
spec: Open 8000 and 8001 port by default
contrib/DEBIAN/control.in | 3 ++-
contrib/DEBIAN/postinst | 2 ++
contrib/DEBIAN/postrm | 2 ++
contrib/kimchi.spec.fedora.in | 24 +++++++++++++++++++++---
contrib/kimchi.spec.suse.in | 10 ++++++++--
src/Makefile.am | 1 +
src/firewalld.xml | 7 +++++++
7 files changed, 43 insertions(+), 6 deletions(-)
create mode 100644 src/firewalld.xml
3
3
03 Jan '14
From: Royce Lv <lvroyce(a)linux.vnet.ibm.com>
v3>v4, fix inconsistency between doc and json schema
v1>v3, fix racing problem, fix style.
Add parameters to GET request so that we will query storage server as:
/storageservers?type=netfs
Royce Lv (5):
Support params for GET method
Add testcase for GET param
Storage server: Update API.md
storage server: update controller.py
storage server: Update model and mockmodel
docs/API.md | 13 +++++++++++++
src/kimchi/API.json | 11 +++++++++++
src/kimchi/controller.py | 26 ++++++++++++++++++++++++--
src/kimchi/mockmodel.py | 13 +++++++++++++
src/kimchi/model.py | 14 +++++++++++++-
src/kimchi/root.py | 1 +
tests/test_rest.py | 37 +++++++++++++++++++++++++++++++++++++
7 files changed, 112 insertions(+), 3 deletions(-)
--
1.8.1.2
4
23
The configruation is also needed for other code except starting kimchi
server. So it should be moved to a separate module, config.py. Then the
configuration can be accessed directly by importing config module.
Signed-off-by: Mark Wu <wudxw(a)linux.vnet.ibm.com>
---
src/kimchi/config.py.in | 23 ++++++++++++++++++++---
src/kimchid.in | 20 +-------------------
2 files changed, 21 insertions(+), 22 deletions(-)
diff --git a/src/kimchi/config.py.in b/src/kimchi/config.py.in
index f3c408a..49d42db 100644
--- a/src/kimchi/config.py.in
+++ b/src/kimchi/config.py.in
@@ -25,11 +25,9 @@
import libvirt
import os
import platform
-
-
+from ConfigParser import SafeConfigParser
from glob import iglob
-
from kimchi.xmlutils import xpath_get_text
@@ -166,5 +164,24 @@ def get_plugin_tab_xml(name):
return os.path.join(_get_plugin_ui_dir(name), 'config/tab-ext.xml')
+CONFIG_FILE = "%s/kimchi.conf" % get_config_dir()
+DEFAULT_LOG_LEVEL = "debug"
+
+config = SafeConfigParser()
+config.add_section("server")
+config.set("server", "host", "0.0.0.0")
+config.set("server", "port", "8000")
+config.set("server", "ssl_port", "8001")
+config.set("server", "ssl_cert", "")
+config.set("server", "ssl_key", "")
+config.set("server", "environment", "development")
+config.add_section("logging")
+config.set("logging", "log_dir", get_default_log_dir())
+config.set("logging", "log_level", DEFAULT_LOG_LEVEL)
+
+if os.path.exists(CONFIG_FILE):
+ config.read(CONFIG_FILE)
+
+
if __name__ == '__main__':
print get_prefix()
diff --git a/src/kimchid.in b/src/kimchid.in
index 7865713..548fa52 100644
--- a/src/kimchid.in
+++ b/src/kimchid.in
@@ -33,31 +33,13 @@ import kimchi.config
if kimchi.config.without_installation():
sys.path.append(kimchi.config.get_prefix())
-from ConfigParser import SafeConfigParser
+from kimchi.config import config
from optparse import OptionParser
ACCESS_LOG = "kimchi-access.log"
ERROR_LOG = "kimchi-error.log"
-CONFIG_FILE = "%s/kimchi.conf" % kimchi.config.get_config_dir()
-DEFAULT_LOG_DIR = kimchi.config.get_default_log_dir()
-DEFAULT_LOG_LEVEL = "debug"
def main(options):
- config = SafeConfigParser()
- config.add_section("server")
- config.set("server", "host", "0.0.0.0")
- config.set("server", "port", "8000")
- config.set("server", "ssl_port", "8001")
- config.set("server", "ssl_cert", "")
- config.set("server", "ssl_key", "")
- config.set("server", "environment", "development")
- config.add_section("logging")
- config.set("logging", "log_dir", DEFAULT_LOG_DIR)
- config.set("logging", "log_level", DEFAULT_LOG_LEVEL)
-
- if os.path.exists(CONFIG_FILE):
- config.read(CONFIG_FILE)
-
host = config.get("server", "host")
port = config.get("server", "port")
ssl_port = config.get("server", "ssl_port")
--
1.8.3.1
4
15
02 Jan '14
Resizing function corrupts when there are more than one kimchi.widget.Grid
instances in a page.
Signed-off-by: Hongliang Wang <hlwang(a)linux.vnet.ibm.com>
---
ui/js/src/kimchi.grid.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/ui/js/src/kimchi.grid.js b/ui/js/src/kimchi.grid.js
index 6e5f1fe..022c4f5 100644
--- a/ui/js/src/kimchi.grid.js
+++ b/ui/js/src/kimchi.grid.js
@@ -308,6 +308,7 @@ kimchi.widget.Grid = function(params) {
left - leftmostOffset
);
fixTableLayout();
+ columnBeingResized = null;
};
var resizeColumnWidth = function(index, width) {
--
1.8.1.4
3
2
From: Royce Lv <lvroyce(a)linux.vnet.ibm.com>
v1>v2, when showmount got no result, instead of fail the request,
return empty list to user.
NOTE: this patchset depends on storage server support patchset.
Tested in Fedora 19, due to libvirt bug, ubuntu 13.10 cannot be tested, fixing.
Usage:
GET /storageservers/<ip or hostname>/storagetargets?target_type=netfs
Royce Lv (3):
storage target: Update API.md
storage target: Update controller and json schema
storage target: Add model support
docs/API.md | 9 +++++++++
src/kimchi/API.json | 11 +++++++++++
src/kimchi/controller.py | 30 ++++++++++++++++++++++++++++--
src/kimchi/model.py | 40 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 88 insertions(+), 2 deletions(-)
--
1.8.1.2
3
7
This patch series is about creating iSCSI storage pool.
After this patch, you can create an ISCSI storage pool as the following.
curl -u root -H 'Content-type: application/json' \
-H 'Accept: application/json' \
-d '{"source": {
"target": "iqn.2013-01.example.com.targetname",
"host": "192.168.X.X",
"port": 326X,
"auth": {"username": "testUser", "password": "123456"}},
"type": "iscsi",
"name": "testIscsiPool"}' \
http://127.0.0.1:8000/storagepools
Note the "port" and "auth" is optional.
Thanks the reviewers. I took most of reviewer advices and submit this
v4 patch series.
Tested the patch over LIO iSCSI target and libvirt-1.1.4-1.fc19.
http://linux-iscsi.org/wiki/ISCSI
v3 -> v4
storagepool: refactor _get_pool_xml()
Change StoragePoolDef.get_xml() into a cached property
StoragePoolDef.xml.
storagepool: rename and consolidate arguments of creating (front end)
No change.
storagepool: rename and consolidate arguments of creating (back end)
Update src/kimchi/API.json
storagepool: Support Creating iSCSI storagepool in model.py
Fix typo.
Give the iscsiadm wrapper class a friendly name TargetClient.
Update API.json.
Update RPM spec files and Debian control file.
iscsi.validate_iscsi_target() is now TargetClient.validate().
test_model: test creating iSCSI storage pool
Since the parent patch changed interface, the tests also change to
use StoragePoolDef.xml and TargetClient.validate(), no other change.
Zhou Zheng Sheng (5):
storagepool: refactor _get_pool_xml()
storagepool: rename and consolidate arguments of creating (back end)
storagepool: rename and consolidate arguments of creating (front end)
storagepool: Support Creating iSCSI storagepool in model.py
test_model: test creating iSCSI storage pool
Makefile.am | 2 +
contrib/DEBIAN/control.in | 3 +-
contrib/kimchi.spec.fedora.in | 1 +
contrib/kimchi.spec.suse.in | 1 +
docs/API.md | 28 ++--
src/kimchi/API.json | 69 ++++++++++
src/kimchi/Makefile.am | 1 +
src/kimchi/iscsi.py | 89 ++++++++++++
src/kimchi/model.py | 227 +++++++++++++++++++++++++------
tests/Makefile.am | 1 +
tests/test_model.py | 89 ++++++------
tests/test_storagepool.py | 156 +++++++++++++++++++++
ui/js/src/kimchi.storagepool_add_main.js | 13 +-
13 files changed, 590 insertions(+), 90 deletions(-)
create mode 100644 src/kimchi/iscsi.py
create mode 100644 tests/test_storagepool.py
--
1.7.11.7
3
16
From: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>
V4 -> V5:
rebase to the latest commit.
for "move RollbackContext..." merged.
V3 -> V4:
after template supports networks, we should change the test case accordingly
aline refactor controller, rebase to the latest commit.
V2 -> V3:
fix typo.
support creating a vm without network.
V1 -> V2:
update mockmodel and test case
add 'networks' option for template get/create/update
ShaoHe Feng (7):
template supports networks: let template xml support more networks
template supports networks: fix test case
template supports networks: update API
template supports networks: update controller and json schema
template supports networks: update model
template supports networks: update mockmodel
template supports networks: update test case
docs/API.md | 4 ++
src/kimchi/API.json | 12 ++++++
src/kimchi/control/templates.py | 3 +-
src/kimchi/mockmodel.py | 7 +++-
src/kimchi/model.py | 25 ++++++++-----
src/kimchi/osinfo.py | 5 ++-
src/kimchi/vmtemplate.py | 20 ++++++++--
tests/test_model.py | 82 ++++++++++++++++++++++++++++++++---------
tests/test_osinfo.py | 2 +-
tests/test_rest.py | 56 ++++++++++++++++++++++++++++
tests/test_vmtemplate.py | 6 +--
11 files changed, 182 insertions(+), 40 deletions(-)
--
1.8.4.2
2
12
[PATCH V2 0/2] move RollbackContext from tests/utils to src/kimchi/rollbackcontext
by shaohef@linux.vnet.ibm.com 31 Dec '13
by shaohef@linux.vnet.ibm.com 31 Dec '13
31 Dec '13
From: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>
V1 -> V2:
rebase
ShaoHe Feng (2):
move RollbackContext from tests/utils to src/kimchi/rollbackcontext
touch 4 files when move RollbackContext, fix pep8 on them
Makefile.am | 10 ++--
src/kimchi/Makefile.am | 59 +++++++++++------------
src/kimchi/rollbackcontext.py | 71 +++++++++++++++++++++++++++
tests/test_model.py | 108 +++++++++++++++++++++++++-----------------
tests/test_rest.py | 73 +++++++++++++++-------------
tests/utils.py | 59 ++++-------------------
6 files changed, 224 insertions(+), 156 deletions(-)
create mode 100644 src/kimchi/rollbackcontext.py
--
1.8.4.2
2
5
From: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>
V3 -> V4:
after template supports networks, we should change the test case accordingly
aline refactor controller, rebase on the latest commit.
V2 -> V3:
fix typo.
support creating a vm without network.
V1 -> V2:
update mockmodel and test case
add 'networks' option for template get/create/update
ShaoHe Feng (7):
template supports networks: let template xml support more networks
template supports networks: fix test case
template supports networks: update API
template supports networks: update controller and json schema
template supports networks: update model
template supports networks: update mockmodel
template supports networks: update test case
docs/API.md | 4 ++
src/kimchi/API.json | 12 ++++++
src/kimchi/control/templates.py | 3 +-
src/kimchi/mockmodel.py | 7 +++-
src/kimchi/model.py | 25 +++++++-----
src/kimchi/osinfo.py | 5 ++-
src/kimchi/vmtemplate.py | 20 ++++++++--
tests/test_model.py | 86 ++++++++++++++++++++++++++++++++---------
tests/test_osinfo.py | 2 +-
tests/test_rest.py | 56 +++++++++++++++++++++++++++
tests/test_vmtemplate.py | 6 +--
11 files changed, 185 insertions(+), 41 deletions(-)
--
1.8.4.2
1
7
From: Aline Manera <alinefm(a)br.ibm.com>
V1 -> V2:
- Remove 'instance' parameter from generate_action_handler()
- Update Makefile and spec files
- Remove controller content when it is moved to new files
- Update authors in new files
- Fix identation error while using pep8 1.4
- Other minor fixes
Aline Manera (15):
Move generate_action_handler() function to Resource() class
Move common functions for Resource and Collection to control/utils.py
Move login() and logout() functions from controller.py to root.py
Move basic controller resources to control/base.py
Move all resources related to vms to control/vms.py
Move all resources related to templates to control/templates.py
Move all resources related to debug reports to
control/debugreports.py
Move all resources related to storage pools to
control/storagepools.py
Move all resources related to storage volume to
control/storagevolumes.py
Move all resources related to interfaces to control/interfaces.py
Move all resources related to networks to control/networks.py
Move all resources related to config to control/config.py
Move all resources related to host to control/host.py
Move all resources related to plugins to control/plugins.py
Move all resources related to tasks to control/tasks.py
Makefile.am | 1 +
configure.ac | 1 +
contrib/kimchi.spec.fedora.in | 1 +
contrib/kimchi.spec.suse.in | 1 +
plugins/sample/__init__.py | 2 +-
src/kimchi/Makefile.am | 3 +-
src/kimchi/control/Makefile.am | 28 ++
src/kimchi/control/__init__.py | 21 +
src/kimchi/control/base.py | 292 +++++++++++++
src/kimchi/control/config.py | 69 ++++
src/kimchi/control/debugreports.py | 52 +++
src/kimchi/control/host.py | 63 +++
src/kimchi/control/interfaces.py | 45 ++
src/kimchi/control/networks.py | 48 +++
src/kimchi/control/plugins.py | 45 ++
src/kimchi/control/storagepools.py | 127 ++++++
src/kimchi/control/storagevolumes.py | 81 ++++
src/kimchi/control/tasks.py | 41 ++
src/kimchi/control/templates.py | 52 +++
src/kimchi/control/utils.py | 105 +++++
src/kimchi/control/vms.py | 65 +++
src/kimchi/controller.py | 755 ----------------------------------
src/kimchi/root.py | 62 ++-
tests/test_mockmodel.py | 6 +-
24 files changed, 1191 insertions(+), 775 deletions(-)
create mode 100644 src/kimchi/control/Makefile.am
create mode 100644 src/kimchi/control/__init__.py
create mode 100644 src/kimchi/control/base.py
create mode 100644 src/kimchi/control/config.py
create mode 100644 src/kimchi/control/debugreports.py
create mode 100644 src/kimchi/control/host.py
create mode 100644 src/kimchi/control/interfaces.py
create mode 100644 src/kimchi/control/networks.py
create mode 100644 src/kimchi/control/plugins.py
create mode 100644 src/kimchi/control/storagepools.py
create mode 100644 src/kimchi/control/storagevolumes.py
create mode 100644 src/kimchi/control/tasks.py
create mode 100644 src/kimchi/control/templates.py
create mode 100644 src/kimchi/control/utils.py
create mode 100644 src/kimchi/control/vms.py
delete mode 100644 src/kimchi/controller.py
--
1.7.10.4
3
46
From: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>
V2 -> V3
fix typo.
support creating a vm without network.
V1 -> V2:
update mockmodel and test case
add 'networks' option for template get/create/update
ShaoHe Feng (6):
template supports networks: let template xml support more networks
template supports networks: update API
template supports networks: update controller and json schema
template supports networks: update model
template supports networks: update mockmodel
template supports networks: update test case
docs/API.md | 4 +++
src/kimchi/API.json | 12 +++++++
src/kimchi/controller.py | 3 +-
src/kimchi/mockmodel.py | 7 +++-
src/kimchi/model.py | 6 ++++
src/kimchi/osinfo.py | 5 +--
src/kimchi/vmtemplate.py | 20 ++++++++---
tests/test_model.py | 86 +++++++++++++++++++++++++++++++++++++-----------
tests/test_rest.py | 56 +++++++++++++++++++++++++++++++
9 files changed, 172 insertions(+), 27 deletions(-)
--
1.8.4.2
4
19
31 Dec '13
From: Aline Manera <alinefm(a)br.ibm.com>
Aline Manera (2):
pep8 cleanup for featuretests.py
Simplify domain xml in featuretests.py
Makefile.am | 1 +
src/kimchi/featuretests.py | 24 +++++-------------------
2 files changed, 6 insertions(+), 19 deletions(-)
--
1.7.10.4
3
7
[PATCH 1/2] move RollbackContext from tests/utils to src/kimchi/rollbackcontext
by shaohef@linux.vnet.ibm.com 31 Dec '13
by shaohef@linux.vnet.ibm.com 31 Dec '13
31 Dec '13
From: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>
Then kimchi source code can make use of it
Also add src/kimchi/rollbackcontext.py in dist list
Signed-off-by: Zhou Zheng Sheng <zhshzhou(a)linux.vnet.ibm.com>
Signed-off-by: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>
---
src/kimchi/Makefile.am | 59 +++++++++++++++++------------------
src/kimchi/rollbackcontext.py | 71 +++++++++++++++++++++++++++++++++++++++++++
tests/test_model.py | 25 +++++++--------
tests/test_rest.py | 3 +-
tests/utils.py | 45 ---------------------------
5 files changed, 116 insertions(+), 87 deletions(-)
create mode 100644 src/kimchi/rollbackcontext.py
diff --git a/src/kimchi/Makefile.am b/src/kimchi/Makefile.am
index 47a3bd2..2f05ab7 100644
--- a/src/kimchi/Makefile.am
+++ b/src/kimchi/Makefile.am
@@ -21,35 +21,36 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
kimchi_PYTHON = \
- asynctask.py \
- auth.py \
- controller.py \
- disks.py \
- distroloader.py \
- exception.py \
- __init__.py \
- isoinfo.py \
- netinfo.py \
- network.py \
- networkxml.py \
- mockmodel.py \
- model.py \
- objectstore.py \
- osinfo.py \
- root.py \
- scan.py \
- screenshot.py \
- server.py \
- sslcert.py \
- template.py \
- vmtemplate.py \
- vnc.py \
- websocket.py \
- websockify.py \
- xmlutils.py \
- utils.py \
- cachebust.py \
- featuretests.py
+ __init__.py \
+ asynctask.py \
+ auth.py \
+ cachebust.py \
+ controller.py \
+ disks.py \
+ distroloader.py \
+ exception.py \
+ featuretests.py \
+ isoinfo.py \
+ mockmodel.py \
+ model.py \
+ netinfo.py \
+ network.py \
+ networkxml.py \
+ objectstore.py \
+ osinfo.py \
+ rollbackcontext.py \
+ root.py \
+ scan.py \
+ screenshot.py \
+ server.py \
+ sslcert.py \
+ template.py \
+ utils.py \
+ vmtemplate.py \
+ vnc.py \
+ websocket.py \
+ websockify.py \
+ xmlutils.py
nodist_kimchi_PYTHON = config.py
diff --git a/src/kimchi/rollbackcontext.py b/src/kimchi/rollbackcontext.py
new file mode 100644
index 0000000..2afd114
--- /dev/null
+++ b/src/kimchi/rollbackcontext.py
@@ -0,0 +1,71 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Adam Litke <agl(a)linux.vnet.ibm.com>
+# ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+import sys
+
+
+class RollbackContext(object):
+ '''
+ A context manager for recording and playing rollback.
+ The first exception will be remembered and re-raised after rollback
+
+ Sample usage:
+ with RollbackContext() as rollback:
+ step1()
+ rollback.prependDefer(lambda: undo step1)
+ def undoStep2(arg): pass
+ step2()
+ rollback.prependDefer(undoStep2, arg)
+ '''
+ def __init__(self, *args):
+ self._finally = []
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ firstException = exc_value
+
+ for undo, args, kwargs in self._finally:
+ try:
+ undo(*args, **kwargs)
+ except Exception as e:
+ # keep the earliest exception info
+ if not firstException:
+ firstException = e
+ # keep the original traceback info
+ traceback = sys.exc_info()[2]
+
+ # re-raise the earliest exception
+ if firstException is not None:
+ if type(firstException) is str:
+ sys.stderr.write(firstException)
+ else:
+ raise firstException, None, traceback
+
+ def defer(self, func, *args, **kwargs):
+ self._finally.append((func, args, kwargs))
+
+ def prependDefer(self, func, *args, **kwargs):
+ self._finally.insert(0, (func, args, kwargs))
diff --git a/tests/test_model.py b/tests/test_model.py
index e19364f..7ecd8c6 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -38,6 +38,7 @@ import utils
from kimchi import netinfo
from kimchi.exception import InvalidOperation, InvalidParameter
from kimchi.exception import NotFoundError, OperationFailed
+from kimchi.rollbackcontext import RollbackContext
class ModelTests(unittest.TestCase):
@@ -72,7 +73,7 @@ class ModelTests(unittest.TestCase):
def test_vm_lifecycle(self):
inst = kimchi.model.Model(objstore_loc=self.tmp_store)
- with utils.RollbackContext() as rollback:
+ with RollbackContext() as rollback:
params = {'name': 'test', 'disks': []}
inst.templates_create(params)
rollback.prependDefer(inst.template_delete, 'test')
@@ -97,7 +98,7 @@ class ModelTests(unittest.TestCase):
def test_vm_storage_provisioning(self):
inst = kimchi.model.Model(objstore_loc=self.tmp_store)
- with utils.RollbackContext() as rollback:
+ with RollbackContext() as rollback:
params = {'name': 'test', 'disks': [{'size': 1}]}
inst.templates_create(params)
rollback.prependDefer(inst.template_delete, 'test')
@@ -115,7 +116,7 @@ class ModelTests(unittest.TestCase):
def test_storagepool(self):
inst = kimchi.model.Model('qemu:///system', self.tmp_store)
- with utils.RollbackContext() as rollback:
+ with RollbackContext() as rollback:
path = '/tmp/kimchi-images'
name = 'test-pool'
if not os.path.exists(path):
@@ -168,7 +169,7 @@ class ModelTests(unittest.TestCase):
def test_storagevolume(self):
inst = kimchi.model.Model('qemu:///system', self.tmp_store)
- with utils.RollbackContext() as rollback:
+ with RollbackContext() as rollback:
path = '/tmp/kimchi-images'
pool = 'test-pool'
vol = 'test-volume.img'
@@ -223,7 +224,7 @@ class ModelTests(unittest.TestCase):
def test_template_storage_customise(self):
inst = kimchi.model.Model(objstore_loc=self.tmp_store)
- with utils.RollbackContext() as rollback:
+ with RollbackContext() as rollback:
path = '/tmp/kimchi-images'
pool = 'test-pool'
if not os.path.exists(path):
@@ -295,7 +296,7 @@ class ModelTests(unittest.TestCase):
orig_params = {'name': 'test', 'memory': '1024', 'cpus': '1'}
inst.templates_create(orig_params)
- with utils.RollbackContext() as rollback:
+ with RollbackContext() as rollback:
params_1 = {'name': 'kimchi-vm1', 'template': '/templates/test'}
params_2 = {'name': 'kimchi-vm2', 'template': '/templates/test'}
inst.vms_create(params_1)
@@ -326,7 +327,7 @@ class ModelTests(unittest.TestCase):
def test_network(self):
inst = kimchi.model.Model('qemu:///system', self.tmp_store)
- with utils.RollbackContext() as rollback:
+ with RollbackContext() as rollback:
name = 'test-network'
networks = inst.networks_get_list()
@@ -480,7 +481,7 @@ class ModelTests(unittest.TestCase):
def test_delete_running_vm(self):
inst = kimchi.model.Model(objstore_loc=self.tmp_store)
- with utils.RollbackContext() as rollback:
+ with RollbackContext() as rollback:
params = {'name': u'test', 'disks': []}
inst.templates_create(params)
rollback.prependDefer(inst.template_delete, 'test')
@@ -501,7 +502,7 @@ class ModelTests(unittest.TestCase):
def test_vm_list_sorted(self):
inst = kimchi.model.Model(objstore_loc=self.tmp_store)
- with utils.RollbackContext() as rollback:
+ with RollbackContext() as rollback:
params = {'name': 'test', 'disks': []}
inst.templates_create(params)
rollback.prependDefer(inst.template_delete, 'test')
@@ -517,7 +518,7 @@ class ModelTests(unittest.TestCase):
def test_use_test_host(self):
inst = kimchi.model.Model('test:///default', objstore_loc=self.tmp_store)
- with utils.RollbackContext() as rollback:
+ with RollbackContext() as rollback:
params = {'name': 'test', 'disks': [],
'storagepool': '/storagepools/default-pool',
'domain': 'test',
@@ -546,7 +547,7 @@ class ModelTests(unittest.TestCase):
inst.debugreport_delete(namePrefix + '*')
except NotFoundError:
pass
- with utils.RollbackContext() as rollback:
+ with RollbackContext() as rollback:
report_list = inst.debugreports_get_list()
self.assertFalse(reportName in report_list)
try:
@@ -617,7 +618,7 @@ class ModelTests(unittest.TestCase):
@unittest.skipUnless(utils.running_as_root(), 'Must be run as root')
def test_deep_scan(self):
inst = kimchi.model.Model('qemu:///system', objstore_loc=self.tmp_store)
- with utils.RollbackContext() as rollback:
+ with RollbackContext() as rollback:
path = '/tmp/kimchi-images/tmpdir'
if not os.path.exists(path):
os.makedirs(path)
diff --git a/tests/test_rest.py b/tests/test_rest.py
index 73946c0..1b7312f 100644
--- a/tests/test_rest.py
+++ b/tests/test_rest.py
@@ -33,8 +33,9 @@ from functools import partial
import kimchi.mockmodel
import kimchi.server
from kimchi.asynctask import AsyncTask
+from kimchi.rollbackcontext import RollbackContext
from utils import fake_user, get_free_port, https_request, patch_auth, request
-from utils import RollbackContext, run_server
+from utils import run_server
test_server = None
diff --git a/tests/utils.py b/tests/utils.py
index a7596e8..960e0be 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -140,51 +140,6 @@ def https_request(host, port, path, data=None, method='GET', headers=None):
return _request(conn, path, data, method, headers)
-class RollbackContext(object):
- '''
- A context manager for recording and playing rollback.
- The first exception will be remembered and re-raised after rollback
-
- Sample usage:
- with RollbackContext() as rollback:
- step1()
- rollback.prependDefer(lambda: undo step1)
- def undoStep2(arg): pass
- step2()
- rollback.prependDefer(undoStep2, arg)
- '''
- def __init__(self, *args):
- self._finally = []
-
- def __enter__(self):
- return self
-
- def __exit__(self, exc_type, exc_value, traceback):
- firstException = exc_value
-
- for undo, args, kwargs in self._finally:
- try:
- undo(*args, **kwargs)
- except Exception as e:
- # keep the earliest exception info
- if not firstException:
- firstException = e
- # keep the original traceback info
- traceback = sys.exc_info()[2]
-
- # re-raise the earliest exception
- if firstException is not None:
- if type(firstException) is str:
- sys.stderr.write(firstException)
- else:
- raise firstException, None, traceback
-
- def defer(self, func, *args, **kwargs):
- self._finally.append((func, args, kwargs))
-
- def prependDefer(self, func, *args, **kwargs):
- self._finally.insert(0, (func, args, kwargs))
-
def patch_auth():
"""
Override the authenticate function with a simple test against an
--
1.8.4.2
2
4
From: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>
V1 -> V2
remove the unuseful comment
Commit e467b32 brings a whitespace, fix it.
Also fix pep8.
ShaoHe Feng (1):
fix whitespace in test_mockmodel
Makefile.am | 1 +
tests/test_mockmodel.py | 25 +++++++++++++++----------
2 files changed, 16 insertions(+), 10 deletions(-)
--
1.8.4.2
2
3
This patch series is about creating iSCSI storage pool.
After this patch, you can create an ISCSI storage pool as the following.
Thanks the reviewers. I took most of your advices in v3 series.
curl -u root -H 'Content-type: application/json' \
-H 'Accept: application/json' \
-d '{"source": {
"target": "iqn.2013-01.example.com.targetname",
"host": "192.168.X.X",
"port": 326X,
"auth": {"username": "testUser", "password": "123456"}},
"type": "iscsi",
"name": "testIscsiPool"}' \
http://127.0.0.1:8000/storagepools
Note the "port" and "auth" is optional.
Zhou Zheng Sheng (5):
storagepool: refactor _get_pool_xml()
This patch is to refactor current storage pool XML generation code
to be more extensible.
v2 -> v3: Extract a StoragePoolDef class and define "prepare()" and
"create()" method.
storagepool: rename and consolidate arguments of creating (back end)
This patch is to consolidate argument serving the same purpose
under the same name.
v2 -> v3: Fix typo.
storagepool: rename and consolidate arguments of creating (front end)
This patch is the respective front-end changes for the above patch.
v2 -> v3: No change.
storagepool: Support Creating iSCSI storagepool in model.py
This patch is to validate iSCSI host and auth, and create libvirt
iSCSI storage pool.
v2 -> v3: Support iSCSI CHAP auth. Add more test cases.
test_model: test creating iSCSI storage pool
v2 -> v3: Do not create directory for storage pool, pool.build()
create it automatically.
Makefile.am | 2 +
docs/API.md | 28 ++--
src/kimchi/Makefile.am | 1 +
src/kimchi/iscsi.py | 89 ++++++++++++
src/kimchi/model.py | 223 +++++++++++++++++++++++++------
tests/Makefile.am | 1 +
tests/test_model.py | 89 ++++++------
tests/test_storagepool.py | 156 +++++++++++++++++++++
ui/js/src/kimchi.storagepool_add_main.js | 13 +-
9 files changed, 513 insertions(+), 89 deletions(-)
create mode 100644 src/kimchi/iscsi.py
create mode 100644 tests/test_storagepool.py
--
1.7.11.7
3
13
From: Aline Manera <alinefm(a)br.ibm.com>
This patch cleans up pep8 style issue in root.py
Also move error_production_handler and error_development_handler functions
to Root() class as they are used only in it.
Signed-off-by: Aline Manera <alinefm(a)br.ibm.com>
---
Makefile.am | 1 +
src/kimchi/root.py | 49 +++++++++++++++++++++++++++----------------------
2 files changed, 28 insertions(+), 22 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index e57d3b6..622d4f0 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -41,6 +41,7 @@ PEP8_WHITELIST = \
src/kimchi/asynctask.py \
src/kimchi/config.py.in \
src/kimchi/disks.py \
+ src/kimchi/root.py \
src/kimchi/server.py \
plugins/__init__.py \
plugins/sample/__init__.py \
diff --git a/src/kimchi/root.py b/src/kimchi/root.py
index c43897c..d1fe818 100644
--- a/src/kimchi/root.py
+++ b/src/kimchi/root.py
@@ -19,7 +19,7 @@
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import cherrypy
import json
@@ -30,30 +30,19 @@ from kimchi import template
from kimchi.config import get_api_schema_file
-def error_production_handler(status, message, traceback, version):
- data = {'code': status, 'reason': message}
- res = template.render('error.html', data)
- if type(res) is unicode:
- res = res.encode("utf-8")
- return res
-
-def error_development_handler(status, message, traceback, version):
- data = {'code': status, 'reason': message, 'call_stack': cherrypy._cperror.format_exc()}
- res = template.render('error.html', data)
- if type(res) is unicode:
- res = res.encode("utf-8")
- return res
-
-
class Root(controller.Resource):
- _handled_error = ['error_page.400',
- 'error_page.404', 'error_page.405',
- 'error_page.406', 'error_page.415', 'error_page.500']
def __init__(self, model, dev_env):
+ self._handled_error = ['error_page.400', 'error_page.404',
+ 'error_page.405', 'error_page.406',
+ 'error_page.415', 'error_page.500']
+
if not dev_env:
- self._cp_config = dict([(key, error_production_handler) for key in self._handled_error])
+ self._cp_config = dict([(key, self.error_production_handler)
+ for key in self._handled_error])
else:
- self._cp_config = dict([(key, error_development_handler) for key in self._handled_error])
+ self._cp_config = dict([(key, self.error_development_handler)
+ for key in self._handled_error])
+
controller.Resource.__init__(self, model)
self.vms = controller.VMs(model)
self.templates = controller.Templates(model)
@@ -69,6 +58,21 @@ class Root(controller.Resource):
self.plugins = controller.Plugins(model)
self.api_schema = json.load(open(get_api_schema_file()))
+ def error_production_handler(status, message, traceback, version):
+ data = {'code': status, 'reason': message}
+ res = template.render('error.html', data)
+ if type(res) is unicode:
+ res = res.encode("utf-8")
+ return res
+
+ def error_development_handler(status, message, traceback, version):
+ data = {'code': status, 'reason': message,
+ 'call_stack': cherrypy._cperror.format_exc()}
+ res = template.render('error.html', data)
+ if type(res) is unicode:
+ res = res.encode("utf-8")
+ return res
+
def get(self):
return self.default('kimchi-ui.html')
@@ -77,8 +81,9 @@ class Root(controller.Resource):
if page.endswith('.html'):
return template.render(page, None)
raise cherrypy.HTTPError(404)
+
@cherrypy.expose
def tabs(self, page, **kwargs):
if page.endswith('.html'):
- return template.render('tabs/'+ page, None)
+ return template.render('tabs/' + page, None)
raise cherrypy.HTTPError(404)
--
1.7.10.4
4
5
From: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>
V1 -> V2:
update mockmodel and test case
add 'networks' option for template get/create/update
ShaoHe Feng (6):
template supports networks: let template xml support more networks
template supports networks: update API
template supports networks: update controller and json schema
template supports networks: update model
template supports networks: update mockmodel
template supports networks: update test case
docs/API.md | 4 +++
src/kimchi/API.json | 14 ++++++++
src/kimchi/controller.py | 3 +-
src/kimchi/mockmodel.py | 7 +++-
src/kimchi/model.py | 6 ++++
src/kimchi/osinfo.py | 5 +--
src/kimchi/vmtemplate.py | 20 ++++++++---
tests/test_model.py | 87 +++++++++++++++++++++++++++++++++++++-----------
tests/test_rest.py | 68 +++++++++++++++++++++++++++++++++++++
9 files changed, 187 insertions(+), 27 deletions(-)
--
1.8.4.2
3
13
Follow this rule:
1) Import common modules
import ...
import ...
from ... import ...
from ... import ...
2) Import kimchi modules
import kimchi.<mod>
import kimchi.<mod>
from kimchi import ...
from kimchi import ...
Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo(a)linux.vnet.ibm.com>
---
plugins/sample/__init__.py | 8 ++++++--
plugins/sample/model.py | 2 +-
src/kimchi/model.py | 4 ++--
src/kimchi/server.py | 2 +-
src/kimchi/sslcert.py | 2 +-
tests/test_exception.py | 8 +++++---
tests/test_mockmodel.py | 6 ++++--
tests/test_model.py | 19 +++++++++++--------
tests/test_networkxml.py | 4 +++-
tests/test_osinfo.py | 5 ++++-
tests/test_plugin.py | 6 +++++-
tests/test_rest.py | 12 +++++++++---
tests/test_server.py | 4 ++--
tests/test_vmtemplate.py | 4 +++-
tests/utils.py | 15 +++++++++------
15 files changed, 66 insertions(+), 35 deletions(-)
diff --git a/plugins/sample/__init__.py b/plugins/sample/__init__.py
index a20f5e6..7064904 100644
--- a/plugins/sample/__init__.py
+++ b/plugins/sample/__init__.py
@@ -22,12 +22,16 @@
import json
import os
+
+
from cherrypy import expose
-from kimchi.controller import Resource, Collection
+
+
+from kimchi.controller import Collection, Resource
from model import Model
-model = Model()
+model = Model()
class Drawings(Resource):
def __init__(self):
diff --git a/plugins/sample/model.py b/plugins/sample/model.py
index f6da5d0..9a2f22f 100644
--- a/plugins/sample/model.py
+++ b/plugins/sample/model.py
@@ -20,7 +20,7 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-from kimchi.exception import NotFoundError, InvalidOperation
+from kimchi.exception import InvalidOperation, NotFoundError
class Model(object):
diff --git a/src/kimchi/model.py b/src/kimchi/model.py
index 3bc5d6d..d5d0dd8 100644
--- a/src/kimchi/model.py
+++ b/src/kimchi/model.py
@@ -42,9 +42,9 @@ import time
import uuid
-from collections import defaultdict
from cherrypy.process.plugins import BackgroundTask
from cherrypy.process.plugins import SimplePlugin
+from collections import defaultdict
from xml.etree import ElementTree
@@ -69,7 +69,7 @@ from kimchi.networkxml import to_network_xml
from kimchi.objectstore import ObjectStore
from kimchi.scan import Scanner
from kimchi.screenshot import VMScreenshot
-from kimchi.utils import kimchi_log, is_digit, get_enabled_plugins
+from kimchi.utils import get_enabled_plugins, is_digit, kimchi_log
from kimchi.vmtemplate import VMTemplate
diff --git a/src/kimchi/server.py b/src/kimchi/server.py
index 6ff6fa0..114a3a0 100644
--- a/src/kimchi/server.py
+++ b/src/kimchi/server.py
@@ -33,7 +33,7 @@ from kimchi import config
from kimchi import model
from kimchi import mockmodel
from kimchi.root import Root
-from kimchi.utils import import_class, get_enabled_plugins
+from kimchi.utils import get_enabled_plugins, import_class
LOGGING_LEVEL = {"debug": logging.DEBUG,
diff --git a/src/kimchi/sslcert.py b/src/kimchi/sslcert.py
index 70441f2..529699d 100644
--- a/src/kimchi/sslcert.py
+++ b/src/kimchi/sslcert.py
@@ -28,7 +28,7 @@
import time
-from M2Crypto import X509, EVP, RSA, ASN1
+from M2Crypto import ASN1, EVP, RSA, X509
class SSLCert(object):
diff --git a/tests/test_exception.py b/tests/test_exception.py
index 9b5355a..df1f507 100644
--- a/tests/test_exception.py
+++ b/tests/test_exception.py
@@ -20,13 +20,15 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-import unittest
-import os
import json
+import os
+import unittest
+
import kimchi.mockmodel
import kimchi.server
-from utils import *
+from utils import get_free_port, patch_auth, request, run_server
+
test_server = None
model = None
diff --git a/tests/test_mockmodel.py b/tests/test_mockmodel.py
index b819172..5a3c73e 100644
--- a/tests/test_mockmodel.py
+++ b/tests/test_mockmodel.py
@@ -20,15 +20,17 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-import unittest
import cherrypy
import json
import os
+import time
+import unittest
+
import kimchi.mockmodel
import kimchi.controller
+from utils import get_free_port, patch_auth, request, run_server
-from utils import *
#utils.silence_server()
test_server = None
diff --git a/tests/test_model.py b/tests/test_model.py
index fb7d6dd..e19364f 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -21,21 +21,24 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-import unittest
-import threading
import os
-import time
-import tempfile
-import psutil
import platform
+import psutil
+import tempfile
+import threading
+import time
+import unittest
import uuid
+
+import iso_gen
import kimchi.model
import kimchi.objectstore
-from kimchi.exception import *
-from kimchi import netinfo
import utils
-import iso_gen
+from kimchi import netinfo
+from kimchi.exception import InvalidOperation, InvalidParameter
+from kimchi.exception import NotFoundError, OperationFailed
+
class ModelTests(unittest.TestCase):
def setUp(self):
diff --git a/tests/test_networkxml.py b/tests/test_networkxml.py
index 4eeeaa2..3073bce 100644
--- a/tests/test_networkxml.py
+++ b/tests/test_networkxml.py
@@ -20,10 +20,12 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+import ipaddr
import unittest
+
+
import kimchi.networkxml as nxml
from kimchi.xmlutils import xpath_get_text
-import ipaddr
class NetworkXmlTests(unittest.TestCase):
diff --git a/tests/test_osinfo.py b/tests/test_osinfo.py
index f92567d..fda8ada 100644
--- a/tests/test_osinfo.py
+++ b/tests/test_osinfo.py
@@ -21,7 +21,10 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import unittest
-from kimchi.osinfo import *
+
+
+from kimchi.osinfo import lookup
+
class OSInfoTests(unittest.TestCase):
def test_default_lookup(self):
diff --git a/tests/test_plugin.py b/tests/test_plugin.py
index 20cc598..42c87a9 100644
--- a/tests/test_plugin.py
+++ b/tests/test_plugin.py
@@ -20,17 +20,21 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-import unittest
import json
import os
import sys
+import unittest
+
+
from functools import partial
+
import kimchi.mockmodel
import kimchi.server
import utils
from kimchi import config
+
test_server = None
model = None
host = None
diff --git a/tests/test_rest.py b/tests/test_rest.py
index f597796..73946c0 100644
--- a/tests/test_rest.py
+++ b/tests/test_rest.py
@@ -20,16 +20,22 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-import unittest
+import base64
import json
-import time
import os
+import time
+import unittest
+
+
from functools import partial
+
import kimchi.mockmodel
import kimchi.server
-from utils import *
from kimchi.asynctask import AsyncTask
+from utils import fake_user, get_free_port, https_request, patch_auth, request
+from utils import RollbackContext, run_server
+
test_server = None
model = None
diff --git a/tests/test_server.py b/tests/test_server.py
index 9bb0034..734a618 100644
--- a/tests/test_server.py
+++ b/tests/test_server.py
@@ -20,12 +20,12 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-import unittest
import json
import os
+import unittest
-import utils
+import utils
import kimchi.mockmodel
#utils.silence_server()
diff --git a/tests/test_vmtemplate.py b/tests/test_vmtemplate.py
index 81382c7..7f032e7 100644
--- a/tests/test_vmtemplate.py
+++ b/tests/test_vmtemplate.py
@@ -23,9 +23,11 @@
import unittest
import uuid
-from kimchi.vmtemplate import *
+
+from kimchi.vmtemplate import VMTemplate
from kimchi.xmlutils import xpath_get_text
+
class VMTemplateTests(unittest.TestCase):
def test_minimal_construct(self):
fields = (('name', 'test'), ('os_distro', 'unknown'),
diff --git a/tests/utils.py b/tests/utils.py
index c114813..a7596e8 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -21,16 +21,19 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
-import httplib
+import base64
import cherrypy
-import threading
-import time
+import httplib
import os
-import sys
import socket
-from contextlib import closing
+import sys
+import threading
+import time
import unittest
-import base64
+
+
+from contextlib import closing
+
import kimchi.server
import kimchi.model
--
1.8.1.4
4
6
[PATCH v4 1/2] logical pool fixes: only list leaf devices, and read file instead of run "cat"
by Zhou Zheng Sheng 30 Dec '13
by Zhou Zheng Sheng 30 Dec '13
30 Dec '13
Some devices are parent of other devices. We should just list the leaf
devices but not parent devices. For example, if a disk contains
partitions, we should only list the partitions. If a disk is held by
multipath device, we should not list the disk.
Currently we strip the last charactter from "vda1" to get "vda" and
ignore the parent "vda" device. This may fails on disk contains lots of
logical partitions, for example "vda10"'s parent is not "vda1". Another
problem is this method can not find the parent-child relation ship
between a multipath device and its slaves.
The most accurate information is on sysfs, and lsblk lists all children
device of the requested device based on sysfs. So when "lsblk -P
devices" prints only one line, it's the device itself, when it prints
more lines, they are the children devices. This patch use this method to
detect if a device a leaf one.
This patch also avoids start new "cat" process to read uevent file, it
opens the uevent file and read it directly.
v2:
Assign string literal to constants thus avoid breaking the line.
v3:
Save N lsblk invocations when list all devices by collecting major:min
and device node path in one run. N is number of block devices.
Use short-circuiting operator "and" instead of all to save extra lsblk
invocation.
v4:
Remove a redundant "else" as suggested by Aline.
Signed-off-by: Zhou Zheng Sheng <zhshzhou(a)linux.vnet.ibm.com>
---
src/kimchi/disks.py | 83 ++++++++++++++++++++++++++++++++---------------------
1 file changed, 50 insertions(+), 33 deletions(-)
diff --git a/src/kimchi/disks.py b/src/kimchi/disks.py
index a054961..a7e5616 100644
--- a/src/kimchi/disks.py
+++ b/src/kimchi/disks.py
@@ -27,27 +27,13 @@ from kimchi.exception import OperationFailed
from kimchi.utils import kimchi_log
-def _get_partition_path(name):
- """ Returns device path given a partition name """
- dev_path = None
- maj_min = None
-
- keys = ["NAME", "MAJ:MIN"]
- dev_list = _get_lsblk_devs(keys)
-
- for dev in dev_list:
- if dev['name'] == name:
- maj_min = dev['maj:min']
- break
+def _get_dev_node_path(maj_min):
+ """ Returns device node path given the device number 'major:min' """
+ uevent = "/sys/dev/block/%s/uevent" % maj_min
+ with open(uevent) as ueventf:
+ content = ueventf.read()
- uevent_cmd = "cat /sys/dev/block/%s/uevent" % maj_min
- uevent = subprocess.Popen(uevent_cmd, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE, shell=True)
- out, err = uevent.communicate()
- if uevent.returncode != 0:
- raise OperationFailed("Error getting partition path for %s", name)
-
- data = dict(re.findall(r'(\S+)=(".*?"|\S+)', out.replace("\n", " ")))
+ data = dict(re.findall(r'(\S+)=(".*?"|\S+)', content.replace("\n", " ")))
return "/dev/%s" % data["DEVNAME"]
@@ -63,6 +49,39 @@ def _get_lsblk_devs(keys, devs=[]):
return _parse_lsblk_output(out, keys)
+def _get_dev_major_min(name):
+ maj_min = None
+
+ keys = ["NAME", "MAJ:MIN"]
+ dev_list = _get_lsblk_devs(keys)
+
+ for dev in dev_list:
+ if dev['name'] == name:
+ maj_min = dev['maj:min']
+ break
+ else:
+ msg = "Failed to find major and minor number for %s" % name
+ raise OperationFailed(msg)
+
+ return maj_min
+
+
+def _is_dev_leaf(devNodePath):
+ try:
+ # By default, lsblk prints a device information followed by children
+ # device information
+ childrenCount = len(
+ _get_lsblk_devs(["NAME"], [devNodePath])) - 1
+ except OperationFailed as e:
+ # lsblk is known to fail on multipath devices
+ # Assume these devices contain children
+ kimchi_log.error(
+ "Error getting device info for %s: %s", devNodePath, e)
+ return False
+
+ return childrenCount == 0
+
+
def _parse_lsblk_output(output, keys):
# output is on format key="value",
# where key can be NAME, TYPE, FSTYPE, SIZE, MOUNTPOINT, etc
@@ -82,32 +101,30 @@ def _parse_lsblk_output(output, keys):
def get_partitions_names():
names = []
- ignore_names = []
- keys = ["NAME", "TYPE", "FSTYPE", "MOUNTPOINT"]
+ keys = ["NAME", "TYPE", "FSTYPE", "MOUNTPOINT", "MAJ:MIN"]
# output is on format key="value",
# where key can be NAME, TYPE, FSTYPE, MOUNTPOINT
for dev in _get_lsblk_devs(keys):
# split()[0] to avoid the second part of the name, after the
# whiteline
name = dev['name'].split()[0]
- # Only list unmounted and unformated partition or disk.
- if not all([dev['type'] in ['part', 'disk'],
- dev['fstype'] == "",
- dev['mountpoint'] == ""]):
-
- # the whole disk must be ignored in it has at least one
- # mounted/formatted partition
- if dev['type'] == 'part':
- ignore_names.append(name[:-1])
+ devNodePath = _get_dev_node_path(dev['maj:min'])
+ # Only list unmounted and unformated and leaf and (partition or disk)
+ # leaf means a partition, a disk has no partition, or a disk not held
+ # by any multipath device.
+ if not (dev['type'] in ['part', 'disk'] and
+ dev['fstype'] == "" and
+ dev['mountpoint'] == "" and
+ _is_dev_leaf(devNodePath)):
continue
names.append(name)
- return list(set(names) - set(ignore_names))
+ return names
def get_partition_details(name):
- dev_path = _get_partition_path(name)
+ dev_path = _get_dev_node_path(_get_dev_major_min(name))
keys = ["TYPE", "FSTYPE", "SIZE", "MOUNTPOINT"]
try:
--
1.7.11.7
2
5
30 Dec '13
From: Eli Qiao <taget(a)linux.vnet.ibm.com>
V4 - V3 changes:
1 Fix typo in firewalld.xml (Rodrigo)
V3 - V2 changes:
1.Rename kimchid.xml to firewalld.xml (Mark)
2.Remove firewalld from serivce require (Mark)
3.Fix typo
V2 - V1 changes:
1.Add firewalld sevice configure file kimchid.xml to help open iptables port (Mark)
2.Add Ubuntu iptables rule (Royce)
Signed-off-by: Eli Qiao <taget(a)linux.vnet.ibm.com>
---
contrib/DEBIAN/control.in | 3 ++-
contrib/DEBIAN/postinst | 2 ++
contrib/DEBIAN/postrm | 2 ++
contrib/kimchi.spec.fedora.in | 19 +++++++++++++++++++
contrib/kimchi.spec.suse.in | 10 ++++++++--
src/Makefile.am | 1 +
src/firewalld.xml | 7 +++++++
7 files changed, 41 insertions(+), 3 deletions(-)
create mode 100644 src/firewalld.xml
diff --git a/contrib/DEBIAN/control.in b/contrib/DEBIAN/control.in
index 380584c..c0ea1f1 100644
--- a/contrib/DEBIAN/control.in
+++ b/contrib/DEBIAN/control.in
@@ -17,7 +17,8 @@ Depends: python-cherrypy3 (>= 3.2.0),
python-psutil (>= 0.6.0),
python-ethtool,
sosreport,
- python-ipaddr
+ python-ipaddr,
+ firewalld
Build-Depends:
Maintainer: Aline Manera <alinefm(a)br.ibm.com>
Description: Kimchi web server
diff --git a/contrib/DEBIAN/postinst b/contrib/DEBIAN/postinst
index c1fc22e..b27205c 100755
--- a/contrib/DEBIAN/postinst
+++ b/contrib/DEBIAN/postinst
@@ -19,3 +19,5 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
service kimchid start
+/usr/bin/firewall-cmd --reload
+/usr/bin/firewall-cmd --add-service kimchid
diff --git a/contrib/DEBIAN/postrm b/contrib/DEBIAN/postrm
index ef90b49..3c70584 100755
--- a/contrib/DEBIAN/postrm
+++ b/contrib/DEBIAN/postrm
@@ -26,3 +26,5 @@ case "$1" in
rm -rf /var/log/kimchi /var/run/kimchi.pid /usr/share/kimchi/
;;
esac
+
+/usr/bin/firewall-cmd --remove-service kimchid
diff --git a/contrib/kimchi.spec.fedora.in b/contrib/kimchi.spec.fedora.in
index 14ec359..57baead 100644
--- a/contrib/kimchi.spec.fedora.in
+++ b/contrib/kimchi.spec.fedora.in
@@ -34,6 +34,7 @@ BuildRequires: python-unittest2
%if 0%{?with_systemd}
Requires: systemd
+Requires: firewalld
Requires(post): systemd
Requires(preun): systemd
Requires(postun): systemd
@@ -63,6 +64,7 @@ make DESTDIR=%{buildroot} install
%if 0%{?with_systemd}
# Install the systemd scripts
install -Dm 0644 contrib/kimchid.service.fedora %{buildroot}%{_unitdir}/kimchid.service
+install -Dm 0640 src/firewalld.xml %{buildroot}%{_prefix}/lib/firewalld/services/kimchid.xml
%endif
%if 0%{?rhel} == 6
@@ -83,16 +85,32 @@ fi
%if 0%{?rhel} == 6
start kimchid
+# Add defult iptable rules to open 8000 and 8001 port
+iptables -I INPUT -p tcp --dport 8000 -j ACCEPT
+iptables -I INPUT -p tcp --dport 8001 -j ACCEPT
+service iptables save
%else
service kimchid start
+# Add firewalld rules to open 8000 and 8001 port
+/usr/bin/firewall-cmd --reload
+/usr/bin/firewall-cmd --add-service kimchid
%endif
%preun
+%if 0%{?rhel} == 6
+iptables -D INPUT -p tcp --dport 8000 -j ACCEPT
+iptables -D INPUT -p tcp --dport 8001 -j ACCEPT
+service iptables save
+%else
+/usr/bin/firewall-cmd --remove-service kimchid
+%endif
+
if [ $1 -eq 0 ] ; then
# Package removal, not upgrade
/bin/systemctl --no-reload disable kimchid.service > /dev/null 2>&1 || :
/bin/systemctl stop kimchid.service > /dev/null 2>&1 || :
fi
+
exit 0
@@ -153,6 +171,7 @@ rm -rf $RPM_BUILD_ROOT
%if 0%{?with_systemd}
%{_unitdir}/kimchid.service
+%{_prefix}/lib/firewalld/services/kimchid.xml
%endif
%if 0%{?rhel} == 6
/etc/init/kimchid.conf
diff --git a/contrib/kimchi.spec.suse.in b/contrib/kimchi.spec.suse.in
index 9051284..dde9dae 100644
--- a/contrib/kimchi.spec.suse.in
+++ b/contrib/kimchi.spec.suse.in
@@ -46,10 +46,16 @@ install -Dm 0755 contrib/kimchid.sysvinit %{buildroot}%{_initrddir}/kimchid
%post
service kimchid start
chkconfig kimchid on
-
+# Add iptables rules to open 8000 and 8001 port
+iptables -I INPUT -p tcp --dport 8000 -j ACCEPT
+iptables -I INPUT -p tcp --dport 8001 -j ACCEPT
+service iptables save
%preun
service kimchid stop
-
+# Remove iptables rules to open 8000 and 8001 port
+iptables -D INPUT -p tcp --dport 8000 -j ACCEPT
+iptables -D INPUT -p tcp --dport 8001 -j ACCEPT
+service iptables save
%clean
rm -rf $RPM_BUILD_ROOT
diff --git a/src/Makefile.am b/src/Makefile.am
index 7d29e28..7514870 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -24,6 +24,7 @@ SUBDIRS = kimchi distros.d
EXTRA_DIST = kimchid.in \
kimchi.conf.in \
+ firewalld.xml \
$(NULL)
bin_SCRIPTS = kimchid
diff --git a/src/firewalld.xml b/src/firewalld.xml
new file mode 100644
index 0000000..7472e20
--- /dev/null
+++ b/src/firewalld.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<service>
+ <short>kimchid</short>
+ <description>Kimchid is a daemon service for kimchi which is a HTML5 based management tool for KVM. It is designed to make it as easy as possible to get started with KVM and create your first guest.</description>
+ <port protocol="tcp" port="8000"/>
+ <port protocol="tcp" port="8001"/>
+</service>
--
1.7.1
3
3
v1-v2 add spice.html.tmpl
This patch add the front end support of spice.
1 If there were a spice vm in host, show a "spice" button
but not "vnc" in guest page.
2 click "spice" we can show a screen just like the demo
http://www.spice-space.org/spice-html5/spice.html
Signed-off-by: zhoumeina <zhoumein(a)linux.vnet.ibm.com>
---
ui/js/spice/atKeynames.js | 183 ++++++
ui/js/spice/bitmap.js | 51 ++
ui/js/spice/cursor.js | 92 +++
ui/js/spice/display.js | 806 ++++++++++++++++++++++++
ui/js/spice/enums.js | 282 +++++++++
ui/js/spice/inputs.js | 251 ++++++++
ui/js/spice/jsbn.js | 589 ++++++++++++++++++
ui/js/spice/lz.js | 166 +++++
ui/js/spice/main.js | 176 ++++++
ui/js/spice/png.js | 256 ++++++++
ui/js/spice/prng4.js | 79 +++
ui/js/spice/quic.js | 1335 ++++++++++++++++++++++++++++++++++++++++
ui/js/spice/rng.js | 102 +++
ui/js/spice/rsa.js | 146 +++++
ui/js/spice/sha1.js | 346 +++++++++++
ui/js/spice/spiceconn.js | 447 ++++++++++++++
ui/js/spice/spicedataview.js | 96 +++
ui/js/spice/spicemsg.js | 883 ++++++++++++++++++++++++++
ui/js/spice/spicetype.js | 480 +++++++++++++++
ui/js/spice/ticket.js | 250 ++++++++
ui/js/spice/utils.js | 261 ++++++++
ui/js/spice/wire.js | 123 ++++
ui/js/src/kimchi.api.js | 21 +
ui/js/src/kimchi.guest_main.js | 26 +-
ui/pages/guest.html.tmpl | 1 +
ui/pages/spice.html.tmpl | 138 +++++
26 files changed, 7581 insertions(+), 5 deletions(-)
create mode 100644 ui/js/spice/atKeynames.js
create mode 100644 ui/js/spice/bitmap.js
create mode 100644 ui/js/spice/cursor.js
create mode 100644 ui/js/spice/display.js
create mode 100644 ui/js/spice/enums.js
create mode 100644 ui/js/spice/inputs.js
create mode 100644 ui/js/spice/jsbn.js
create mode 100644 ui/js/spice/lz.js
create mode 100644 ui/js/spice/main.js
create mode 100644 ui/js/spice/png.js
create mode 100644 ui/js/spice/prng4.js
create mode 100644 ui/js/spice/quic.js
create mode 100644 ui/js/spice/rng.js
create mode 100644 ui/js/spice/rsa.js
create mode 100644 ui/js/spice/sha1.js
create mode 100644 ui/js/spice/spiceconn.js
create mode 100644 ui/js/spice/spicedataview.js
create mode 100644 ui/js/spice/spicemsg.js
create mode 100644 ui/js/spice/spicetype.js
create mode 100644 ui/js/spice/ticket.js
create mode 100644 ui/js/spice/utils.js
create mode 100644 ui/js/spice/wire.js
create mode 100644 ui/pages/spice.html.tmpl
diff --git a/ui/js/spice/atKeynames.js b/ui/js/spice/atKeynames.js
new file mode 100644
index 0000000..e1e27fd
--- /dev/null
+++ b/ui/js/spice/atKeynames.js
@@ -0,0 +1,183 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Aric Stewart <aric(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+/*
+ * Copyright 1990,91 by Thomas Roell, Dinkelscherben, Germany.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of Thomas Roell not be used in
+ * advertising or publicity pertaining to distribution of the software without
+ * specific, written prior permission. Thomas Roell makes no representations
+ * about the suitability of this software for any purpose. It is provided
+ * "as is" without express or implied warranty.
+ *
+ * THOMAS ROELL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THOMAS ROELL BE LIABLE FOR ANY SPECIAL, 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.
+ *
+ */
+/*
+ * Copyright (c) 1994-2003 by The XFree86 Project, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Except as contained in this notice, the name of the copyright holder(s)
+ * and author(s) shall not be used in advertising or otherwise to promote
+ * the sale, use or other dealings in this Software without prior written
+ * authorization from the copyright holder(s) and author(s).
+ */
+
+/*
+ * NOTE: The AT/MF keyboards can generate (via the 8042) two (MF: three)
+ * sets of scancodes. Set3 can only be generated by a MF keyboard.
+ * Set2 sends a makecode for keypress, and the same code prefixed by a
+ * F0 for keyrelease. This is a little bit ugly to handle. Thus we use
+ * here for X386 the PC/XT compatible Set1. This set uses 8bit scancodes.
+ * Bit 7 ist set if the key is released. The code E0 switches to a
+ * different meaning to add the new MF cursorkeys, while not breaking old
+ * applications. E1 is another special prefix. Since I assume that there
+ * will be further versions of PC/XT scancode compatible keyboards, we
+ * may be in trouble one day.
+ *
+ * IDEA: 1) Use Set2 on AT84 keyboards and translate it to MF Set3.
+ * 2) Use the keyboards native set and translate it to common keysyms.
+ */
+
+/*
+ * definition of the AT84/MF101/MF102 Keyboard:
+ * ============================================================
+ * Defined Key Cap Glyphs Pressed value
+ * Key Name Main Also (hex) (dec)
+ * ---------------- ---------- ------- ------ ------
+ */
+
+var KEY_Escape =/* Escape 0x01 */ 1
+var KEY_1 =/* 1 ! 0x02 */ 2
+var KEY_2 =/* 2 @ 0x03 */ 3
+var KEY_3 =/* 3 # 0x04 */ 4
+var KEY_4 =/* 4 $ 0x05 */ 5
+var KEY_5 =/* 5 % 0x06 */ 6
+var KEY_6 =/* 6 ^ 0x07 */ 7
+var KEY_7 =/* 7 & 0x08 */ 8
+var KEY_8 =/* 8 * 0x09 */ 9
+var KEY_9 =/* 9 ( 0x0a */ 10
+var KEY_0 =/* 0 ) 0x0b */ 11
+var KEY_Minus =/* - (Minus) _ (Under) 0x0c */ 12
+var KEY_Equal =/* = (Equal) + 0x0d */ 13
+var KEY_BackSpace =/* Back Space 0x0e */ 14
+var KEY_Tab =/* Tab 0x0f */ 15
+var KEY_Q =/* Q 0x10 */ 16
+var KEY_W =/* W 0x11 */ 17
+var KEY_E =/* E 0x12 */ 18
+var KEY_R =/* R 0x13 */ 19
+var KEY_T =/* T 0x14 */ 20
+var KEY_Y =/* Y 0x15 */ 21
+var KEY_U =/* U 0x16 */ 22
+var KEY_I =/* I 0x17 */ 23
+var KEY_O =/* O 0x18 */ 24
+var KEY_P =/* P 0x19 */ 25
+var KEY_LBrace =/* [ { 0x1a */ 26
+var KEY_RBrace =/* ] } 0x1b */ 27
+var KEY_Enter =/* Enter 0x1c */ 28
+var KEY_LCtrl =/* Ctrl(left) 0x1d */ 29
+var KEY_A =/* A 0x1e */ 30
+var KEY_S =/* S 0x1f */ 31
+var KEY_D =/* D 0x20 */ 32
+var KEY_F =/* F 0x21 */ 33
+var KEY_G =/* G 0x22 */ 34
+var KEY_H =/* H 0x23 */ 35
+var KEY_J =/* J 0x24 */ 36
+var KEY_K =/* K 0x25 */ 37
+var KEY_L =/* L 0x26 */ 38
+var KEY_SemiColon =/* ;(SemiColon) :(Colon) 0x27 */ 39
+var KEY_Quote =/* ' (Apostr) " (Quote) 0x28 */ 40
+var KEY_Tilde =/* ` (Accent) ~ (Tilde) 0x29 */ 41
+var KEY_ShiftL =/* Shift(left) 0x2a */ 42
+var KEY_BSlash =/* \(BckSlash) |(VertBar)0x2b */ 43
+var KEY_Z =/* Z 0x2c */ 44
+var KEY_X =/* X 0x2d */ 45
+var KEY_C =/* C 0x2e */ 46
+var KEY_V =/* V 0x2f */ 47
+var KEY_B =/* B 0x30 */ 48
+var KEY_N =/* N 0x31 */ 49
+var KEY_M =/* M 0x32 */ 50
+var KEY_Comma =/* , (Comma) < (Less) 0x33 */ 51
+var KEY_Period =/* . (Period) >(Greater)0x34 */ 52
+var KEY_Slash =/* / (Slash) ? 0x35 */ 53
+var KEY_ShiftR =/* Shift(right) 0x36 */ 54
+var KEY_KP_Multiply =/* * 0x37 */ 55
+var KEY_Alt =/* Alt(left) 0x38 */ 56
+var KEY_Space =/* (SpaceBar) 0x39 */ 57
+var KEY_CapsLock =/* CapsLock 0x3a */ 58
+var KEY_F1 =/* F1 0x3b */ 59
+var KEY_F2 =/* F2 0x3c */ 60
+var KEY_F3 =/* F3 0x3d */ 61
+var KEY_F4 =/* F4 0x3e */ 62
+var KEY_F5 =/* F5 0x3f */ 63
+var KEY_F6 =/* F6 0x40 */ 64
+var KEY_F7 =/* F7 0x41 */ 65
+var KEY_F8 =/* F8 0x42 */ 66
+var KEY_F9 =/* F9 0x43 */ 67
+var KEY_F10 =/* F10 0x44 */ 68
+var KEY_NumLock =/* NumLock 0x45 */ 69
+var KEY_ScrollLock =/* ScrollLock 0x46 */ 70
+var KEY_KP_7 =/* 7 Home 0x47 */ 71
+var KEY_KP_8 =/* 8 Up 0x48 */ 72
+var KEY_KP_9 =/* 9 PgUp 0x49 */ 73
+var KEY_KP_Minus =/* - (Minus) 0x4a */ 74
+var KEY_KP_4 =/* 4 Left 0x4b */ 75
+var KEY_KP_5 =/* 5 0x4c */ 76
+var KEY_KP_6 =/* 6 Right 0x4d */ 77
+var KEY_KP_Plus =/* + (Plus) 0x4e */ 78
+var KEY_KP_1 =/* 1 End 0x4f */ 79
+var KEY_KP_2 =/* 2 Down 0x50 */ 80
+var KEY_KP_3 =/* 3 PgDown 0x51 */ 81
+var KEY_KP_0 =/* 0 Insert 0x52 */ 82
+var KEY_KP_Decimal =/* . (Decimal) Delete 0x53 */ 83
+var KEY_SysReqest =/* SysReqest 0x54 */ 84
+ /* NOTUSED 0x55 */
+var KEY_Less =/* < (Less) >(Greater) 0x56 */ 86
+var KEY_F11 =/* F11 0x57 */ 87
+var KEY_F12 =/* F12 0x58 */ 88
+
+var KEY_Prefix0 =/* special 0x60 */ 96
+var KEY_Prefix1 =/* specail 0x61 */ 97
diff --git a/ui/js/spice/bitmap.js b/ui/js/spice/bitmap.js
new file mode 100644
index 0000000..03f5127
--- /dev/null
+++ b/ui/js/spice/bitmap.js
@@ -0,0 +1,51 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+/*----------------------------------------------------------------------------
+** bitmap.js
+** Handle SPICE_IMAGE_TYPE_BITMAP
+**--------------------------------------------------------------------------*/
+function convert_spice_bitmap_to_web(context, spice_bitmap)
+{
+ var ret;
+ var offset, x;
+ var u8 = new Uint8Array(spice_bitmap.data);
+ if (spice_bitmap.format != SPICE_BITMAP_FMT_32BIT &&
+ spice_bitmap.format != SPICE_BITMAP_FMT_RGBA)
+ return undefined;
+
+ ret = context.createImageData(spice_bitmap.x, spice_bitmap.y);
+ for (offset = 0; offset < (spice_bitmap.y * spice_bitmap.stride); )
+ for (x = 0; x < spice_bitmap.x; x++, offset += 4)
+ {
+ ret.data[offset + 0 ] = u8[offset + 2];
+ ret.data[offset + 1 ] = u8[offset + 1];
+ ret.data[offset + 2 ] = u8[offset + 0];
+
+ // FIXME - We effectively treat all images as having SPICE_IMAGE_FLAGS_HIGH_BITS_SET
+ if (spice_bitmap.format == SPICE_BITMAP_FMT_32BIT)
+ ret.data[offset + 3] = 255;
+ else
+ ret.data[offset + 3] = u8[offset];
+ }
+
+ return ret;
+}
diff --git a/ui/js/spice/cursor.js b/ui/js/spice/cursor.js
new file mode 100644
index 0000000..b3184c3
--- /dev/null
+++ b/ui/js/spice/cursor.js
@@ -0,0 +1,92 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+/*----------------------------------------------------------------------------
+** SpiceCursorConn
+** Drive the Spice Cursor Channel
+**--------------------------------------------------------------------------*/
+function SpiceCursorConn()
+{
+ SpiceConn.apply(this, arguments);
+}
+
+SpiceCursorConn.prototype = Object.create(SpiceConn.prototype);
+SpiceCursorConn.prototype.process_channel_message = function(msg)
+{
+ if (msg.type == SPICE_MSG_CURSOR_INIT)
+ {
+ var cursor_init = new SpiceMsgCursorInit(msg.data);
+ DEBUG > 1 && console.log("SpiceMsgCursorInit");
+ if (this.parent && this.parent.inputs &&
+ this.parent.inputs.mouse_mode == SPICE_MOUSE_MODE_SERVER)
+ {
+ // FIXME - this imagines that the server actually
+ // provides the current cursor position,
+ // instead of 0,0. As of May 11, 2012,
+ // that assumption was false :-(.
+ this.parent.inputs.mousex = cursor_init.position.x;
+ this.parent.inputs.mousey = cursor_init.position.y;
+ }
+ // FIXME - We don't handle most of the parameters here...
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_CURSOR_SET)
+ {
+ var cursor_set = new SpiceMsgCursorSet(msg.data);
+ DEBUG > 1 && console.log("SpiceMsgCursorSet");
+ if (cursor_set.flags & SPICE_CURSOR_FLAGS_NONE)
+ {
+ document.getElementById(this.parent.screen_id).style.cursor = "none";
+ return true;
+ }
+
+ if (cursor_set.flags > 0)
+ this.log_warn("FIXME: No support for cursor flags " + cursor_set.flags);
+
+ if (cursor_set.cursor.header.type != SPICE_CURSOR_TYPE_ALPHA)
+ {
+ this.log_warn("FIXME: No support for cursor type " + cursor_set.cursor.header.type);
+ return false;
+ }
+
+ this.set_cursor(cursor_set.cursor);
+
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_CURSOR_HIDE)
+ {
+ DEBUG > 1 && console.log("SpiceMsgCursorHide");
+ document.getElementById(this.parent.screen_id).style.cursor = "none";
+ return true;
+ }
+
+ return false;
+}
+
+SpiceCursorConn.prototype.set_cursor = function(cursor)
+{
+ var pngstr = create_rgba_png(cursor.header.height, cursor.header.width, cursor.data);
+ var curstr = 'url(data:image/png,' + pngstr + ') ' +
+ cursor.header.hot_spot_x + ' ' + cursor.header.hot_spot_y + ", default";
+ document.getElementById(this.parent.screen_id).style.cursor = curstr;
+}
diff --git a/ui/js/spice/display.js b/ui/js/spice/display.js
new file mode 100644
index 0000000..fdf9dc5
--- /dev/null
+++ b/ui/js/spice/display.js
@@ -0,0 +1,806 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+/*----------------------------------------------------------------------------
+** FIXME: putImageData does not support Alpha blending
+** or compositing. So if we have data in an ImageData
+** format, we have to draw it onto a context,
+** and then use drawImage to put it onto the target,
+** as drawImage does alpha.
+**--------------------------------------------------------------------------*/
+function putImageDataWithAlpha(context, d, x, y)
+{
+ var c = document.createElement("canvas");
+ var t = c.getContext("2d");
+ c.setAttribute('width', d.width);
+ c.setAttribute('height', d.height);
+ t.putImageData(d, 0, 0);
+ context.drawImage(c, x, y, d.width, d.height);
+}
+
+/*----------------------------------------------------------------------------
+** FIXME: Spice will send an image with '0' alpha when it is intended to
+** go on a surface w/no alpha. So in that case, we have to strip
+** out the alpha. The test case for this was flux box; in a Xspice
+** server, right click on the desktop to get the menu; the top bar
+** doesn't paint/highlight correctly w/out this change.
+**--------------------------------------------------------------------------*/
+function stripAlpha(d)
+{
+ var i;
+ for (i = 0; i < (d.width * d.height * 4); i += 4)
+ d.data[i + 3] = 255;
+}
+
+/*----------------------------------------------------------------------------
+** SpiceDisplayConn
+** Drive the Spice Display Channel
+**--------------------------------------------------------------------------*/
+function SpiceDisplayConn()
+{
+ SpiceConn.apply(this, arguments);
+}
+
+SpiceDisplayConn.prototype = Object.create(SpiceConn.prototype);
+SpiceDisplayConn.prototype.process_channel_message = function(msg)
+{
+ if (msg.type == SPICE_MSG_DISPLAY_MARK)
+ {
+ // FIXME - DISPLAY_MARK not implemented (may be hard or impossible)
+ this.known_unimplemented(msg.type, "Display Mark");
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_RESET)
+ {
+ DEBUG > 2 && console.log("Display reset");
+ this.surfaces[this.primary_surface].canvas.context.restore();
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_DRAW_COPY)
+ {
+ var draw_copy = new SpiceMsgDisplayDrawCopy(msg.data);
+
+ DEBUG > 1 && this.log_draw("DrawCopy", draw_copy);
+
+ if (! draw_copy.base.box.is_same_size(draw_copy.data.src_area))
+ this.log_warn("FIXME: DrawCopy src_area is a different size than base.box; we do not handle that yet.");
+ if (draw_copy.base.clip.type != SPICE_CLIP_TYPE_NONE)
+ this.log_warn("FIXME: DrawCopy we don't handle clipping yet");
+ if (draw_copy.data.rop_descriptor != SPICE_ROPD_OP_PUT)
+ this.log_warn("FIXME: DrawCopy we don't handle ropd type: " + draw_copy.data.rop_descriptor);
+ if (draw_copy.data.mask.flags)
+ this.log_warn("FIXME: DrawCopy we don't handle mask flag: " + draw_copy.data.mask.flags);
+ if (draw_copy.data.mask.bitmap)
+ this.log_warn("FIXME: DrawCopy we don't handle mask");
+
+ if (draw_copy.data && draw_copy.data.src_bitmap)
+ {
+ if (draw_copy.data.src_bitmap.descriptor.flags &&
+ draw_copy.data.src_bitmap.descriptor.flags != SPICE_IMAGE_FLAGS_CACHE_ME &&
+ draw_copy.data.src_bitmap.descriptor.flags != SPICE_IMAGE_FLAGS_HIGH_BITS_SET)
+ {
+ this.log_warn("FIXME: DrawCopy unhandled image flags: " + draw_copy.data.src_bitmap.descriptor.flags);
+ DEBUG <= 1 && this.log_draw("DrawCopy", draw_copy);
+ }
+
+ if (draw_copy.data.src_bitmap.descriptor.type == SPICE_IMAGE_TYPE_QUIC)
+ {
+ var canvas = this.surfaces[draw_copy.base.surface_id].canvas;
+ if (! draw_copy.data.src_bitmap.quic)
+ {
+ this.log_warn("FIXME: DrawCopy could not handle this QUIC file.");
+ return false;
+ }
+ var source_img = convert_spice_quic_to_web(canvas.context,
+ draw_copy.data.src_bitmap.quic);
+
+ return this.draw_copy_helper(
+ { base: draw_copy.base,
+ src_area: draw_copy.data.src_area,
+ image_data: source_img,
+ tag: "copyquic." + draw_copy.data.src_bitmap.quic.type,
+ has_alpha: (draw_copy.data.src_bitmap.quic.type == QUIC_IMAGE_TYPE_RGBA ? true : false) ,
+ descriptor : draw_copy.data.src_bitmap.descriptor
+ });
+ }
+ else if (draw_copy.data.src_bitmap.descriptor.type == SPICE_IMAGE_TYPE_FROM_CACHE ||
+ draw_copy.data.src_bitmap.descriptor.type == SPICE_IMAGE_TYPE_FROM_CACHE_LOSSLESS)
+ {
+ if (! this.cache || ! this.cache[draw_copy.data.src_bitmap.descriptor.id])
+ {
+ this.log_warn("FIXME: DrawCopy did not find image id " + draw_copy.data.src_bitmap.descriptor.id + " in cache.");
+ return false;
+ }
+
+ return this.draw_copy_helper(
+ { base: draw_copy.base,
+ src_area: draw_copy.data.src_area,
+ image_data: this.cache[draw_copy.data.src_bitmap.descriptor.id],
+ tag: "copycache." + draw_copy.data.src_bitmap.descriptor.id,
+ has_alpha: true, /* FIXME - may want this to be false... */
+ descriptor : draw_copy.data.src_bitmap.descriptor
+ });
+
+ /* FIXME - LOSSLESS CACHE ramifications not understood or handled */
+ }
+ else if (draw_copy.data.src_bitmap.descriptor.type == SPICE_IMAGE_TYPE_SURFACE)
+ {
+ var source_context = this.surfaces[draw_copy.data.src_bitmap.surface_id].canvas.context;
+ var target_context = this.surfaces[draw_copy.base.surface_id].canvas.context;
+
+ var source_img = source_context.getImageData(
+ draw_copy.data.src_area.left, draw_copy.data.src_area.top,
+ draw_copy.data.src_area.right - draw_copy.data.src_area.left,
+ draw_copy.data.src_area.bottom - draw_copy.data.src_area.top);
+ var computed_src_area = new SpiceRect;
+ computed_src_area.top = computed_src_area.left = 0;
+ computed_src_area.right = source_img.width;
+ computed_src_area.bottom = source_img.height;
+
+ /* FIXME - there is a potential optimization here.
+ That is, if the surface is from 0,0, and
+ both surfaces are alpha surfaces, you should
+ be able to just do a drawImage, which should
+ save time. */
+
+ return this.draw_copy_helper(
+ { base: draw_copy.base,
+ src_area: computed_src_area,
+ image_data: source_img,
+ tag: "copysurf." + draw_copy.data.src_bitmap.surface_id,
+ has_alpha: this.surfaces[draw_copy.data.src_bitmap.surface_id].format == SPICE_SURFACE_FMT_32_xRGB ? false : true,
+ descriptor : draw_copy.data.src_bitmap.descriptor
+ });
+
+ return true;
+ }
+ else if (draw_copy.data.src_bitmap.descriptor.type == SPICE_IMAGE_TYPE_JPEG)
+ {
+ if (! draw_copy.data.src_bitmap.jpeg)
+ {
+ this.log_warn("FIXME: DrawCopy could not handle this JPEG file.");
+ return false;
+ }
+
+ // FIXME - how lame is this. Be have it in binary format, and we have
+ // to put it into string to get it back into jpeg. Blech.
+ var tmpstr = "data:image/jpeg,";
+ var img = new Image;
+ var i;
+ var qdv = new Uint8Array(draw_copy.data.src_bitmap.jpeg.data);
+ for (i = 0; i < qdv.length; i++)
+ {
+ tmpstr += '%';
+ if (qdv[i] < 16)
+ tmpstr += '0';
+ tmpstr += qdv[i].toString(16);
+ }
+
+ img.o =
+ { base: draw_copy.base,
+ tag: "jpeg." + draw_copy.data.src_bitmap.surface_id,
+ descriptor : draw_copy.data.src_bitmap.descriptor,
+ sc : this,
+ };
+ img.onload = handle_draw_jpeg_onload;
+ img.src = tmpstr;
+
+ return true;
+ }
+ else if (draw_copy.data.src_bitmap.descriptor.type == SPICE_IMAGE_TYPE_JPEG_ALPHA)
+ {
+ if (! draw_copy.data.src_bitmap.jpeg_alpha)
+ {
+ this.log_warn("FIXME: DrawCopy could not handle this JPEG ALPHA file.");
+ return false;
+ }
+
+ // FIXME - how lame is this. Be have it in binary format, and we have
+ // to put it into string to get it back into jpeg. Blech.
+ var tmpstr = "data:image/jpeg,";
+ var img = new Image;
+ var i;
+ var qdv = new Uint8Array(draw_copy.data.src_bitmap.jpeg_alpha.data);
+ for (i = 0; i < qdv.length; i++)
+ {
+ tmpstr += '%';
+ if (qdv[i] < 16)
+ tmpstr += '0';
+ tmpstr += qdv[i].toString(16);
+ }
+
+ img.o =
+ { base: draw_copy.base,
+ tag: "jpeg." + draw_copy.data.src_bitmap.surface_id,
+ descriptor : draw_copy.data.src_bitmap.descriptor,
+ sc : this,
+ };
+
+ if (this.surfaces[draw_copy.base.surface_id].format == SPICE_SURFACE_FMT_32_ARGB)
+ {
+
+ var canvas = this.surfaces[draw_copy.base.surface_id].canvas;
+ img.alpha_img = convert_spice_lz_to_web(canvas.context,
+ draw_copy.data.src_bitmap.jpeg_alpha.alpha);
+ }
+ img.onload = handle_draw_jpeg_onload;
+ img.src = tmpstr;
+
+ return true;
+ }
+ else if (draw_copy.data.src_bitmap.descriptor.type == SPICE_IMAGE_TYPE_BITMAP)
+ {
+ var canvas = this.surfaces[draw_copy.base.surface_id].canvas;
+ if (! draw_copy.data.src_bitmap.bitmap)
+ {
+ this.log_err("null bitmap");
+ return false;
+ }
+
+ var source_img = convert_spice_bitmap_to_web(canvas.context,
+ draw_copy.data.src_bitmap.bitmap);
+ if (! source_img)
+ {
+ this.log_warn("FIXME: Unable to interpret bitmap of format: " +
+ draw_copy.data.src_bitmap.bitmap.format);
+ return false;
+ }
+
+ return this.draw_copy_helper(
+ { base: draw_copy.base,
+ src_area: draw_copy.data.src_area,
+ image_data: source_img,
+ tag: "bitmap." + draw_copy.data.src_bitmap.bitmap.format,
+ has_alpha: draw_copy.data.src_bitmap.bitmap == SPICE_BITMAP_FMT_32BIT ? false : true,
+ descriptor : draw_copy.data.src_bitmap.descriptor
+ });
+ }
+ else if (draw_copy.data.src_bitmap.descriptor.type == SPICE_IMAGE_TYPE_LZ_RGB)
+ {
+ var canvas = this.surfaces[draw_copy.base.surface_id].canvas;
+ if (! draw_copy.data.src_bitmap.lz_rgb)
+ {
+ this.log_err("null lz_rgb ");
+ return false;
+ }
+
+ if (draw_copy.data.src_bitmap.lz_rgb.top_down != 1)
+ this.log_warn("FIXME: Implement non top down support for lz_rgb");
+
+ var source_img = convert_spice_lz_to_web(canvas.context,
+ draw_copy.data.src_bitmap.lz_rgb);
+ if (! source_img)
+ {
+ this.log_warn("FIXME: Unable to interpret bitmap of type: " +
+ draw_copy.data.src_bitmap.lz_rgb.type);
+ return false;
+ }
+
+ return this.draw_copy_helper(
+ { base: draw_copy.base,
+ src_area: draw_copy.data.src_area,
+ image_data: source_img,
+ tag: "lz_rgb." + draw_copy.data.src_bitmap.lz_rgb.type,
+ has_alpha: draw_copy.data.src_bitmap.lz_rgb.type == LZ_IMAGE_TYPE_RGBA ? true : false ,
+ descriptor : draw_copy.data.src_bitmap.descriptor
+ });
+ }
+ else
+ {
+ this.log_warn("FIXME: DrawCopy unhandled image type: " + draw_copy.data.src_bitmap.descriptor.type);
+ this.log_draw("DrawCopy", draw_copy);
+ return false;
+ }
+ }
+
+ this.log_warn("FIXME: DrawCopy no src_bitmap.");
+ return false;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_DRAW_FILL)
+ {
+ var draw_fill = new SpiceMsgDisplayDrawFill(msg.data);
+
+ DEBUG > 1 && this.log_draw("DrawFill", draw_fill);
+
+ if (draw_fill.data.rop_descriptor != SPICE_ROPD_OP_PUT)
+ this.log_warn("FIXME: DrawFill we don't handle ropd type: " + draw_fill.data.rop_descriptor);
+ if (draw_fill.data.mask.flags)
+ this.log_warn("FIXME: DrawFill we don't handle mask flag: " + draw_fill.data.mask.flags);
+ if (draw_fill.data.mask.bitmap)
+ this.log_warn("FIXME: DrawFill we don't handle mask");
+
+ if (draw_fill.data.brush.type == SPICE_BRUSH_TYPE_SOLID)
+ {
+ // FIXME - do brushes ever have alpha?
+ var color = draw_fill.data.brush.color & 0xffffff;
+ var color_str = "rgb(" + (color >> 16) + ", " + ((color >> 8) & 0xff) + ", " + (color & 0xff) + ")";
+ this.surfaces[draw_fill.base.surface_id].canvas.context.fillStyle = color_str;
+
+ this.surfaces[draw_fill.base.surface_id].canvas.context.fillRect(
+ draw_fill.base.box.left, draw_fill.base.box.top,
+ draw_fill.base.box.right - draw_fill.base.box.left,
+ draw_fill.base.box.bottom - draw_fill.base.box.top);
+
+ if (DUMP_DRAWS && this.parent.dump_id)
+ {
+ var debug_canvas = document.createElement("canvas");
+ debug_canvas.setAttribute('width', this.surfaces[draw_fill.base.surface_id].canvas.width);
+ debug_canvas.setAttribute('height', this.surfaces[draw_fill.base.surface_id].canvas.height);
+ debug_canvas.setAttribute('id', "fillbrush." + draw_fill.base.surface_id + "." + this.surfaces[draw_fill.base.surface_id].draw_count);
+ debug_canvas.getContext("2d").fillStyle = color_str;
+ debug_canvas.getContext("2d").fillRect(
+ draw_fill.base.box.left, draw_fill.base.box.top,
+ draw_fill.base.box.right - draw_fill.base.box.left,
+ draw_fill.base.box.bottom - draw_fill.base.box.top);
+ document.getElementById(this.parent.dump_id).appendChild(debug_canvas);
+ }
+
+ this.surfaces[draw_fill.base.surface_id].draw_count++;
+
+ }
+ else
+ {
+ this.log_warn("FIXME: DrawFill can't handle brush type: " + draw_fill.data.brush.type);
+ }
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_COPY_BITS)
+ {
+ var copy_bits = new SpiceMsgDisplayCopyBits(msg.data);
+
+ DEBUG > 1 && this.log_draw("CopyBits", copy_bits);
+
+ var source_canvas = this.surfaces[copy_bits.base.surface_id].canvas;
+ var source_context = source_canvas.context;
+
+ var width = source_canvas.width - copy_bits.src_pos.x;
+ var height = source_canvas.height - copy_bits.src_pos.y;
+ if (width > (copy_bits.base.box.right - copy_bits.base.box.left))
+ width = copy_bits.base.box.right - copy_bits.base.box.left;
+ if (height > (copy_bits.base.box.bottom - copy_bits.base.box.top))
+ height = copy_bits.base.box.bottom - copy_bits.base.box.top;
+
+ var source_img = source_context.getImageData(
+ copy_bits.src_pos.x, copy_bits.src_pos.y, width, height);
+ //source_context.putImageData(source_img, copy_bits.base.box.left, copy_bits.base.box.top);
+ putImageDataWithAlpha(source_context, source_img, copy_bits.base.box.left, copy_bits.base.box.top);
+
+ if (DUMP_DRAWS && this.parent.dump_id)
+ {
+ var debug_canvas = document.createElement("canvas");
+ debug_canvas.setAttribute('width', width);
+ debug_canvas.setAttribute('height', height);
+ debug_canvas.setAttribute('id', "copybits" + copy_bits.base.surface_id + "." + this.surfaces[copy_bits.base.surface_id].draw_count);
+ debug_canvas.getContext("2d").putImageData(source_img, 0, 0);
+ document.getElementById(this.parent.dump_id).appendChild(debug_canvas);
+ }
+
+
+ this.surfaces[copy_bits.base.surface_id].draw_count++;
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_INVAL_ALL_PALETTES)
+ {
+ this.known_unimplemented(msg.type, "Inval All Palettes");
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_SURFACE_CREATE)
+ {
+ if (! ("surfaces" in this))
+ this.surfaces = [];
+
+ var m = new SpiceMsgSurfaceCreate(msg.data);
+ DEBUG > 1 && console.log(this.type + ": MsgSurfaceCreate id " + m.surface.surface_id
+ + "; " + m.surface.width + "x" + m.surface.height
+ + "; format " + m.surface.format
+ + "; flags " + m.surface.flags);
+ if (m.surface.format != SPICE_SURFACE_FMT_32_xRGB &&
+ m.surface.format != SPICE_SURFACE_FMT_32_ARGB)
+ {
+ this.log_warn("FIXME: cannot handle surface format " + m.surface.format + " yet.");
+ return false;
+ }
+
+ var canvas = document.createElement("canvas");
+ canvas.setAttribute('width', m.surface.width);
+ canvas.setAttribute('height', m.surface.height);
+ canvas.setAttribute('id', "spice_surface_" + m.surface.surface_id);
+ canvas.setAttribute('tabindex', m.surface.surface_id);
+ canvas.context = canvas.getContext("2d");
+
+ if (DUMP_CANVASES && this.parent.dump_id)
+ document.getElementById(this.parent.dump_id).appendChild(canvas);
+
+ m.surface.canvas = canvas;
+ m.surface.draw_count = 0;
+ this.surfaces[m.surface.surface_id] = m.surface;
+
+ if (m.surface.flags & SPICE_SURFACE_FLAGS_PRIMARY)
+ {
+ this.primary_surface = m.surface.surface_id;
+
+ /* This .save() is done entirely to enable SPICE_MSG_DISPLAY_RESET */
+ canvas.context.save();
+ document.getElementById(this.parent.screen_id).appendChild(canvas);
+ document.getElementById(this.parent.screen_id).setAttribute('width', m.surface.width);
+ document.getElementById(this.parent.screen_id).setAttribute('height', m.surface.height);
+ this.hook_events();
+ }
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_SURFACE_DESTROY)
+ {
+ var m = new SpiceMsgSurfaceDestroy(msg.data);
+ DEBUG > 1 && console.log(this.type + ": MsgSurfaceDestroy id " + m.surface_id);
+ this.delete_surface(m.surface_id);
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_STREAM_CREATE)
+ {
+ var m = new SpiceMsgDisplayStreamCreate(msg.data);
+ DEBUG > 1 && console.log(this.type + ": MsgStreamCreate id" + m.id);
+ if (!this.streams)
+ this.streams = new Array();
+ if (this.streams[m.id])
+ console.log("Stream already exists");
+ else
+ this.streams[m.id] = m;
+ if (m.codec_type != SPICE_VIDEO_CODEC_TYPE_MJPEG)
+ console.log("Unhandled stream codec: "+m.codec_type);
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_STREAM_DATA)
+ {
+ var m = new SpiceMsgDisplayStreamData(msg.data);
+ if (!this.streams[m.base.id])
+ {
+ console.log("no stream for data");
+ return false;
+ }
+ if (this.streams[m.base.id].codec_type === SPICE_VIDEO_CODEC_TYPE_MJPEG)
+ {
+ var tmpstr = "data:image/jpeg,";
+ var img = new Image;
+ var i;
+ for (i = 0; i < m.data.length; i++)
+ {
+ tmpstr += '%';
+ if (m.data[i] < 16)
+ tmpstr += '0';
+ tmpstr += m.data[i].toString(16);
+ }
+ var strm_base = new SpiceMsgDisplayBase();
+ strm_base.surface_id = this.streams[m.base.id].surface_id;
+ strm_base.box = this.streams[m.base.id].dest;
+ strm_base.clip = this.streams[m.base.id].clip;
+ img.o =
+ { base: strm_base,
+ tag: "mjpeg." + m.base.id,
+ descriptor: null,
+ sc : this,
+ };
+ img.onload = handle_draw_jpeg_onload;
+ img.src = tmpstr;
+ }
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_STREAM_CLIP)
+ {
+ var m = new SpiceMsgDisplayStreamClip(msg.data);
+ DEBUG > 1 && console.log(this.type + ": MsgStreamClip id" + m.id);
+ this.streams[m.id].clip = m.clip;
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_STREAM_DESTROY)
+ {
+ var m = new SpiceMsgDisplayStreamDestroy(msg.data);
+ DEBUG > 1 && console.log(this.type + ": MsgStreamDestroy id" + m.id);
+ this.streams[m.id] = undefined;
+ return true;
+ }
+
+ return false;
+}
+
+SpiceDisplayConn.prototype.delete_surface = function(surface_id)
+{
+ var canvas = document.getElementById("spice_surface_" + surface_id);
+ if (DUMP_CANVASES && this.parent.dump_id)
+ document.getElementById(this.parent.dump_id).removeChild(canvas);
+ if (this.primary_surface == surface_id)
+ {
+ this.unhook_events();
+ this.primary_surface = undefined;
+ document.getElementById(this.parent.screen_id).removeChild(canvas);
+ }
+
+ delete this.surfaces[surface_id];
+}
+
+
+SpiceDisplayConn.prototype.draw_copy_helper = function(o)
+{
+
+ var canvas = this.surfaces[o.base.surface_id].canvas;
+ if (o.has_alpha)
+ {
+ /* FIXME - This is based on trial + error, not a serious thoughtful
+ analysis of what Spice requires. See display.js for more. */
+ if (this.surfaces[o.base.surface_id].format == SPICE_SURFACE_FMT_32_xRGB)
+ {
+ stripAlpha(o.image_data);
+ canvas.context.putImageData(o.image_data, o.base.box.left, o.base.box.top);
+ }
+ else
+ putImageDataWithAlpha(canvas.context, o.image_data,
+ o.base.box.left, o.base.box.top);
+ }
+ else
+ canvas.context.putImageData(o.image_data, o.base.box.left, o.base.box.top);
+
+ if (o.src_area.left > 0 || o.src_area.top > 0)
+ {
+ this.log_warn("FIXME: DrawCopy not shifting draw copies just yet...");
+ }
+
+ if (o.descriptor && (o.descriptor.flags & SPICE_IMAGE_FLAGS_CACHE_ME))
+ {
+ if (! ("cache" in this))
+ this.cache = [];
+ this.cache[o.descriptor.id] = o.image_data;
+ }
+
+ if (DUMP_DRAWS && this.parent.dump_id)
+ {
+ var debug_canvas = document.createElement("canvas");
+ debug_canvas.setAttribute('width', o.image_data.width);
+ debug_canvas.setAttribute('height', o.image_data.height);
+ debug_canvas.setAttribute('id', o.tag + "." +
+ this.surfaces[o.base.surface_id].draw_count + "." +
+ o.base.surface_id + "@" + o.base.box.left + "x" + o.base.box.top);
+ debug_canvas.getContext("2d").putImageData(o.image_data, 0, 0);
+ document.getElementById(this.parent.dump_id).appendChild(debug_canvas);
+ }
+
+ this.surfaces[o.base.surface_id].draw_count++;
+
+ return true;
+}
+
+
+SpiceDisplayConn.prototype.log_draw = function(prefix, draw)
+{
+ var str = prefix + "." + draw.base.surface_id + "." + this.surfaces[draw.base.surface_id].draw_count + ": ";
+ str += "base.box " + draw.base.box.left + ", " + draw.base.box.top + " to " +
+ draw.base.box.right + ", " + draw.base.box.bottom;
+ str += "; clip.type " + draw.base.clip.type;
+
+ if (draw.data)
+ {
+ if (draw.data.src_area)
+ str += "; src_area " + draw.data.src_area.left + ", " + draw.data.src_area.top + " to "
+ + draw.data.src_area.right + ", " + draw.data.src_area.bottom;
+
+ if (draw.data.src_bitmap && draw.data.src_bitmap != null)
+ {
+ str += "; src_bitmap id: " + draw.data.src_bitmap.descriptor.id;
+ str += "; src_bitmap width " + draw.data.src_bitmap.descriptor.width + ", height " + draw.data.src_bitmap.descriptor.height;
+ str += "; src_bitmap type " + draw.data.src_bitmap.descriptor.type + ", flags " + draw.data.src_bitmap.descriptor.flags;
+ if (draw.data.src_bitmap.surface_id !== undefined)
+ str += "; src_bitmap surface_id " + draw.data.src_bitmap.surface_id;
+ if (draw.data.src_bitmap.quic)
+ str += "; QUIC type " + draw.data.src_bitmap.quic.type +
+ "; width " + draw.data.src_bitmap.quic.width +
+ "; height " + draw.data.src_bitmap.quic.height ;
+ if (draw.data.src_bitmap.lz_rgb)
+ str += "; LZ_RGB length " + draw.data.src_bitmap.lz_rgb.length +
+ "; magic " + draw.data.src_bitmap.lz_rgb.magic +
+ "; version 0x" + draw.data.src_bitmap.lz_rgb.version.toString(16) +
+ "; type " + draw.data.src_bitmap.lz_rgb.type +
+ "; width " + draw.data.src_bitmap.lz_rgb.width +
+ "; height " + draw.data.src_bitmap.lz_rgb.height +
+ "; stride " + draw.data.src_bitmap.lz_rgb.stride +
+ "; top down " + draw.data.src_bitmap.lz_rgb.top_down;
+ }
+ else
+ str += "; src_bitmap is null";
+
+ if (draw.data.brush)
+ {
+ if (draw.data.brush.type == SPICE_BRUSH_TYPE_SOLID)
+ str += "; brush.color 0x" + draw.data.brush.color.toString(16);
+ if (draw.data.brush.type == SPICE_BRUSH_TYPE_PATTERN)
+ {
+ str += "; brush.pat ";
+ if (draw.data.brush.pattern.pat != null)
+ str += "[SpiceImage]";
+ else
+ str += "[null]";
+ str += " at " + draw.data.brush.pattern.pos.x + ", " + draw.data.brush.pattern.pos.y;
+ }
+ }
+
+ str += "; rop_descriptor " + draw.data.rop_descriptor;
+ if (draw.data.scale_mode !== undefined)
+ str += "; scale_mode " + draw.data.scale_mode;
+ str += "; mask.flags " + draw.data.mask.flags;
+ str += "; mask.pos " + draw.data.mask.pos.x + ", " + draw.data.mask.pos.y;
+ if (draw.data.mask.bitmap != null)
+ {
+ str += "; mask.bitmap width " + draw.data.mask.bitmap.descriptor.width + ", height " + draw.data.mask.bitmap.descriptor.height;
+ str += "; mask.bitmap type " + draw.data.mask.bitmap.descriptor.type + ", flags " + draw.data.mask.bitmap.descriptor.flags;
+ }
+ else
+ str += "; mask.bitmap is null";
+ }
+
+ console.log(str);
+}
+
+SpiceDisplayConn.prototype.hook_events = function()
+{
+ if (this.primary_surface !== undefined)
+ {
+ var canvas = this.surfaces[this.primary_surface].canvas;
+ canvas.sc = this.parent;
+ canvas.addEventListener('mousemove', handle_mousemove);
+ canvas.addEventListener('mousedown', handle_mousedown);
+ canvas.addEventListener('contextmenu', handle_contextmenu);
+ canvas.addEventListener('mouseup', handle_mouseup);
+ canvas.addEventListener('keydown', handle_keydown);
+ canvas.addEventListener('keyup', handle_keyup);
+ canvas.addEventListener('mouseout', handle_mouseout);
+ canvas.addEventListener('mouseover', handle_mouseover);
+ canvas.addEventListener('mousewheel', handle_mousewheel);
+ canvas.focus();
+ }
+}
+
+SpiceDisplayConn.prototype.unhook_events = function()
+{
+ if (this.primary_surface !== undefined)
+ {
+ var canvas = this.surfaces[this.primary_surface].canvas;
+ canvas.removeEventListener('mousemove', handle_mousemove);
+ canvas.removeEventListener('mousedown', handle_mousedown);
+ canvas.removeEventListener('contextmenu', handle_contextmenu);
+ canvas.removeEventListener('mouseup', handle_mouseup);
+ canvas.removeEventListener('keydown', handle_keydown);
+ canvas.removeEventListener('keyup', handle_keyup);
+ canvas.removeEventListener('mouseout', handle_mouseout);
+ canvas.removeEventListener('mouseover', handle_mouseover);
+ canvas.removeEventListener('mousewheel', handle_mousewheel);
+ }
+}
+
+
+SpiceDisplayConn.prototype.destroy_surfaces = function()
+{
+ for (var s in this.surfaces)
+ {
+ this.delete_surface(this.surfaces[s].surface_id);
+ }
+
+ this.surfaces = undefined;
+}
+
+
+function handle_mouseover(e)
+{
+ this.focus();
+}
+
+function handle_mouseout(e)
+{
+ this.blur();
+}
+
+function handle_draw_jpeg_onload()
+{
+ var temp_canvas = null;
+ var context;
+
+ /*------------------------------------------------------------
+ ** FIXME:
+ ** The helper should be extended to be able to handle actual HtmlImageElements
+ ** ...and the cache should be modified to do so as well
+ **----------------------------------------------------------*/
+ if (this.o.sc.surfaces[this.o.base.surface_id] === undefined)
+ {
+ // This can happen; if the jpeg image loads after our surface
+ // has been destroyed (e.g. open a menu, close it quickly),
+ // we'll find we have no surface.
+ DEBUG > 2 && this.o.sc.log_info("Discarding jpeg; presumed lost surface " + this.o.base.surface_id);
+ temp_canvas = document.createElement("canvas");
+ temp_canvas.setAttribute('width', this.o.base.box.right);
+ temp_canvas.setAttribute('height', this.o.base.box.bottom);
+ context = temp_canvas.getContext("2d");
+ }
+ else
+ context = this.o.sc.surfaces[this.o.base.surface_id].canvas.context;
+
+ if (this.alpha_img)
+ {
+ var c = document.createElement("canvas");
+ var t = c.getContext("2d");
+ c.setAttribute('width', this.alpha_img.width);
+ c.setAttribute('height', this.alpha_img.height);
+ t.putImageData(this.alpha_img, 0, 0);
+ t.globalCompositeOperation = 'source-in';
+ t.drawImage(this, 0, 0);
+
+ context.drawImage(c, this.o.base.box.left, this.o.base.box.top);
+
+ if (this.o.descriptor &&
+ (this.o.descriptor.flags & SPICE_IMAGE_FLAGS_CACHE_ME))
+ {
+ if (! ("cache" in this.o.sc))
+ this.o.sc.cache = [];
+
+ this.o.sc.cache[this.o.descriptor.id] =
+ t.getImageData(0, 0,
+ this.alpha_img.width,
+ this.alpha_img.height);
+ }
+ }
+ else
+ {
+ context.drawImage(this, this.o.base.box.left, this.o.base.box.top);
+
+ if (this.o.descriptor &&
+ (this.o.descriptor.flags & SPICE_IMAGE_FLAGS_CACHE_ME))
+ {
+ if (! ("cache" in this.o.sc))
+ this.o.sc.cache = [];
+
+ this.o.sc.cache[this.o.descriptor.id] =
+ context.getImageData(this.o.base.box.left, this.o.base.box.top,
+ this.o.base.box.right - this.o.base.box.left,
+ this.o.base.box.bottom - this.o.base.box.top);
+ }
+ }
+
+ if (temp_canvas == null)
+ {
+ if (DUMP_DRAWS && this.o.sc.parent.dump_id)
+ {
+ var debug_canvas = document.createElement("canvas");
+ debug_canvas.setAttribute('id', this.o.tag + "." +
+ this.o.sc.surfaces[this.o.base.surface_id].draw_count + "." +
+ this.o.base.surface_id + "@" + this.o.base.box.left + "x" + this.o.base.box.top);
+ debug_canvas.getContext("2d").drawImage(this, 0, 0);
+ document.getElementById(this.o.sc.parent.dump_id).appendChild(debug_canvas);
+ }
+
+ this.o.sc.surfaces[this.o.base.surface_id].draw_count++;
+ }
+}
diff --git a/ui/js/spice/enums.js b/ui/js/spice/enums.js
new file mode 100644
index 0000000..fd42a14
--- /dev/null
+++ b/ui/js/spice/enums.js
@@ -0,0 +1,282 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+/*----------------------------------------------------------------------------
+** enums.js
+** 'constants' for Spice
+**--------------------------------------------------------------------------*/
+var SPICE_MAGIC = "REDQ";
+var SPICE_VERSION_MAJOR = 2;
+var SPICE_VERSION_MINOR = 2;
+
+var SPICE_CONNECT_TIMEOUT = (30 * 1000);
+
+var SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION = 0;
+var SPICE_COMMON_CAP_AUTH_SPICE = 1;
+var SPICE_COMMON_CAP_AUTH_SASL = 2;
+var SPICE_COMMON_CAP_MINI_HEADER = 3;
+
+var SPICE_TICKET_KEY_PAIR_LENGTH = 1024;
+var SPICE_TICKET_PUBKEY_BYTES = (SPICE_TICKET_KEY_PAIR_LENGTH / 8 + 34);
+
+var SPICE_LINK_ERR_OK = 0,
+ SPICE_LINK_ERR_ERROR = 1,
+ SPICE_LINK_ERR_INVALID_MAGIC = 2,
+ SPICE_LINK_ERR_INVALID_DATA = 3,
+ SPICE_LINK_ERR_VERSION_MISMATCH = 4,
+ SPICE_LINK_ERR_NEED_SECURED = 5,
+ SPICE_LINK_ERR_NEED_UNSECURED = 6,
+ SPICE_LINK_ERR_PERMISSION_DENIED = 7,
+ SPICE_LINK_ERR_BAD_CONNECTION_ID = 8,
+ SPICE_LINK_ERR_CHANNEL_NOT_AVAILABLE = 9;
+
+var SPICE_MSG_MIGRATE = 1;
+var SPICE_MSG_MIGRATE_DATA = 2;
+var SPICE_MSG_SET_ACK = 3;
+var SPICE_MSG_PING = 4;
+var SPICE_MSG_WAIT_FOR_CHANNELS = 5;
+var SPICE_MSG_DISCONNECTING = 6;
+var SPICE_MSG_NOTIFY = 7;
+var SPICE_MSG_LIST = 8;
+
+var SPICE_MSG_MAIN_MIGRATE_BEGIN = 101;
+var SPICE_MSG_MAIN_MIGRATE_CANCEL = 102;
+var SPICE_MSG_MAIN_INIT = 103;
+var SPICE_MSG_MAIN_CHANNELS_LIST = 104;
+var SPICE_MSG_MAIN_MOUSE_MODE = 105;
+var SPICE_MSG_MAIN_MULTI_MEDIA_TIME = 106;
+var SPICE_MSG_MAIN_AGENT_CONNECTED = 107;
+var SPICE_MSG_MAIN_AGENT_DISCONNECTED = 108;
+var SPICE_MSG_MAIN_AGENT_DATA = 109;
+var SPICE_MSG_MAIN_AGENT_TOKEN = 110;
+var SPICE_MSG_MAIN_MIGRATE_SWITCH_HOST = 111;
+var SPICE_MSG_MAIN_MIGRATE_END = 112;
+var SPICE_MSG_MAIN_NAME = 113;
+var SPICE_MSG_MAIN_UUID = 114;
+var SPICE_MSG_END_MAIN = 115;
+
+
+var SPICE_MSGC_ACK_SYNC = 1;
+var SPICE_MSGC_ACK = 2;
+var SPICE_MSGC_PONG = 3;
+var SPICE_MSGC_MIGRATE_FLUSH_MARK = 4;
+var SPICE_MSGC_MIGRATE_DATA = 5;
+var SPICE_MSGC_DISCONNECTING = 6;
+
+
+var SPICE_MSGC_MAIN_CLIENT_INFO = 101;
+var SPICE_MSGC_MAIN_MIGRATE_CONNECTED = 102;
+var SPICE_MSGC_MAIN_MIGRATE_CONNECT_ERROR = 103;
+var SPICE_MSGC_MAIN_ATTACH_CHANNELS = 104;
+var SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST = 105;
+var SPICE_MSGC_MAIN_AGENT_START = 106;
+var SPICE_MSGC_MAIN_AGENT_DATA = 107;
+var SPICE_MSGC_MAIN_AGENT_TOKEN = 108;
+var SPICE_MSGC_MAIN_MIGRATE_END = 109;
+var SPICE_MSGC_END_MAIN = 110;
+
+var SPICE_MSG_DISPLAY_MODE = 101;
+var SPICE_MSG_DISPLAY_MARK = 102;
+var SPICE_MSG_DISPLAY_RESET = 103;
+var SPICE_MSG_DISPLAY_COPY_BITS = 104;
+var SPICE_MSG_DISPLAY_INVAL_LIST = 105;
+var SPICE_MSG_DISPLAY_INVAL_ALL_PIXMAPS = 106;
+var SPICE_MSG_DISPLAY_INVAL_PALETTE = 107;
+var SPICE_MSG_DISPLAY_INVAL_ALL_PALETTES= 108;
+
+var SPICE_MSG_DISPLAY_STREAM_CREATE = 122;
+var SPICE_MSG_DISPLAY_STREAM_DATA = 123;
+var SPICE_MSG_DISPLAY_STREAM_CLIP = 124;
+var SPICE_MSG_DISPLAY_STREAM_DESTROY = 125;
+var SPICE_MSG_DISPLAY_STREAM_DESTROY_ALL= 126;
+
+var SPICE_MSG_DISPLAY_DRAW_FILL = 302;
+var SPICE_MSG_DISPLAY_DRAW_OPAQUE = 303;
+var SPICE_MSG_DISPLAY_DRAW_COPY = 304;
+var SPICE_MSG_DISPLAY_DRAW_BLEND = 305;
+var SPICE_MSG_DISPLAY_DRAW_BLACKNESS = 306;
+var SPICE_MSG_DISPLAY_DRAW_WHITENESS = 307;
+var SPICE_MSG_DISPLAY_DRAW_INVERS = 308;
+var SPICE_MSG_DISPLAY_DRAW_ROP3 = 309;
+var SPICE_MSG_DISPLAY_DRAW_STROKE = 310;
+var SPICE_MSG_DISPLAY_DRAW_TEXT = 311;
+var SPICE_MSG_DISPLAY_DRAW_TRANSPARENT = 312;
+var SPICE_MSG_DISPLAY_DRAW_ALPHA_BLEND = 313;
+var SPICE_MSG_DISPLAY_SURFACE_CREATE = 314;
+var SPICE_MSG_DISPLAY_SURFACE_DESTROY = 315;
+
+var SPICE_MSGC_DISPLAY_INIT = 101;
+
+var SPICE_MSG_INPUTS_INIT = 101;
+var SPICE_MSG_INPUTS_KEY_MODIFIERS = 102;
+
+var SPICE_MSG_INPUTS_MOUSE_MOTION_ACK = 111;
+
+var SPICE_MSGC_INPUTS_KEY_DOWN = 101;
+var SPICE_MSGC_INPUTS_KEY_UP = 102;
+var SPICE_MSGC_INPUTS_KEY_MODIFIERS = 103;
+
+var SPICE_MSGC_INPUTS_MOUSE_MOTION = 111;
+var SPICE_MSGC_INPUTS_MOUSE_POSITION = 112;
+var SPICE_MSGC_INPUTS_MOUSE_PRESS = 113;
+var SPICE_MSGC_INPUTS_MOUSE_RELEASE = 114;
+
+var SPICE_MSG_CURSOR_INIT = 101;
+var SPICE_MSG_CURSOR_RESET = 102;
+var SPICE_MSG_CURSOR_SET = 103;
+var SPICE_MSG_CURSOR_MOVE = 104;
+var SPICE_MSG_CURSOR_HIDE = 105;
+var SPICE_MSG_CURSOR_TRAIL = 106;
+var SPICE_MSG_CURSOR_INVAL_ONE = 107;
+var SPICE_MSG_CURSOR_INVAL_ALL = 108;
+
+
+var SPICE_CHANNEL_MAIN = 1;
+var SPICE_CHANNEL_DISPLAY = 2;
+var SPICE_CHANNEL_INPUTS = 3;
+var SPICE_CHANNEL_CURSOR = 4;
+var SPICE_CHANNEL_PLAYBACK = 5;
+var SPICE_CHANNEL_RECORD = 6;
+var SPICE_CHANNEL_TUNNEL = 7;
+var SPICE_CHANNEL_SMARTCARD = 8;
+var SPICE_CHANNEL_USBREDIR = 9;
+
+var SPICE_SURFACE_FLAGS_PRIMARY = (1 << 0);
+
+var SPICE_NOTIFY_SEVERITY_INFO = 0;
+var SPICE_NOTIFY_SEVERITY_WARN = 1;
+var SPICE_NOTIFY_SEVERITY_ERROR = 2;
+
+var SPICE_MOUSE_MODE_SERVER = (1 << 0),
+ SPICE_MOUSE_MODE_CLIENT = (1 << 1),
+ SPICE_MOUSE_MODE_MASK = 0x3;
+
+var SPICE_CLIP_TYPE_NONE = 0;
+var SPICE_CLIP_TYPE_RECTS = 1;
+
+var SPICE_IMAGE_TYPE_BITMAP = 0;
+var SPICE_IMAGE_TYPE_QUIC = 1;
+var SPICE_IMAGE_TYPE_RESERVED = 2;
+var SPICE_IMAGE_TYPE_LZ_PLT = 100;
+var SPICE_IMAGE_TYPE_LZ_RGB = 101;
+var SPICE_IMAGE_TYPE_GLZ_RGB = 102;
+var SPICE_IMAGE_TYPE_FROM_CACHE = 103;
+var SPICE_IMAGE_TYPE_SURFACE = 104;
+var SPICE_IMAGE_TYPE_JPEG = 105;
+var SPICE_IMAGE_TYPE_FROM_CACHE_LOSSLESS = 106;
+var SPICE_IMAGE_TYPE_ZLIB_GLZ_RGB = 107;
+var SPICE_IMAGE_TYPE_JPEG_ALPHA = 108;
+
+var SPICE_IMAGE_FLAGS_CACHE_ME = (1 << 0),
+ SPICE_IMAGE_FLAGS_HIGH_BITS_SET = (1 << 1),
+ SPICE_IMAGE_FLAGS_CACHE_REPLACE_ME = (1 << 2);
+
+var SPICE_BITMAP_FLAGS_PAL_CACHE_ME = (1 << 0),
+ SPICE_BITMAP_FLAGS_PAL_FROM_CACHE = (1 << 1),
+ SPICE_BITMAP_FLAGS_TOP_DOWN = (1 << 2),
+ SPICE_BITMAP_FLAGS_MASK = 0x7;
+
+var SPICE_BITMAP_FMT_INVALID = 0,
+ SPICE_BITMAP_FMT_1BIT_LE = 1,
+ SPICE_BITMAP_FMT_1BIT_BE = 2,
+ SPICE_BITMAP_FMT_4BIT_LE = 3,
+ SPICE_BITMAP_FMT_4BIT_BE = 4,
+ SPICE_BITMAP_FMT_8BIT = 5,
+ SPICE_BITMAP_FMT_16BIT = 6,
+ SPICE_BITMAP_FMT_24BIT = 7,
+ SPICE_BITMAP_FMT_32BIT = 8,
+ SPICE_BITMAP_FMT_RGBA = 9;
+
+
+var SPICE_CURSOR_FLAGS_NONE = (1 << 0),
+ SPICE_CURSOR_FLAGS_CACHE_ME = (1 << 1),
+ SPICE_CURSOR_FLAGS_FROM_CACHE = (1 << 2),
+ SPICE_CURSOR_FLAGS_MASK = 0x7;
+
+var SPICE_MOUSE_BUTTON_MASK_LEFT = (1 << 0),
+ SPICE_MOUSE_BUTTON_MASK_MIDDLE = (1 << 1),
+ SPICE_MOUSE_BUTTON_MASK_RIGHT = (1 << 2),
+ SPICE_MOUSE_BUTTON_MASK_MASK = 0x7;
+
+var SPICE_MOUSE_BUTTON_INVALID = 0;
+var SPICE_MOUSE_BUTTON_LEFT = 1;
+var SPICE_MOUSE_BUTTON_MIDDLE = 2;
+var SPICE_MOUSE_BUTTON_RIGHT = 3;
+var SPICE_MOUSE_BUTTON_UP = 4;
+var SPICE_MOUSE_BUTTON_DOWN = 5;
+
+var SPICE_BRUSH_TYPE_NONE = 0,
+ SPICE_BRUSH_TYPE_SOLID = 1,
+ SPICE_BRUSH_TYPE_PATTERN = 2;
+
+var SPICE_SURFACE_FMT_INVALID = 0,
+ SPICE_SURFACE_FMT_1_A = 1,
+ SPICE_SURFACE_FMT_8_A = 8,
+ SPICE_SURFACE_FMT_16_555 = 16,
+ SPICE_SURFACE_FMT_32_xRGB = 32,
+ SPICE_SURFACE_FMT_16_565 = 80,
+ SPICE_SURFACE_FMT_32_ARGB = 96;
+
+var SPICE_ROPD_INVERS_SRC = (1 << 0),
+ SPICE_ROPD_INVERS_BRUSH = (1 << 1),
+ SPICE_ROPD_INVERS_DEST = (1 << 2),
+ SPICE_ROPD_OP_PUT = (1 << 3),
+ SPICE_ROPD_OP_OR = (1 << 4),
+ SPICE_ROPD_OP_AND = (1 << 5),
+ SPICE_ROPD_OP_XOR = (1 << 6),
+ SPICE_ROPD_OP_BLACKNESS = (1 << 7),
+ SPICE_ROPD_OP_WHITENESS = (1 << 8),
+ SPICE_ROPD_OP_INVERS = (1 << 9),
+ SPICE_ROPD_INVERS_RES = (1 << 10),
+ SPICE_ROPD_MASK = 0x7ff;
+
+var LZ_IMAGE_TYPE_INVALID = 0,
+ LZ_IMAGE_TYPE_PLT1_LE = 1,
+ LZ_IMAGE_TYPE_PLT1_BE = 2, // PLT stands for palette
+ LZ_IMAGE_TYPE_PLT4_LE = 3,
+ LZ_IMAGE_TYPE_PLT4_BE = 4,
+ LZ_IMAGE_TYPE_PLT8 = 5,
+ LZ_IMAGE_TYPE_RGB16 = 6,
+ LZ_IMAGE_TYPE_RGB24 = 7,
+ LZ_IMAGE_TYPE_RGB32 = 8,
+ LZ_IMAGE_TYPE_RGBA = 9,
+ LZ_IMAGE_TYPE_XXXA = 10;
+
+
+var QUIC_IMAGE_TYPE_INVALID = 0,
+ QUIC_IMAGE_TYPE_GRAY = 1,
+ QUIC_IMAGE_TYPE_RGB16 = 2,
+ QUIC_IMAGE_TYPE_RGB24 = 3,
+ QUIC_IMAGE_TYPE_RGB32 = 4,
+ QUIC_IMAGE_TYPE_RGBA = 5;
+
+var SPICE_INPUT_MOTION_ACK_BUNCH = 4;
+
+
+var SPICE_CURSOR_TYPE_ALPHA = 0,
+ SPICE_CURSOR_TYPE_MONO = 1,
+ SPICE_CURSOR_TYPE_COLOR4 = 2,
+ SPICE_CURSOR_TYPE_COLOR8 = 3,
+ SPICE_CURSOR_TYPE_COLOR16 = 4,
+ SPICE_CURSOR_TYPE_COLOR24 = 5,
+ SPICE_CURSOR_TYPE_COLOR32 = 6;
+
+var SPICE_VIDEO_CODEC_TYPE_MJPEG = 1;
diff --git a/ui/js/spice/inputs.js b/ui/js/spice/inputs.js
new file mode 100644
index 0000000..4d3b28f
--- /dev/null
+++ b/ui/js/spice/inputs.js
@@ -0,0 +1,251 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*----------------------------------------------------------------------------
+ ** Modifier Keystates
+ ** These need to be tracked because focus in and out can get the keyboard
+ ** out of sync.
+ **------------------------------------------------------------------------*/
+var Shift_state = -1;
+var Ctrl_state = -1;
+var Alt_state = -1;
+var Meta_state = -1;
+
+/*----------------------------------------------------------------------------
+** SpiceInputsConn
+** Drive the Spice Inputs channel (e.g. mouse + keyboard)
+**--------------------------------------------------------------------------*/
+function SpiceInputsConn()
+{
+ SpiceConn.apply(this, arguments);
+
+ this.mousex = undefined;
+ this.mousey = undefined;
+ this.button_state = 0;
+ this.waiting_for_ack = 0;
+}
+
+SpiceInputsConn.prototype = Object.create(SpiceConn.prototype);
+SpiceInputsConn.prototype.process_channel_message = function(msg)
+{
+ if (msg.type == SPICE_MSG_INPUTS_INIT)
+ {
+ var inputs_init = new SpiceMsgInputsInit(msg.data);
+ this.keyboard_modifiers = inputs_init.keyboard_modifiers;
+ DEBUG > 1 && console.log("MsgInputsInit - modifier " + this.keyboard_modifiers);
+ // FIXME - We don't do anything with the keyboard modifiers...
+ return true;
+ }
+ if (msg.type == SPICE_MSG_INPUTS_KEY_MODIFIERS)
+ {
+ var key = new SpiceMsgInputsKeyModifiers(msg.data);
+ this.keyboard_modifiers = key.keyboard_modifiers;
+ DEBUG > 1 && console.log("MsgInputsKeyModifiers - modifier " + this.keyboard_modifiers);
+ // FIXME - We don't do anything with the keyboard modifiers...
+ return true;
+ }
+ if (msg.type == SPICE_MSG_INPUTS_MOUSE_MOTION_ACK)
+ {
+ DEBUG > 1 && console.log("mouse motion ack");
+ this.waiting_for_ack -= SPICE_INPUT_MOTION_ACK_BUNCH;
+ return true;
+ }
+ return false;
+}
+
+
+
+function handle_mousemove(e)
+{
+ var msg = new SpiceMiniData();
+ var move;
+ if (this.sc.mouse_mode == SPICE_MOUSE_MODE_CLIENT)
+ {
+ move = new SpiceMsgcMousePosition(this.sc, e)
+ msg.build_msg(SPICE_MSGC_INPUTS_MOUSE_POSITION, move);
+ }
+ else
+ {
+ move = new SpiceMsgcMouseMotion(this.sc, e)
+ msg.build_msg(SPICE_MSGC_INPUTS_MOUSE_MOTION, move);
+ }
+ if (this.sc && this.sc.inputs && this.sc.inputs.state === "ready")
+ {
+ if (this.sc.inputs.waiting_for_ack < (2 * SPICE_INPUT_MOTION_ACK_BUNCH))
+ {
+ this.sc.inputs.send_msg(msg);
+ this.sc.inputs.waiting_for_ack++;
+ }
+ else
+ {
+ DEBUG > 0 && this.sc.log_info("Discarding mouse motion");
+ }
+ }
+}
+
+function handle_mousedown(e)
+{
+ var press = new SpiceMsgcMousePress(this.sc, e)
+ var msg = new SpiceMiniData();
+ msg.build_msg(SPICE_MSGC_INPUTS_MOUSE_PRESS, press);
+ if (this.sc && this.sc.inputs && this.sc.inputs.state === "ready")
+ this.sc.inputs.send_msg(msg);
+
+ e.preventDefault();
+}
+
+function handle_contextmenu(e)
+{
+ e.preventDefault();
+ return false;
+}
+
+function handle_mouseup(e)
+{
+ var release = new SpiceMsgcMouseRelease(this.sc, e)
+ var msg = new SpiceMiniData();
+ msg.build_msg(SPICE_MSGC_INPUTS_MOUSE_RELEASE, release);
+ if (this.sc && this.sc.inputs && this.sc.inputs.state === "ready")
+ this.sc.inputs.send_msg(msg);
+
+ e.preventDefault();
+}
+
+function handle_mousewheel(e)
+{
+ var press = new SpiceMsgcMousePress;
+ var release = new SpiceMsgcMouseRelease;
+ if (e.wheelDelta > 0)
+ press.button = release.button = SPICE_MOUSE_BUTTON_UP;
+ else
+ press.button = release.button = SPICE_MOUSE_BUTTON_DOWN;
+ press.buttons_state = 0;
+ release.buttons_state = 0;
+
+ var msg = new SpiceMiniData();
+ msg.build_msg(SPICE_MSGC_INPUTS_MOUSE_PRESS, press);
+ if (this.sc && this.sc.inputs && this.sc.inputs.state === "ready")
+ this.sc.inputs.send_msg(msg);
+
+ msg.build_msg(SPICE_MSGC_INPUTS_MOUSE_RELEASE, release);
+ if (this.sc && this.sc.inputs && this.sc.inputs.state === "ready")
+ this.sc.inputs.send_msg(msg);
+
+ e.preventDefault();
+}
+
+function handle_keydown(e)
+{
+ var key = new SpiceMsgcKeyDown(e)
+ var msg = new SpiceMiniData();
+ check_and_update_modifiers(e, key.code, this.sc);
+ msg.build_msg(SPICE_MSGC_INPUTS_KEY_DOWN, key);
+ if (this.sc && this.sc.inputs && this.sc.inputs.state === "ready")
+ this.sc.inputs.send_msg(msg);
+
+ e.preventDefault();
+}
+
+function handle_keyup(e)
+{
+ var key = new SpiceMsgcKeyUp(e)
+ var msg = new SpiceMiniData();
+ check_and_update_modifiers(e, key.code, this.sc);
+ msg.build_msg(SPICE_MSGC_INPUTS_KEY_UP, key);
+ if (this.sc && this.sc.inputs && this.sc.inputs.state === "ready")
+ this.sc.inputs.send_msg(msg);
+
+ e.preventDefault();
+}
+
+function update_modifier(state, code, sc)
+{
+ var msg = new SpiceMiniData();
+ if (!state)
+ {
+ var key = new SpiceMsgcKeyUp()
+ key.code =(0x80|code);
+ msg.build_msg(SPICE_MSGC_INPUTS_KEY_UP, key);
+ }
+ else
+ {
+ var key = new SpiceMsgcKeyDown()
+ key.code = code;
+ msg.build_msg(SPICE_MSGC_INPUTS_KEY_DOWN, key);
+ }
+
+ sc.inputs.send_msg(msg);
+}
+
+function check_and_update_modifiers(e, code, sc)
+{
+ if (Shift_state === -1)
+ {
+ Shift_state = e.shiftKey;
+ Ctrl_state = e.ctrlKey;
+ Alt_state = e.altKey;
+ Meta_state = e.metaKey;
+ }
+
+ if (code === KEY_ShiftL)
+ Shift_state = true;
+ else if (code === KEY_Alt)
+ Alt_state = true;
+ else if (code === KEY_LCtrl)
+ Ctrl_state = true;
+ else if (code === 0xE0B5)
+ Meta_state = true;
+ else if (code === (0x80|KEY_ShiftL))
+ Shift_state = false;
+ else if (code === (0x80|KEY_Alt))
+ Alt_state = false;
+ else if (code === (0x80|KEY_LCtrl))
+ Ctrl_state = false;
+ else if (code === (0x80|0xE0B5))
+ Meta_state = false;
+
+ if (sc && sc.inputs && sc.inputs.state === "ready")
+ {
+ if (Shift_state != e.shiftKey)
+ {
+ console.log("Shift state out of sync");
+ update_modifier(e.shiftKey, KEY_ShiftL, sc);
+ Shift_state = e.shiftKey;
+ }
+ if (Alt_state != e.altKey)
+ {
+ console.log("Alt state out of sync");
+ update_modifier(e.altKey, KEY_Alt, sc);
+ Alt_state = e.altKey;
+ }
+ if (Ctrl_state != e.ctrlKey)
+ {
+ console.log("Ctrl state out of sync");
+ update_modifier(e.ctrlKey, KEY_LCtrl, sc);
+ Ctrl_state = e.ctrlKey;
+ }
+ if (Meta_state != e.metaKey)
+ {
+ console.log("Meta state out of sync");
+ update_modifier(e.metaKey, 0xE0B5, sc);
+ Meta_state = e.metaKey;
+ }
+ }
+}
diff --git a/ui/js/spice/jsbn.js b/ui/js/spice/jsbn.js
new file mode 100644
index 0000000..d88ec54
--- /dev/null
+++ b/ui/js/spice/jsbn.js
@@ -0,0 +1,589 @@
+// Downloaded from http://www-cs-students.stanford.edu/~tjw/jsbn/ by Jeremy White on 6/1/2012
+
+/*
+ * Copyright (c) 2003-2005 Tom Wu
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
+ * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * In addition, the following condition applies:
+ *
+ * All redistributions must retain an intact copy of this copyright notice
+ * and disclaimer.
+ */
+
+
+// Basic JavaScript BN library - subset useful for RSA encryption.
+
+// Bits per digit
+var dbits;
+
+// JavaScript engine analysis
+var canary = 0xdeadbeefcafe;
+var j_lm = ((canary&0xffffff)==0xefcafe);
+
+// (public) Constructor
+function BigInteger(a,b,c) {
+ if(a != null)
+ if("number" == typeof a) this.fromNumber(a,b,c);
+ else if(b == null && "string" != typeof a) this.fromString(a,256);
+ else this.fromString(a,b);
+}
+
+// return new, unset BigInteger
+function nbi() { return new BigInteger(null); }
+
+// am: Compute w_j += (x*this_i), propagate carries,
+// c is initial carry, returns final carry.
+// c < 3*dvalue, x < 2*dvalue, this_i < dvalue
+// We need to select the fastest one that works in this environment.
+
+// am1: use a single mult and divide to get the high bits,
+// max digit bits should be 26 because
+// max internal value = 2*dvalue^2-2*dvalue (< 2^53)
+function am1(i,x,w,j,c,n) {
+ while(--n >= 0) {
+ var v = x*this[i++]+w[j]+c;
+ c = Math.floor(v/0x4000000);
+ w[j++] = v&0x3ffffff;
+ }
+ return c;
+}
+// am2 avoids a big mult-and-extract completely.
+// Max digit bits should be <= 30 because we do bitwise ops
+// on values up to 2*hdvalue^2-hdvalue-1 (< 2^31)
+function am2(i,x,w,j,c,n) {
+ var xl = x&0x7fff, xh = x>>15;
+ while(--n >= 0) {
+ var l = this[i]&0x7fff;
+ var h = this[i++]>>15;
+ var m = xh*l+h*xl;
+ l = xl*l+((m&0x7fff)<<15)+w[j]+(c&0x3fffffff);
+ c = (l>>>30)+(m>>>15)+xh*h+(c>>>30);
+ w[j++] = l&0x3fffffff;
+ }
+ return c;
+}
+// Alternately, set max digit bits to 28 since some
+// browsers slow down when dealing with 32-bit numbers.
+function am3(i,x,w,j,c,n) {
+ var xl = x&0x3fff, xh = x>>14;
+ while(--n >= 0) {
+ var l = this[i]&0x3fff;
+ var h = this[i++]>>14;
+ var m = xh*l+h*xl;
+ l = xl*l+((m&0x3fff)<<14)+w[j]+c;
+ c = (l>>28)+(m>>14)+xh*h;
+ w[j++] = l&0xfffffff;
+ }
+ return c;
+}
+if(j_lm && (navigator.appName == "Microsoft Internet Explorer")) {
+ BigInteger.prototype.am = am2;
+ dbits = 30;
+}
+else if(j_lm && (navigator.appName != "Netscape")) {
+ BigInteger.prototype.am = am1;
+ dbits = 26;
+}
+else { // Mozilla/Netscape seems to prefer am3
+ BigInteger.prototype.am = am3;
+ dbits = 28;
+}
+
+BigInteger.prototype.DB = dbits;
+BigInteger.prototype.DM = ((1<<dbits)-1);
+BigInteger.prototype.DV = (1<<dbits);
+
+var BI_FP = 52;
+BigInteger.prototype.FV = Math.pow(2,BI_FP);
+BigInteger.prototype.F1 = BI_FP-dbits;
+BigInteger.prototype.F2 = 2*dbits-BI_FP;
+
+// Digit conversions
+var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz";
+var BI_RC = new Array();
+var rr,vv;
+rr = "0".charCodeAt(0);
+for(vv = 0; vv <= 9; ++vv) BI_RC[rr++] = vv;
+rr = "a".charCodeAt(0);
+for(vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv;
+rr = "A".charCodeAt(0);
+for(vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv;
+
+function int2char(n) { return BI_RM.charAt(n); }
+function intAt(s,i) {
+ var c = BI_RC[s.charCodeAt(i)];
+ return (c==null)?-1:c;
+}
+
+// (protected) copy this to r
+function bnpCopyTo(r) {
+ for(var i = this.t-1; i >= 0; --i) r[i] = this[i];
+ r.t = this.t;
+ r.s = this.s;
+}
+
+// (protected) set from integer value x, -DV <= x < DV
+function bnpFromInt(x) {
+ this.t = 1;
+ this.s = (x<0)?-1:0;
+ if(x > 0) this[0] = x;
+ else if(x < -1) this[0] = x+DV;
+ else this.t = 0;
+}
+
+// return bigint initialized to value
+function nbv(i) { var r = nbi(); r.fromInt(i); return r; }
+
+// (protected) set from string and radix
+function bnpFromString(s,b) {
+ var k;
+ if(b == 16) k = 4;
+ else if(b == 8) k = 3;
+ else if(b == 256) k = 8; // byte array
+ else if(b == 2) k = 1;
+ else if(b == 32) k = 5;
+ else if(b == 4) k = 2;
+ else { this.fromRadix(s,b); return; }
+ this.t = 0;
+ this.s = 0;
+ var i = s.length, mi = false, sh = 0;
+ while(--i >= 0) {
+ var x = (k==8)?s[i]&0xff:intAt(s,i);
+ if(x < 0) {
+ if(s.charAt(i) == "-") mi = true;
+ continue;
+ }
+ mi = false;
+ if(sh == 0)
+ this[this.t++] = x;
+ else if(sh+k > this.DB) {
+ this[this.t-1] |= (x&((1<<(this.DB-sh))-1))<<sh;
+ this[this.t++] = (x>>(this.DB-sh));
+ }
+ else
+ this[this.t-1] |= x<<sh;
+ sh += k;
+ if(sh >= this.DB) sh -= this.DB;
+ }
+ if(k == 8 && (s[0]&0x80) != 0) {
+ this.s = -1;
+ if(sh > 0) this[this.t-1] |= ((1<<(this.DB-sh))-1)<<sh;
+ }
+ this.clamp();
+ if(mi) BigInteger.ZERO.subTo(this,this);
+}
+
+// (protected) clamp off excess high words
+function bnpClamp() {
+ var c = this.s&this.DM;
+ while(this.t > 0 && this[this.t-1] == c) --this.t;
+}
+
+// (public) return string representation in given radix
+function bnToString(b) {
+ if(this.s < 0) return "-"+this.negate().toString(b);
+ var k;
+ if(b == 16) k = 4;
+ else if(b == 8) k = 3;
+ else if(b == 2) k = 1;
+ else if(b == 32) k = 5;
+ else if(b == 4) k = 2;
+ else return this.toRadix(b);
+ var km = (1<<k)-1, d, m = false, r = "", i = this.t;
+ var p = this.DB-(i*this.DB)%k;
+ if(i-- > 0) {
+ if(p < this.DB && (d = this[i]>>p) > 0) { m = true; r = int2char(d); }
+ while(i >= 0) {
+ if(p < k) {
+ d = (this[i]&((1<<p)-1))<<(k-p);
+ d |= this[--i]>>(p+=this.DB-k);
+ }
+ else {
+ d = (this[i]>>(p-=k))&km;
+ if(p <= 0) { p += this.DB; --i; }
+ }
+ if(d > 0) m = true;
+ if(m) r += int2char(d);
+ }
+ }
+ return m?r:"0";
+}
+
+// (public) -this
+function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this,r); return r; }
+
+// (public) |this|
+function bnAbs() { return (this.s<0)?this.negate():this; }
+
+// (public) return + if this > a, - if this < a, 0 if equal
+function bnCompareTo(a) {
+ var r = this.s-a.s;
+ if(r != 0) return r;
+ var i = this.t;
+ r = i-a.t;
+ if(r != 0) return r;
+ while(--i >= 0) if((r=this[i]-a[i]) != 0) return r;
+ return 0;
+}
+
+// returns bit length of the integer x
+function nbits(x) {
+ var r = 1, t;
+ if((t=x>>>16) != 0) { x = t; r += 16; }
+ if((t=x>>8) != 0) { x = t; r += 8; }
+ if((t=x>>4) != 0) { x = t; r += 4; }
+ if((t=x>>2) != 0) { x = t; r += 2; }
+ if((t=x>>1) != 0) { x = t; r += 1; }
+ return r;
+}
+
+// (public) return the number of bits in "this"
+function bnBitLength() {
+ if(this.t <= 0) return 0;
+ return this.DB*(this.t-1)+nbits(this[this.t-1]^(this.s&this.DM));
+}
+
+// (protected) r = this << n*DB
+function bnpDLShiftTo(n,r) {
+ var i;
+ for(i = this.t-1; i >= 0; --i) r[i+n] = this[i];
+ for(i = n-1; i >= 0; --i) r[i] = 0;
+ r.t = this.t+n;
+ r.s = this.s;
+}
+
+// (protected) r = this >> n*DB
+function bnpDRShiftTo(n,r) {
+ for(var i = n; i < this.t; ++i) r[i-n] = this[i];
+ r.t = Math.max(this.t-n,0);
+ r.s = this.s;
+}
+
+// (protected) r = this << n
+function bnpLShiftTo(n,r) {
+ var bs = n%this.DB;
+ var cbs = this.DB-bs;
+ var bm = (1<<cbs)-1;
+ var ds = Math.floor(n/this.DB), c = (this.s<<bs)&this.DM, i;
+ for(i = this.t-1; i >= 0; --i) {
+ r[i+ds+1] = (this[i]>>cbs)|c;
+ c = (this[i]&bm)<<bs;
+ }
+ for(i = ds-1; i >= 0; --i) r[i] = 0;
+ r[ds] = c;
+ r.t = this.t+ds+1;
+ r.s = this.s;
+ r.clamp();
+}
+
+// (protected) r = this >> n
+function bnpRShiftTo(n,r) {
+ r.s = this.s;
+ var ds = Math.floor(n/this.DB);
+ if(ds >= this.t) { r.t = 0; return; }
+ var bs = n%this.DB;
+ var cbs = this.DB-bs;
+ var bm = (1<<bs)-1;
+ r[0] = this[ds]>>bs;
+ for(var i = ds+1; i < this.t; ++i) {
+ r[i-ds-1] |= (this[i]&bm)<<cbs;
+ r[i-ds] = this[i]>>bs;
+ }
+ if(bs > 0) r[this.t-ds-1] |= (this.s&bm)<<cbs;
+ r.t = this.t-ds;
+ r.clamp();
+}
+
+// (protected) r = this - a
+function bnpSubTo(a,r) {
+ var i = 0, c = 0, m = Math.min(a.t,this.t);
+ while(i < m) {
+ c += this[i]-a[i];
+ r[i++] = c&this.DM;
+ c >>= this.DB;
+ }
+ if(a.t < this.t) {
+ c -= a.s;
+ while(i < this.t) {
+ c += this[i];
+ r[i++] = c&this.DM;
+ c >>= this.DB;
+ }
+ c += this.s;
+ }
+ else {
+ c += this.s;
+ while(i < a.t) {
+ c -= a[i];
+ r[i++] = c&this.DM;
+ c >>= this.DB;
+ }
+ c -= a.s;
+ }
+ r.s = (c<0)?-1:0;
+ if(c < -1) r[i++] = this.DV+c;
+ else if(c > 0) r[i++] = c;
+ r.t = i;
+ r.clamp();
+}
+
+// (protected) r = this * a, r != this,a (HAC 14.12)
+// "this" should be the larger one if appropriate.
+function bnpMultiplyTo(a,r) {
+ var x = this.abs(), y = a.abs();
+ var i = x.t;
+ r.t = i+y.t;
+ while(--i >= 0) r[i] = 0;
+ for(i = 0; i < y.t; ++i) r[i+x.t] = x.am(0,y[i],r,i,0,x.t);
+ r.s = 0;
+ r.clamp();
+ if(this.s != a.s) BigInteger.ZERO.subTo(r,r);
+}
+
+// (protected) r = this^2, r != this (HAC 14.16)
+function bnpSquareTo(r) {
+ var x = this.abs();
+ var i = r.t = 2*x.t;
+ while(--i >= 0) r[i] = 0;
+ for(i = 0; i < x.t-1; ++i) {
+ var c = x.am(i,x[i],r,2*i,0,1);
+ if((r[i+x.t]+=x.am(i+1,2*x[i],r,2*i+1,c,x.t-i-1)) >= x.DV) {
+ r[i+x.t] -= x.DV;
+ r[i+x.t+1] = 1;
+ }
+ }
+ if(r.t > 0) r[r.t-1] += x.am(i,x[i],r,2*i,0,1);
+ r.s = 0;
+ r.clamp();
+}
+
+// (protected) divide this by m, quotient and remainder to q, r (HAC 14.20)
+// r != q, this != m. q or r may be null.
+function bnpDivRemTo(m,q,r) {
+ var pm = m.abs();
+ if(pm.t <= 0) return;
+ var pt = this.abs();
+ if(pt.t < pm.t) {
+ if(q != null) q.fromInt(0);
+ if(r != null) this.copyTo(r);
+ return;
+ }
+ if(r == null) r = nbi();
+ var y = nbi(), ts = this.s, ms = m.s;
+ var nsh = this.DB-nbits(pm[pm.t-1]); // normalize modulus
+ if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); }
+ else { pm.copyTo(y); pt.copyTo(r); }
+ var ys = y.t;
+ var y0 = y[ys-1];
+ if(y0 == 0) return;
+ var yt = y0*(1<<this.F1)+((ys>1)?y[ys-2]>>this.F2:0);
+ var d1 = this.FV/yt, d2 = (1<<this.F1)/yt, e = 1<<this.F2;
+ var i = r.t, j = i-ys, t = (q==null)?nbi():q;
+ y.dlShiftTo(j,t);
+ if(r.compareTo(t) >= 0) {
+ r[r.t++] = 1;
+ r.subTo(t,r);
+ }
+ BigInteger.ONE.dlShiftTo(ys,t);
+ t.subTo(y,y); // "negative" y so we can replace sub with am later
+ while(y.t < ys) y[y.t++] = 0;
+ while(--j >= 0) {
+ // Estimate quotient digit
+ var qd = (r[--i]==y0)?this.DM:Math.floor(r[i]*d1+(r[i-1]+e)*d2);
+ if((r[i]+=y.am(0,qd,r,j,0,ys)) < qd) { // Try it out
+ y.dlShiftTo(j,t);
+ r.subTo(t,r);
+ while(r[i] < --qd) r.subTo(t,r);
+ }
+ }
+ if(q != null) {
+ r.drShiftTo(ys,q);
+ if(ts != ms) BigInteger.ZERO.subTo(q,q);
+ }
+ r.t = ys;
+ r.clamp();
+ if(nsh > 0) r.rShiftTo(nsh,r); // Denormalize remainder
+ if(ts < 0) BigInteger.ZERO.subTo(r,r);
+}
+
+// (public) this mod a
+function bnMod(a) {
+ var r = nbi();
+ this.abs().divRemTo(a,null,r);
+ if(this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r,r);
+ return r;
+}
+
+// Modular reduction using "classic" algorithm
+function Classic(m) { this.m = m; }
+function cConvert(x) {
+ if(x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m);
+ else return x;
+}
+function cRevert(x) { return x; }
+function cReduce(x) { x.divRemTo(this.m,null,x); }
+function cMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
+function cSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
+
+Classic.prototype.convert = cConvert;
+Classic.prototype.revert = cRevert;
+Classic.prototype.reduce = cReduce;
+Classic.prototype.mulTo = cMulTo;
+Classic.prototype.sqrTo = cSqrTo;
+
+// (protected) return "-1/this % 2^DB"; useful for Mont. reduction
+// justification:
+// xy == 1 (mod m)
+// xy = 1+km
+// xy(2-xy) = (1+km)(1-km)
+// x[y(2-xy)] = 1-k^2m^2
+// x[y(2-xy)] == 1 (mod m^2)
+// if y is 1/x mod m, then y(2-xy) is 1/x mod m^2
+// should reduce x and y(2-xy) by m^2 at each step to keep size bounded.
+// JS multiply "overflows" differently from C/C++, so care is needed here.
+function bnpInvDigit() {
+ if(this.t < 1) return 0;
+ var x = this[0];
+ if((x&1) == 0) return 0;
+ var y = x&3; // y == 1/x mod 2^2
+ y = (y*(2-(x&0xf)*y))&0xf; // y == 1/x mod 2^4
+ y = (y*(2-(x&0xff)*y))&0xff; // y == 1/x mod 2^8
+ y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff; // y == 1/x mod 2^16
+ // last step - calculate inverse mod DV directly;
+ // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints
+ y = (y*(2-x*y%this.DV))%this.DV; // y == 1/x mod 2^dbits
+ // we really want the negative inverse, and -DV < y < DV
+ return (y>0)?this.DV-y:-y;
+}
+
+// Montgomery reduction
+function Montgomery(m) {
+ this.m = m;
+ this.mp = m.invDigit();
+ this.mpl = this.mp&0x7fff;
+ this.mph = this.mp>>15;
+ this.um = (1<<(m.DB-15))-1;
+ this.mt2 = 2*m.t;
+}
+
+// xR mod m
+function montConvert(x) {
+ var r = nbi();
+ x.abs().dlShiftTo(this.m.t,r);
+ r.divRemTo(this.m,null,r);
+ if(x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r,r);
+ return r;
+}
+
+// x/R mod m
+function montRevert(x) {
+ var r = nbi();
+ x.copyTo(r);
+ this.reduce(r);
+ return r;
+}
+
+// x = x/R mod m (HAC 14.32)
+function montReduce(x) {
+ while(x.t <= this.mt2) // pad x so am has enough room later
+ x[x.t++] = 0;
+ for(var i = 0; i < this.m.t; ++i) {
+ // faster way of calculating u0 = x[i]*mp mod DV
+ var j = x[i]&0x7fff;
+ var u0 = (j*this.mpl+(((j*this.mph+(x[i]>>15)*this.mpl)&this.um)<<15))&x.DM;
+ // use am to combine the multiply-shift-add into one call
+ j = i+this.m.t;
+ x[j] += this.m.am(0,u0,x,i,0,this.m.t);
+ // propagate carry
+ while(x[j] >= x.DV) { x[j] -= x.DV; x[++j]++; }
+ }
+ x.clamp();
+ x.drShiftTo(this.m.t,x);
+ if(x.compareTo(this.m) >= 0) x.subTo(this.m,x);
+}
+
+// r = "x^2/R mod m"; x != r
+function montSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
+
+// r = "xy/R mod m"; x,y != r
+function montMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
+
+Montgomery.prototype.convert = montConvert;
+Montgomery.prototype.revert = montRevert;
+Montgomery.prototype.reduce = montReduce;
+Montgomery.prototype.mulTo = montMulTo;
+Montgomery.prototype.sqrTo = montSqrTo;
+
+// (protected) true iff this is even
+function bnpIsEven() { return ((this.t>0)?(this[0]&1):this.s) == 0; }
+
+// (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79)
+function bnpExp(e,z) {
+ if(e > 0xffffffff || e < 1) return BigInteger.ONE;
+ var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1;
+ g.copyTo(r);
+ while(--i >= 0) {
+ z.sqrTo(r,r2);
+ if((e&(1<<i)) > 0) z.mulTo(r2,g,r);
+ else { var t = r; r = r2; r2 = t; }
+ }
+ return z.revert(r);
+}
+
+// (public) this^e % m, 0 <= e < 2^32
+function bnModPowInt(e,m) {
+ var z;
+ if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m);
+ return this.exp(e,z);
+}
+
+// protected
+BigInteger.prototype.copyTo = bnpCopyTo;
+BigInteger.prototype.fromInt = bnpFromInt;
+BigInteger.prototype.fromString = bnpFromString;
+BigInteger.prototype.clamp = bnpClamp;
+BigInteger.prototype.dlShiftTo = bnpDLShiftTo;
+BigInteger.prototype.drShiftTo = bnpDRShiftTo;
+BigInteger.prototype.lShiftTo = bnpLShiftTo;
+BigInteger.prototype.rShiftTo = bnpRShiftTo;
+BigInteger.prototype.subTo = bnpSubTo;
+BigInteger.prototype.multiplyTo = bnpMultiplyTo;
+BigInteger.prototype.squareTo = bnpSquareTo;
+BigInteger.prototype.divRemTo = bnpDivRemTo;
+BigInteger.prototype.invDigit = bnpInvDigit;
+BigInteger.prototype.isEven = bnpIsEven;
+BigInteger.prototype.exp = bnpExp;
+
+// public
+BigInteger.prototype.toString = bnToString;
+BigInteger.prototype.negate = bnNegate;
+BigInteger.prototype.abs = bnAbs;
+BigInteger.prototype.compareTo = bnCompareTo;
+BigInteger.prototype.bitLength = bnBitLength;
+BigInteger.prototype.mod = bnMod;
+BigInteger.prototype.modPowInt = bnModPowInt;
+
+// "constants"
+BigInteger.ZERO = nbv(0);
+BigInteger.ONE = nbv(1);
diff --git a/ui/js/spice/lz.js b/ui/js/spice/lz.js
new file mode 100644
index 0000000..4292eac
--- /dev/null
+++ b/ui/js/spice/lz.js
@@ -0,0 +1,166 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+/*----------------------------------------------------------------------------
+** lz.js
+** Functions for handling SPICE_IMAGE_TYPE_LZ_RGB
+** Adapted from lz.c .
+**--------------------------------------------------------------------------*/
+function lz_rgb32_decompress(in_buf, at, out_buf, type, default_alpha)
+{
+ var encoder = at;
+ var op = 0;
+ var ctrl;
+ var ctr = 0;
+
+ for (ctrl = in_buf[encoder++]; (op * 4) < out_buf.length; ctrl = in_buf[encoder++])
+ {
+ var ref = op;
+ var len = ctrl >> 5;
+ var ofs = (ctrl & 31) << 8;
+
+//if (type == LZ_IMAGE_TYPE_RGBA)
+//console.log(ctr++ + ": from " + (encoder + 28) + ", ctrl " + ctrl + ", len " + len + ", ofs " + ofs + ", op " + op);
+ if (ctrl >= 32) {
+
+ var code;
+ len--;
+
+ if (len == 7 - 1) {
+ do {
+ code = in_buf[encoder++];
+ len += code;
+ } while (code == 255);
+ }
+ code = in_buf[encoder++];
+ ofs += code;
+
+
+ if (code == 255) {
+ if ((ofs - code) == (31 << 8)) {
+ ofs = in_buf[encoder++] << 8;
+ ofs += in_buf[encoder++];
+ ofs += 8191;
+ }
+ }
+ len += 1;
+ if (type == LZ_IMAGE_TYPE_RGBA)
+ len += 2;
+
+ ofs += 1;
+
+ ref -= ofs;
+ if (ref == (op - 1)) {
+ var b = ref;
+//if (type == LZ_IMAGE_TYPE_RGBA) console.log("alpha " + out_buf[(b*4)+3] + " dupped into pixel " + op + " through pixel " + (op + len));
+ for (; len; --len) {
+ if (type == LZ_IMAGE_TYPE_RGBA)
+ {
+ out_buf[(op*4) + 3] = out_buf[(b*4)+3];
+ }
+ else
+ {
+ for (i = 0; i < 4; i++)
+ out_buf[(op*4) + i] = out_buf[(b*4)+i];
+ }
+ op++;
+ }
+ } else {
+//if (type == LZ_IMAGE_TYPE_RGBA) console.log("alpha copied to pixel " + op + " through " + (op + len) + " from " + ref);
+ for (; len; --len) {
+ if (type == LZ_IMAGE_TYPE_RGBA)
+ {
+ out_buf[(op*4) + 3] = out_buf[(ref*4)+3];
+ }
+ else
+ {
+ for (i = 0; i < 4; i++)
+ out_buf[(op*4) + i] = out_buf[(ref*4)+i];
+ }
+ op++; ref++;
+ }
+ }
+ } else {
+ ctrl++;
+
+ if (type == LZ_IMAGE_TYPE_RGBA)
+ {
+//console.log("alpha " + in_buf[encoder] + " set into pixel " + op);
+ out_buf[(op*4) + 3] = in_buf[encoder++];
+ }
+ else
+ {
+ out_buf[(op*4) + 0] = in_buf[encoder + 2];
+ out_buf[(op*4) + 1] = in_buf[encoder + 1];
+ out_buf[(op*4) + 2] = in_buf[encoder + 0];
+ if (default_alpha)
+ out_buf[(op*4) + 3] = 255;
+ encoder += 3;
+ }
+ op++;
+
+
+ for (--ctrl; ctrl; ctrl--) {
+ if (type == LZ_IMAGE_TYPE_RGBA)
+ {
+//console.log("alpha " + in_buf[encoder] + " set into pixel " + op);
+ out_buf[(op*4) + 3] = in_buf[encoder++];
+ }
+ else
+ {
+ out_buf[(op*4) + 0] = in_buf[encoder + 2];
+ out_buf[(op*4) + 1] = in_buf[encoder + 1];
+ out_buf[(op*4) + 2] = in_buf[encoder + 0];
+ if (default_alpha)
+ out_buf[(op*4) + 3] = 255;
+ encoder += 3;
+ }
+ op++;
+ }
+ }
+
+ }
+ return encoder - 1;
+}
+
+function convert_spice_lz_to_web(context, lz_image)
+{
+ var at;
+ if (lz_image.type === LZ_IMAGE_TYPE_RGB32 || lz_image.type === LZ_IMAGE_TYPE_RGBA)
+ {
+ var u8 = new Uint8Array(lz_image.data);
+ var ret = context.createImageData(lz_image.width, lz_image.height);
+
+ at = lz_rgb32_decompress(u8, 0, ret.data, LZ_IMAGE_TYPE_RGB32, lz_image.type != LZ_IMAGE_TYPE_RGBA);
+ if (lz_image.type == LZ_IMAGE_TYPE_RGBA)
+ lz_rgb32_decompress(u8, at, ret.data, LZ_IMAGE_TYPE_RGBA, false);
+ }
+ else if (lz_image.type === LZ_IMAGE_TYPE_XXXA)
+ {
+ var u8 = new Uint8Array(lz_image.data);
+ var ret = context.createImageData(lz_image.width, lz_image.height);
+ lz_rgb32_decompress(u8, 0, ret.data, LZ_IMAGE_TYPE_RGBA, false);
+ }
+ else
+ return undefined;
+
+ return ret;
+}
diff --git a/ui/js/spice/main.js b/ui/js/spice/main.js
new file mode 100644
index 0000000..e7eb1ed
--- /dev/null
+++ b/ui/js/spice/main.js
@@ -0,0 +1,176 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*----------------------------------------------------------------------------
+** SpiceMainConn
+** This is the master Javascript class for establishing and
+** managing a connection to a Spice Server.
+**
+** Invocation: You must pass an object with properties as follows:
+** uri (required) Uri of a WebSocket listener that is
+** connected to a spice server.
+** password (required) Password to send to the spice server
+** message_id (optional) Identifier of an element in the DOM
+** where SpiceConn will write messages.
+** It will use classes spice-messages-x,
+** where x is one of info, warning, or error.
+** screen_id (optional) Identifier of an element in the DOM
+** where SpiceConn will create any new
+** client screens. This is the main UI.
+** dump_id (optional) If given, an element to use for
+** dumping every single image + canvas drawn.
+** Sometimes useful for debugging.
+** onerror (optional) If given, a function to receive async
+** errors. Note that you should also catch
+** errors for ones that occur inline
+**
+** Throws error if there are troubles. Requires a modern (by 2012 standards)
+** browser, including WebSocket and WebSocket.binaryType == arraybuffer
+**
+**--------------------------------------------------------------------------*/
+function SpiceMainConn()
+{
+ if (typeof WebSocket === "undefined")
+ throw new Error("WebSocket unavailable. You need to use a different browser.");
+
+ SpiceConn.apply(this, arguments);
+
+}
+
+SpiceMainConn.prototype = Object.create(SpiceConn.prototype);
+SpiceMainConn.prototype.process_channel_message = function(msg)
+{
+ if (msg.type == SPICE_MSG_MAIN_INIT)
+ {
+ this.log_info("Connected to " + this.ws.url);
+ this.main_init = new SpiceMsgMainInit(msg.data);
+ this.connection_id = this.main_init.session_id;
+
+ if (DEBUG > 0)
+ {
+ // FIXME - there is a lot here we don't handle; mouse modes, agent,
+ // ram_hint, multi_media_time
+ this.log_info("session id " + this.main_init.session_id +
+ " ; display_channels_hint " + this.main_init.display_channels_hint +
+ " ; supported_mouse_modes " + this.main_init.supported_mouse_modes +
+ " ; current_mouse_mode " + this.main_init.current_mouse_mode +
+ " ; agent_connected " + this.main_init.agent_connected +
+ " ; agent_tokens " + this.main_init.agent_tokens +
+ " ; multi_media_time " + this.main_init.multi_media_time +
+ " ; ram_hint " + this.main_init.ram_hint);
+ }
+
+ this.mouse_mode = this.main_init.current_mouse_mode;
+ if (this.main_init.current_mouse_mode != SPICE_MOUSE_MODE_CLIENT &&
+ (this.main_init.supported_mouse_modes & SPICE_MOUSE_MODE_CLIENT))
+ {
+ var mode_request = new SpiceMsgcMainMouseModeRequest(SPICE_MOUSE_MODE_CLIENT);
+ var mr = new SpiceMiniData();
+ mr.build_msg(SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST, mode_request);
+ this.send_msg(mr);
+ }
+
+ var attach = new SpiceMiniData;
+ attach.type = SPICE_MSGC_MAIN_ATTACH_CHANNELS;
+ attach.size = attach.buffer_size();
+ this.send_msg(attach);
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_MAIN_MOUSE_MODE)
+ {
+ var mode = new SpiceMsgMainMouseMode(msg.data);
+ DEBUG > 0 && this.log_info("Mouse supported modes " + mode.supported_modes + "; current " + mode.current_mode);
+ this.mouse_mode = mode.current_mode;
+ if (this.inputs)
+ this.inputs.mouse_mode = mode.current_mode;
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_MAIN_CHANNELS_LIST)
+ {
+ var i;
+ var chans;
+ DEBUG > 0 && console.log("channels");
+ chans = new SpiceMsgChannels(msg.data);
+ for (i = 0; i < chans.channels.length; i++)
+ {
+ var conn = {
+ uri: this.ws.url,
+ parent: this,
+ connection_id : this.connection_id,
+ type : chans.channels[i].type,
+ chan_id : chans.channels[i].id
+ };
+ if (chans.channels[i].type == SPICE_CHANNEL_DISPLAY)
+ this.display = new SpiceDisplayConn(conn);
+ else if (chans.channels[i].type == SPICE_CHANNEL_INPUTS)
+ {
+ this.inputs = new SpiceInputsConn(conn);
+ this.inputs.mouse_mode = this.mouse_mode;
+ }
+ else if (chans.channels[i].type == SPICE_CHANNEL_CURSOR)
+ this.cursor = new SpiceCursorConn(conn);
+ else
+ {
+ this.log_err("Channel type " + chans.channels[i].type + " unknown.");
+ if (! ("extra_channels" in this))
+ this.extra_channels = [];
+ this.extra_channels[i] = new SpiceConn(conn);
+ }
+
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+SpiceMainConn.prototype.stop = function(msg)
+{
+ this.state = "closing";
+
+ if (this.inputs)
+ {
+ this.inputs.cleanup();
+ this.inputs = undefined;
+ }
+
+ if (this.cursor)
+ {
+ this.cursor.cleanup();
+ this.cursor = undefined;
+ }
+
+ if (this.display)
+ {
+ this.display.cleanup();
+ this.display.destroy_surfaces();
+ this.display = undefined;
+ }
+
+ this.cleanup();
+
+ if ("extra_channels" in this)
+ for (var e in this.extra_channels)
+ this.extra_channels[e].cleanup();
+ this.extra_channels = undefined;
+}
diff --git a/ui/js/spice/png.js b/ui/js/spice/png.js
new file mode 100644
index 0000000..6a26151
--- /dev/null
+++ b/ui/js/spice/png.js
@@ -0,0 +1,256 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*----------------------------------------------------------------------------
+** crc logic from rfc2083 ported to Javascript
+**--------------------------------------------------------------------------*/
+
+var rfc2083_crc_table = Array(256);
+var rfc2083_crc_table_computed = 0;
+/* Make the table for a fast CRC. */
+function rfc2083_make_crc_table()
+{
+ var c;
+ var n, k;
+ for (n = 0; n < 256; n++)
+ {
+ c = n;
+ for (k = 0; k < 8; k++)
+ {
+ if (c & 1)
+ c = ((0xedb88320 ^ (c >>> 1)) >>> 0) & 0xffffffff;
+ else
+ c = c >>> 1;
+ }
+ rfc2083_crc_table[n] = c;
+ }
+
+ rfc2083_crc_table_computed = 1;
+}
+
+/* Update a running CRC with the bytes buf[0..len-1]--the CRC
+ should be initialized to all 1's, and the transmitted value
+ is the 1's complement of the final running CRC (see the
+ crc() routine below)). */
+
+function rfc2083_update_crc(crc, u8buf, at, len)
+{
+ var c = crc;
+ var n;
+
+ if (!rfc2083_crc_table_computed)
+ rfc2083_make_crc_table();
+
+ for (n = 0; n < len; n++)
+ {
+ c = rfc2083_crc_table[(c ^ u8buf[at + n]) & 0xff] ^ (c >>> 8);
+ }
+
+ return c;
+}
+
+function rfc2083_crc(u8buf, at, len)
+{
+ return rfc2083_update_crc(0xffffffff, u8buf, at, len) ^ 0xffffffff;
+}
+
+function crc32(mb, at, len)
+{
+ var u8 = new Uint8Array(mb);
+ return rfc2083_crc(u8, at, len);
+}
+
+function PngIHDR(width, height)
+{
+ this.width = width;
+ this.height = height;
+ this.depth = 8;
+ this.type = 6;
+ this.compression = 0;
+ this.filter = 0;
+ this.interlace = 0;
+}
+
+PngIHDR.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var orig = at;
+ var dv = new SpiceDataView(a);
+ dv.setUint32(at, this.buffer_size() - 12); at += 4;
+ dv.setUint8(at, 'I'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'H'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'D'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'R'.charCodeAt(0)); at++;
+ dv.setUint32(at, this.width); at += 4;
+ dv.setUint32(at, this.height); at += 4;
+ dv.setUint8(at, this.depth); at++;
+ dv.setUint8(at, this.type); at++;
+ dv.setUint8(at, this.compression); at++;
+ dv.setUint8(at, this.filter); at++;
+ dv.setUint8(at, this.interlace); at++;
+ dv.setUint32(at, crc32(a, orig + 4, this.buffer_size() - 8)); at += 4;
+ return at;
+ },
+ buffer_size: function()
+ {
+ return 12 + 13;
+ }
+}
+
+
+function adler()
+{
+ this.s1 = 1;
+ this.s2 = 0;
+}
+
+adler.prototype.update = function(b)
+{
+ this.s1 += b;
+ this.s1 %= 65521;
+ this.s2 += this.s1;
+ this.s2 %= 65521;
+}
+
+function PngIDAT(width, height, bytes)
+{
+ if (bytes.byteLength > 65535)
+ {
+ throw new Error("Cannot handle more than 64K");
+ }
+ this.data = bytes;
+ this.width = width;
+ this.height = height;
+}
+
+PngIDAT.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var orig = at;
+ var x, y, i, j;
+ var dv = new SpiceDataView(a);
+ var zsum = new adler();
+ dv.setUint32(at, this.buffer_size() - 12); at += 4;
+ dv.setUint8(at, 'I'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'D'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'A'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'T'.charCodeAt(0)); at++;
+
+ /* zlib header. */
+ dv.setUint8(at, 0x78); at++;
+ dv.setUint8(at, 0x01); at++;
+
+ /* Deflate header. Specifies uncompressed, final bit */
+ dv.setUint8(at, 0x80); at++;
+ dv.setUint16(at, this.data.byteLength + this.height); at += 2;
+ dv.setUint16(at, ~(this.data.byteLength + this.height)); at += 2;
+ var u8 = new Uint8Array(this.data);
+ for (i = 0, y = 0; y < this.height; y++)
+ {
+ /* Filter type 0 - uncompressed */
+ dv.setUint8(at, 0); at++;
+ zsum.update(0);
+ for (x = 0; x < this.width && i < this.data.byteLength; x++)
+ {
+ zsum.update(u8[i]);
+ dv.setUint8(at, u8[i++]); at++;
+ zsum.update(u8[i]);
+ dv.setUint8(at, u8[i++]); at++;
+ zsum.update(u8[i]);
+ dv.setUint8(at, u8[i++]); at++;
+ zsum.update(u8[i]);
+ dv.setUint8(at, u8[i++]); at++;
+ }
+ }
+
+ /* zlib checksum. */
+ dv.setUint16(at, zsum.s2); at+=2;
+ dv.setUint16(at, zsum.s1); at+=2;
+
+ /* FIXME - something is not quite right with the zlib code;
+ you get an error from libpng if you open the image in
+ gimp. But it works, so it's good enough for now... */
+
+ dv.setUint32(at, crc32(a, orig + 4, this.buffer_size() - 8)); at += 4;
+ return at;
+ },
+ buffer_size: function()
+ {
+ return 12 + this.data.byteLength + this.height + 4 + 2 + 1 + 2 + 2;
+ }
+}
+
+
+function PngIEND()
+{
+}
+
+PngIEND.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var orig = at;
+ var i;
+ var dv = new SpiceDataView(a);
+ dv.setUint32(at, this.buffer_size() - 12); at += 4;
+ dv.setUint8(at, 'I'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'E'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'N'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'D'.charCodeAt(0)); at++;
+ dv.setUint32(at, crc32(a, orig + 4, this.buffer_size() - 8)); at += 4;
+ return at;
+ },
+ buffer_size: function()
+ {
+ return 12;
+ }
+}
+
+
+function create_rgba_png(width, height, bytes)
+{
+ var i;
+ var ihdr = new PngIHDR(width, height);
+ var idat = new PngIDAT(width, height, bytes);
+ var iend = new PngIEND;
+
+ var mb = new ArrayBuffer(ihdr.buffer_size() + idat.buffer_size() + iend.buffer_size());
+ var at = ihdr.to_buffer(mb);
+ at = idat.to_buffer(mb, at);
+ at = iend.to_buffer(mb, at);
+
+ var u8 = new Uint8Array(mb);
+ var str = "";
+ for (i = 0; i < at; i++)
+ {
+ str += "%";
+ if (u8[i] < 16)
+ str += "0";
+ str += u8[i].toString(16);
+ }
+
+
+ return "%89PNG%0D%0A%1A%0A" + str;
+}
diff --git a/ui/js/spice/prng4.js b/ui/js/spice/prng4.js
new file mode 100644
index 0000000..ef3efd6
--- /dev/null
+++ b/ui/js/spice/prng4.js
@@ -0,0 +1,79 @@
+// Downloaded from http://www-cs-students.stanford.edu/~tjw/jsbn/ by Jeremy White on 6/1/2012
+
+/*
+ * Copyright (c) 2003-2005 Tom Wu
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
+ * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * In addition, the following condition applies:
+ *
+ * All redistributions must retain an intact copy of this copyright notice
+ * and disclaimer.
+ */
+
+
+// prng4.js - uses Arcfour as a PRNG
+
+function Arcfour() {
+ this.i = 0;
+ this.j = 0;
+ this.S = new Array();
+}
+
+// Initialize arcfour context from key, an array of ints, each from [0..255]
+function ARC4init(key) {
+ var i, j, t;
+ for(i = 0; i < 256; ++i)
+ this.S[i] = i;
+ j = 0;
+ for(i = 0; i < 256; ++i) {
+ j = (j + this.S[i] + key[i % key.length]) & 255;
+ t = this.S[i];
+ this.S[i] = this.S[j];
+ this.S[j] = t;
+ }
+ this.i = 0;
+ this.j = 0;
+}
+
+function ARC4next() {
+ var t;
+ this.i = (this.i + 1) & 255;
+ this.j = (this.j + this.S[this.i]) & 255;
+ t = this.S[this.i];
+ this.S[this.i] = this.S[this.j];
+ this.S[this.j] = t;
+ return this.S[(t + this.S[this.i]) & 255];
+}
+
+Arcfour.prototype.init = ARC4init;
+Arcfour.prototype.next = ARC4next;
+
+// Plug in your RNG constructor here
+function prng_newstate() {
+ return new Arcfour();
+}
+
+// Pool size must be a multiple of 4 and greater than 32.
+// An array of bytes the size of the pool will be passed to init()
+var rng_psize = 256;
diff --git a/ui/js/spice/quic.js b/ui/js/spice/quic.js
new file mode 100644
index 0000000..9bb9f47
--- /dev/null
+++ b/ui/js/spice/quic.js
@@ -0,0 +1,1335 @@
+/*"use strict";*/
+/* use strict is commented out because it results in a 5x slowdone in chrome */
+/*
+ * Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+ * Copyright (C) 2012 by Aric Stewart <aric(a)codeweavers.com>
+ *
+ * This file is part of spice-html5.
+ *
+ * spice-html5 is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * spice-html5 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+var encoder;
+
+var QUIC_IMAGE_TYPE_INVALID = 0;
+var QUIC_IMAGE_TYPE_GRAY = 1;
+var QUIC_IMAGE_TYPE_RGB16 = 2;
+var QUIC_IMAGE_TYPE_RGB24 = 3;
+var QUIC_IMAGE_TYPE_RGB32 = 4;
+var QUIC_IMAGE_TYPE_RGBA = 5;
+var DEFevol = 3;
+var DEFwmimax = 6;
+var DEFwminext = 2048;
+var need_init = true;
+var DEFmaxclen = 26;
+var evol = DEFevol;
+var wmimax = DEFwmimax;
+var wminext = DEFwminext;
+var family_5bpc = { nGRcodewords:[0,0,0,0,0,0,0,0],
+ notGRcwlen:[0,0,0,0,0,0,0,0],
+ notGRprefixmask:[0,0,0,0,0,0,0,0],
+ notGRsuffixlen:[0,0,0,0,0,0,0,0],
+ xlatU2L:[0,0,0,0,0,0,0,0],
+ xlatL2U:[0,0,0,0,0,0,0,0]
+ };
+var family_8bpc = { nGRcodewords:[0,0,0,0,0,0,0,0],
+ notGRcwlen:[0,0,0,0,0,0,0,0],
+ notGRprefixmask:[0,0,0,0,0,0,0,0],
+ notGRsuffixlen:[0,0,0,0,0,0,0,0],
+ xlatU2L:[0,0,0,0,0,0,0,0],
+ xlatL2U:[0,0,0,0,0,0,0,0]
+ };
+var bppmask = [ 0x00000000,
+ 0x00000001, 0x00000003, 0x00000007, 0x0000000f,
+ 0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff,
+ 0x000001ff, 0x000003ff, 0x000007ff, 0x00000fff,
+ 0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff,
+ 0x0001ffff, 0x0003ffff, 0x0007ffff, 0x000fffff,
+ 0x001fffff, 0x003fffff, 0x007fffff, 0x00ffffff,
+ 0x01ffffff, 0x03ffffff, 0x07ffffff, 0x0fffffff,
+ 0x1fffffff, 0x3fffffff, 0x7fffffff, 0xffffffff];
+
+var zeroLUT = [];
+
+var besttrigtab = [
+ [ 550, 900, 800, 700, 500, 350, 300, 200, 180, 180, 160],
+ [ 110, 550, 900, 800, 550, 400, 350, 250, 140, 160, 140],
+ [ 100, 120, 550, 900, 700, 500, 400, 300, 220, 250, 160]];
+
+var J = [ 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 5, 5, 6, 6,
+ 7, 7, 8, 9, 10, 11, 12, 13, 14, 15];
+
+var lzeroes = [
+ 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0];
+
+var tabrand_chaos = [
+ 0x02c57542, 0x35427717, 0x2f5a2153, 0x9244f155, 0x7bd26d07, 0x354c6052,
+ 0x57329b28, 0x2993868e, 0x6cd8808c, 0x147b46e0, 0x99db66af, 0xe32b4cac,
+ 0x1b671264, 0x9d433486, 0x62a4c192, 0x06089a4b, 0x9e3dce44, 0xdaabee13,
+ 0x222425ea, 0xa46f331d, 0xcd589250, 0x8bb81d7f, 0xc8b736b9, 0x35948d33,
+ 0xd7ac7fd0, 0x5fbe2803, 0x2cfbc105, 0x013dbc4e, 0x7a37820f, 0x39f88e9e,
+ 0xedd58794, 0xc5076689, 0xfcada5a4, 0x64c2f46d, 0xb3ba3243, 0x8974b4f9,
+ 0x5a05aebd, 0x20afcd00, 0x39e2b008, 0x88a18a45, 0x600bde29, 0xf3971ace,
+ 0xf37b0a6b, 0x7041495b, 0x70b707ab, 0x06beffbb, 0x4206051f, 0xe13c4ee3,
+ 0xc1a78327, 0x91aa067c, 0x8295f72a, 0x732917a6, 0x1d871b4d, 0x4048f136,
+ 0xf1840e7e, 0x6a6048c1, 0x696cb71a, 0x7ff501c3, 0x0fc6310b, 0x57e0f83d,
+ 0x8cc26e74, 0x11a525a2, 0x946934c7, 0x7cd888f0, 0x8f9d8604, 0x4f86e73b,
+ 0x04520316, 0xdeeea20c, 0xf1def496, 0x67687288, 0xf540c5b2, 0x22401484,
+ 0x3478658a, 0xc2385746, 0x01979c2c, 0x5dad73c8, 0x0321f58b, 0xf0fedbee,
+ 0x92826ddf, 0x284bec73, 0x5b1a1975, 0x03df1e11, 0x20963e01, 0xa17cf12b,
+ 0x740d776e, 0xa7a6bf3c, 0x01b5cce4, 0x1118aa76, 0xfc6fac0a, 0xce927e9b,
+ 0x00bf2567, 0x806f216c, 0xbca69056, 0x795bd3e9, 0xc9dc4557, 0x8929b6c2,
+ 0x789d52ec, 0x3f3fbf40, 0xb9197368, 0xa38c15b5, 0xc3b44fa8, 0xca8333b0,
+ 0xb7e8d590, 0xbe807feb, 0xbf5f8360, 0xd99e2f5c, 0x372928e1, 0x7c757c4c,
+ 0x0db5b154, 0xc01ede02, 0x1fc86e78, 0x1f3985be, 0xb4805c77, 0x00c880fa,
+ 0x974c1b12, 0x35ab0214, 0xb2dc840d, 0x5b00ae37, 0xd313b026, 0xb260969d,
+ 0x7f4c8879, 0x1734c4d3, 0x49068631, 0xb9f6a021, 0x6b863e6f, 0xcee5debf,
+ 0x29f8c9fb, 0x53dd6880, 0x72b61223, 0x1f67a9fd, 0x0a0f6993, 0x13e59119,
+ 0x11cca12e, 0xfe6b6766, 0x16b6effc, 0x97918fc4, 0xc2b8a563, 0x94f2f741,
+ 0x0bfa8c9a, 0xd1537ae8, 0xc1da349c, 0x873c60ca, 0x95005b85, 0x9b5c080e,
+ 0xbc8abbd9, 0xe1eab1d2, 0x6dac9070, 0x4ea9ebf1, 0xe0cf30d4, 0x1ef5bd7b,
+ 0xd161043e, 0x5d2fa2e2, 0xff5d3cae, 0x86ed9f87, 0x2aa1daa1, 0xbd731a34,
+ 0x9e8f4b22, 0xb1c2c67a, 0xc21758c9, 0xa182215d, 0xccb01948, 0x8d168df7,
+ 0x04238cfe, 0x368c3dbc, 0x0aeadca5, 0xbad21c24, 0x0a71fee5, 0x9fc5d872,
+ 0x54c152c6, 0xfc329483, 0x6783384a, 0xeddb3e1c, 0x65f90e30, 0x884ad098,
+ 0xce81675a, 0x4b372f7d, 0x68bf9a39, 0x43445f1e, 0x40f8d8cb, 0x90d5acb6,
+ 0x4cd07282, 0x349eeb06, 0x0c9d5332, 0x520b24ef, 0x80020447, 0x67976491,
+ 0x2f931ca3, 0xfe9b0535, 0xfcd30220, 0x61a9e6cc, 0xa487d8d7, 0x3f7c5dd1,
+ 0x7d0127c5, 0x48f51d15, 0x60dea871, 0xc9a91cb7, 0x58b53bb3, 0x9d5e0b2d,
+ 0x624a78b4, 0x30dbee1b, 0x9bdf22e7, 0x1df5c299, 0x2d5643a7, 0xf4dd35ff,
+ 0x03ca8fd6, 0x53b47ed8, 0x6f2c19aa, 0xfeb0c1f4, 0x49e54438, 0x2f2577e6,
+ 0xbf876969, 0x72440ea9, 0xfa0bafb8, 0x74f5b3a0, 0x7dd357cd, 0x89ce1358,
+ 0x6ef2cdda, 0x1e7767f3, 0xa6be9fdb, 0x4f5f88f8, 0xba994a3a, 0x08ca6b65,
+ 0xe0893818, 0x9e00a16a, 0xf42bfc8f, 0x9972eedc, 0x749c8b51, 0x32c05f5e,
+ 0xd706805f, 0x6bfbb7cf, 0xd9210a10, 0x31a1db97, 0x923a9559, 0x37a7a1f6,
+ 0x059f8861, 0xca493e62, 0x65157e81, 0x8f6467dd, 0xab85ff9f, 0x9331aff2,
+ 0x8616b9f5, 0xedbd5695, 0xee7e29b1, 0x313ac44f, 0xb903112f, 0x432ef649,
+ 0xdc0a36c0, 0x61cf2bba, 0x81474925, 0xa8b6c7ad, 0xee5931de, 0xb2f8158d,
+ 0x59fb7409, 0x2e3dfaed, 0x9af25a3f, 0xe1fed4d5 ];
+
+var rgb32_pixel_pad = 3;
+var rgb32_pixel_r = 2;
+var rgb32_pixel_g = 1;
+var rgb32_pixel_b = 0;
+var rgb32_pixel_size = 4;
+
+/* Helper Functions */
+
+function ceil_log_2(val)
+{
+ if (val === 1)
+ return 0;
+
+ var result = 1;
+ val -= 1;
+ while (val = val >>> 1)
+ result++;
+
+ return result;
+}
+
+function family_init(family, bpc, limit)
+{
+ var l;
+ for (l = 0; l < bpc; l++)
+ {
+ var altprefixlen, altcodewords;
+ altprefixlen = limit - bpc;
+ if (altprefixlen > bppmask[bpc - l])
+ altprefixlen = bppmask[bpc - l];
+
+ altcodewords = bppmask[bpc] + 1 - (altprefixlen << l);
+ family.nGRcodewords[l] = (altprefixlen << l);
+ family.notGRcwlen[l] = altprefixlen + ceil_log_2(altcodewords);
+ family.notGRprefixmask[l] = bppmask[32 - altprefixlen]>>>0;
+ family.notGRsuffixlen[l] = ceil_log_2(altcodewords);
+ }
+
+ /* decorelate_init */
+ var pixelbitmask = bppmask[bpc];
+ var pixelbitmaskshr = pixelbitmask >>> 1;
+ var s;
+ for (s = 0; s <= pixelbitmask; s++) {
+ if (s <= pixelbitmaskshr) {
+ family.xlatU2L[s] = s << 1;
+ } else {
+ family.xlatU2L[s] = ((pixelbitmask - s) << 1) + 1;
+ }
+ }
+
+ /* corelate_init */
+ for (s = 0; s <= pixelbitmask; s++) {
+ if (s & 0x01) {
+ family.xlatL2U[s] = pixelbitmask - (s >>> 1);
+ } else {
+ family.xlatL2U[s] = (s >>> 1);
+ }
+ }
+}
+
+function quic_image_bpc(type)
+{
+ switch (type) {
+ case QUIC_IMAGE_TYPE_GRAY:
+ return 8;
+ case QUIC_IMAGE_TYPE_RGB16:
+ return 5;
+ case QUIC_IMAGE_TYPE_RGB24:
+ return 8;
+ case QUIC_IMAGE_TYPE_RGB32:
+ return 8;
+ case QUIC_IMAGE_TYPE_RGBA:
+ return 8;
+ case QUIC_IMAGE_TYPE_INVALID:
+ default:
+ console.log("quic: bad image type\n");
+ return 0;
+ }
+}
+
+function cnt_l_zeroes(bits)
+{
+ if (bits & 0xff800000) {
+ return lzeroes[bits >>> 24];
+ } else if (bits & 0xffff8000) {
+ return 8 + lzeroes[(bits >>> 16) & 0x000000ff];
+ } else if (bits & 0xffffff80) {
+ return 16 + lzeroes[(bits >>> 8) & 0x000000ff];
+ } else {
+ return 24 + lzeroes[bits & 0x000000ff];
+ }
+}
+
+function golomb_decoding_8bpc(l, bits)
+{
+ var rc;
+ var cwlen;
+
+ if (bits < 0 || bits > family_8bpc.notGRprefixmask[l])
+ {
+ var zeroprefix = cnt_l_zeroes(bits);
+ cwlen = zeroprefix + 1 + l;
+ rc = (zeroprefix << l) | (bits >> (32-cwlen)) & bppmask[l];
+ }
+ else
+ {
+ cwlen = family_8bpc.notGRcwlen[l];
+ rc = family_8bpc.nGRcodewords[l] + ((bits >> (32-cwlen)) & bppmask[family_8bpc.notGRsuffixlen[l]]);
+ }
+ return {'codewordlen':cwlen, 'rc':rc};
+}
+
+function golomb_code_len_8bpc(n, l)
+{
+ if (n < family_8bpc.nGRcodewords[l]) {
+ return (n >>> l) + 1 + l;
+ } else {
+ return family_8bpc.notGRcwlen[l];
+ }
+}
+
+function QuicModel(bpc)
+{
+ var bstart;
+ var bend = 0;
+
+ this.levels = 0x1 << bpc;
+ this.n_buckets_ptrs = 0;
+
+ switch (evol) {
+ case 1:
+ this.repfirst = 3;
+ this.firstsize = 1;
+ this.repnext = 2;
+ this.mulsize = 2;
+ break;
+ case 3:
+ this.repfirst = 1;
+ this.firstsize = 1;
+ this.repnext = 1;
+ this.mulsize = 2;
+ break;
+ case 5:
+ this.repfirst = 1;
+ this.firstsize = 1;
+ this.repnext = 1;
+ this.mulsize = 4;
+ break;
+ case 0:
+ case 2:
+ case 4:
+ console.log("quic: findmodelparams(): evol value obsolete!!!\n");
+ default:
+ console.log("quic: findmodelparams(): evol out of range!!!\n");
+ }
+
+ this.n_buckets = 0;
+ var repcntr = this.repfirst + 1;
+ var bsize = this.firstsize;
+
+ do {
+ if (this.n_buckets) {
+ bstart = bend + 1;
+ } else {
+ bstart = 0;
+ }
+
+ if (!--repcntr) {
+ repcntr = this.repnext;
+ bsize *= this.mulsize;
+ }
+
+ bend = bstart + bsize - 1;
+ if (bend + bsize >= this.levels) {
+ bend = this.levels - 1;
+ }
+
+ if (!this.n_buckets_ptrs) {
+ this.n_buckets_ptrs = this.levels;
+ }
+
+ (this.n_buckets)++;
+ } while (bend < this.levels - 1);
+}
+
+QuicModel.prototype = {
+ n_buckets : 0,
+ n_buckets_ptrs : 0,
+ repfirst : 0,
+ firstsize : 0,
+ repnext : 0,
+ mulsize : 0,
+ levels :0
+}
+
+function QuicBucket()
+{
+ this.counters = [0,0,0,0,0,0,0,0];
+}
+
+QuicBucket.prototype = {
+ bestcode: 0,
+
+ reste : function (bpp)
+ {
+ this.bestcode = bpp;
+ this.counters = [0,0,0,0,0,0,0,0];
+ },
+
+ update_model_8bpc : function (state, curval, bpp)
+ {
+ var i;
+
+ var bestcode = bpp - 1;
+ var bestcodelen = (this.counters[bestcode] += golomb_code_len_8bpc(curval, bestcode));
+
+ for (i = bpp - 2; i >= 0; i--) {
+ var ithcodelen = (this.counters[i] += golomb_code_len_8bpc(curval, i));
+
+ if (ithcodelen < bestcodelen) {
+ bestcode = i;
+ bestcodelen = ithcodelen;
+ }
+ }
+
+ this.bestcode = bestcode;
+
+ if (bestcodelen > state.wm_trigger) {
+ for (i = 0; i < bpp; i++) {
+ this.counters[i] = this.counters[i] >>> 1;
+ }
+ }
+ }
+}
+
+function QuicFamilyStat()
+{
+ this.buckets_ptrs = [];
+ this.buckets_buf = [];
+}
+
+QuicFamilyStat.prototype = {
+
+ fill_model_structures : function(model)
+ {
+ var bstart;
+ var bend = 0;
+ var bnumber = 0;
+
+ var repcntr = model.repfirst + 1;
+ var bsize = model.firstsize;
+
+ do {
+ if (bnumber) {
+ bstart = bend + 1;
+ } else {
+ bstart = 0;
+ }
+
+ if (!--repcntr) {
+ repcntr = model.repnext;
+ bsize *= model.mulsize;
+ }
+
+ bend = bstart + bsize - 1;
+ if (bend + bsize >= model.levels) {
+ bend = model.levels - 1;
+ }
+
+ this.buckets_buf[bnumber] = new QuicBucket;
+
+ var i;
+ for (i = bstart; i <= bend; i++) {
+ this.buckets_ptrs[i] = this.buckets_buf[bnumber];
+ }
+
+ bnumber++;
+ } while (bend < model.levels - 1);
+ return true;
+ }
+}
+
+function QuicChannel(model_8bpc, model_5bpc)
+{
+ this.state = new CommonState;
+ this.family_stat_8bpc = new QuicFamilyStat;
+ this.family_stat_5bpc = new QuicFamilyStat;
+ this.correlate_row = { zero: 0 , row:[] };
+ this.model_8bpc = model_8bpc;
+ this.model_5bpc = model_5bpc;
+ this.buckets_ptrs = [];
+
+ if (!this.family_stat_8bpc.fill_model_structures(this.model_8bpc))
+ return undefined;
+
+ if (!this.family_stat_5bpc.fill_model_structures(this.model_5bpc))
+ return undefined;
+}
+
+QuicChannel.prototype = {
+
+ reste : function (bpc)
+ {
+ var j;
+ this.correlate_row = { zero: 0 , row: []};
+
+ if (bpc == 8) {
+ for (j = 0; j < this.model_8bpc.n_buckets; j++)
+ this.family_stat_8bpc.buckets_buf[j].reste(7);
+ this.buckets_ptrs = this.family_stat_8bpc.buckets_ptrs;
+ } else if (bpc == 5) {
+ for (j = 0; j < this.model_5bpc.n_buckets; j++)
+ this.family_stat_8bpc.buckets_buf[j].reste(4);
+ this.buckets_ptrs = this.family_stat_5bpc.buckets_ptrs;
+ } else {
+ console.log("quic: %s: bad bpc %d\n", __FUNCTION__, bpc);
+ return false;
+ }
+
+ this.state.reste();
+ return true;
+ }
+}
+
+function CommonState()
+{
+}
+
+CommonState.prototype = {
+ waitcnt: 0,
+ tabrand_seed: 0xff,
+ wm_trigger: 0,
+ wmidx: 0,
+ wmileft: wminext,
+ melcstate: 0,
+ melclen: 0,
+ melcorder: 0,
+
+ set_wm_trigger : function()
+ {
+ var wm = this.wmidx;
+ if (wm > 10) {
+ wm = 10;
+ }
+
+ this.wm_trigger = besttrigtab[Math.floor(evol / 2)][wm];
+ },
+
+ reste : function()
+ {
+ this.waitcnt = 0;
+ this.tabrand_seed = 0x0ff;
+ this.wmidx = 0;
+ this.wmileft = wminext;
+
+ this.set_wm_trigger();
+
+ this.melcstate = 0;
+ this.melclen = J[0];
+ this.melcorder = 1 << this.melclen;
+ },
+
+ tabrand : function()
+ {
+ this.tabrand_seed++;
+ return tabrand_chaos[this.tabrand_seed & 0x0ff];
+ }
+}
+
+
+function QuicEncoder()
+{
+ this.rgb_state = new CommonState;
+ this.model_8bpc = new QuicModel(8);
+ this.model_5bpc = new QuicModel(5);
+ this.channels = [];
+
+ var i;
+ for (i = 0; i < 4; i++) {
+ this.channels[i] = new QuicChannel(this.model_8bpc, this.model_5bpc);
+ if (!this.channels[i])
+ {
+ console.log("quic: failed to create channel");
+ return undefined;
+ }
+ }
+}
+
+QuicEncoder.prototype = {
+ type: 0,
+ width: 0,
+ height: 0,
+ io_idx: 0,
+ io_available_bits: 0,
+ io_word: 0,
+ io_next_word: 0,
+ io_now: 0,
+ io_end: 0,
+ rows_completed: 0,
+ };
+
+QuicEncoder.prototype.reste = function(io_ptr)
+{
+ this.rgb_state.reste();
+
+ this.io_now = io_ptr;
+ this.io_end = this.io_now.length;
+ this.io_idx = 0;
+ this.rows_completed = 0;
+ return true;
+}
+
+QuicEncoder.prototype.read_io_word = function()
+{
+ if (this.io_idx >= this.io_end)
+ throw("quic: out of data");
+ this.io_next_word = this.io_now[this.io_idx++] | this.io_now[this.io_idx++]<<8 | this.io_now[this.io_idx++]<<16 | this.io_now[this.io_idx++]<<24;
+}
+
+QuicEncoder.prototype.decode_eatbits = function (len)
+{
+ this.io_word = this.io_word << len;
+
+ var delta = (this.io_available_bits - len);
+ if (delta >= 0)
+ {
+ this.io_available_bits = delta;
+ this.io_word |= this.io_next_word >>> this.io_available_bits;
+ }
+ else
+ {
+ delta = -1 * delta;
+ this.io_word |= this.io_next_word << delta;
+ this.read_io_word();
+ this.io_available_bits = 32 - delta;
+ this.io_word |= this.io_next_word >>> this.io_available_bits;
+ }
+}
+
+QuicEncoder.prototype.decode_eat32bits = function()
+{
+ this.decode_eatbits(16);
+ this.decode_eatbits(16);
+}
+
+QuicEncoder.prototype.reste_channels = function(bpc)
+{
+ var i;
+
+ for (i = 0; i < 4; i++)
+ if (!this.channels[i].reste(bpc))
+ return false;
+ return true;
+}
+
+QuicEncoder.prototype.quic_decode_begin = function(io_ptr)
+{
+ if (!this.reste(io_ptr)) {
+ return false;
+ }
+
+ this.io_idx = 0;
+ this.io_next_word = this.io_now[this.io_idx++] | this.io_now[this.io_idx++]<<8 | this.io_now[this.io_idx++]<<16 | this.io_now[this.io_idx++]<<24;
+ this.io_word = this.io_next_word;
+ this.io_available_bits = 0;
+
+ var magic = this.io_word;
+ this.decode_eat32bits();
+ if (magic != 0x43495551) /*QUIC*/ {
+ console.log("quic: bad magic "+magic.toString(16));
+ return false;
+ }
+
+ var version = this.io_word;
+ this.decode_eat32bits();
+ if (version != ((0 << 16) | (0 & 0xffff))) {
+ console.log("quic: bad version "+version.toString(16));
+ return false;
+ }
+
+ this.type = this.io_word;
+ this.decode_eat32bits();
+
+ this.width = this.io_word;
+ this.decode_eat32bits();
+
+ this.height = this.io_word;
+ this.decode_eat32bits();
+
+ var bpc = quic_image_bpc(this.type);
+
+ if (!this.reste_channels(bpc))
+ return false;
+
+ return true;
+}
+
+QuicEncoder.prototype.quic_rgb32_uncompress_row0_seg = function (i, cur_row, end,
+ waitmask, bpc, bpc_mask)
+{
+ var stopidx;
+ var n_channels = 3;
+ var c;
+ var a;
+
+ if (!i) {
+ cur_row[rgb32_pixel_pad] = 0;
+ c = 0;
+ do
+ {
+ a = golomb_decoding_8bpc(this.channels[c].buckets_ptrs[this.channels[c].correlate_row.zero].bestcode, this.io_word);
+ this.channels[c].correlate_row.row[0] = a.rc;
+ cur_row[2-c] = (family_8bpc.xlatL2U[a.rc]&0xFF);
+ this.decode_eatbits(a.codewordlen);
+ } while (++c < n_channels);
+
+ if (this.rgb_state.waitcnt) {
+ --this.rgb_state.waitcnt;
+ } else {
+ this.rgb_state.waitcnt = (this.rgb_state.tabrand() & waitmask);
+ c = 0;
+ do
+ {
+ this.channels[c].buckets_ptrs[this.channels[c].correlate_row.zero].update_model_8bpc(this.rgb_state, this.channels[c].correlate_row.row[0], bpc);
+ } while (++c < n_channels);
+ }
+ stopidx = ++i + this.rgb_state.waitcnt;
+ } else {
+ stopidx = i + this.rgb_state.waitcnt;
+ }
+
+ while (stopidx < end) {
+ for (; i <= stopidx; i++) {
+ cur_row[(i* rgb32_pixel_size)+rgb32_pixel_pad] = 0;
+ c = 0;
+ do
+ {
+ a = golomb_decoding_8bpc(this.channels[c].buckets_ptrs[this.channels[c].correlate_row.row[i - 1]].bestcode, this.io_word);
+ this.channels[c].correlate_row.row[i] = a.rc;
+ cur_row[(i* rgb32_pixel_size)+(2-c)] = (family_8bpc.xlatL2U[a.rc] + cur_row[((i-1) * rgb32_pixel_size) + (2-c)]) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ } while (++c < n_channels);
+ }
+ c = 0;
+ do
+ {
+ this.channels[c].buckets_ptrs[this.channels[c].correlate_row.row[stopidx - 1]].update_model_8bpc(this.rgb_state, this.channels[c].correlate_row.row[stopidx], bpc);
+ } while (++c < n_channels);
+ stopidx = i + (this.rgb_state.tabrand() & waitmask);
+ }
+
+ for (; i < end; i++) {
+ cur_row[(i* rgb32_pixel_size)+rgb32_pixel_pad] = 0;
+ c = 0;
+ do
+ {
+ a = golomb_decoding_8bpc(this.channels[c].buckets_ptrs[this.channels[c].correlate_row.row[i - 1]].bestcode, this.io_word);
+ this.channels[c].correlate_row.row[i] = a.rc;
+ cur_row[(i* rgb32_pixel_size)+(2-c)] = (family_8bpc.xlatL2U[a.rc] + cur_row[((i-1) * rgb32_pixel_size) + (2-c)]) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ } while (++c < n_channels);
+ }
+ this.rgb_state.waitcnt = stopidx - end;
+}
+
+QuicEncoder.prototype.quic_rgb32_uncompress_row0 = function (cur_row)
+{
+ var bpc = 8;
+ var bpc_mask = 0xff;
+ var pos = 0;
+ var width = this.width;
+
+ while ((wmimax > this.rgb_state.wmidx) && (this.rgb_state.wmileft <= width)) {
+ if (this.rgb_state.wmileft) {
+ this.quic_rgb32_uncompress_row0_seg(pos, cur_row,
+ pos + this.rgb_state.wmileft,
+ bppmask[this.rgb_state.wmidx],
+ bpc, bpc_mask);
+ pos += this.rgb_state.wmileft;
+ width -= this.rgb_state.wmileft;
+ }
+
+ this.rgb_state.wmidx++;
+ this.rgb_state.set_wm_trigger();
+ this.rgb_state.wmileft = wminext;
+ }
+
+ if (width) {
+ this.quic_rgb32_uncompress_row0_seg(pos, cur_row, pos + width,
+ bppmask[this.rgb_state.wmidx], bpc, bpc_mask);
+ if (wmimax > this.rgb_state.wmidx) {
+ this.rgb_state.wmileft -= width;
+ }
+ }
+}
+
+QuicEncoder.prototype.quic_rgb32_uncompress_row_seg = function( prev_row, cur_row, i, end, bpc, bpc_mask)
+{
+ var n_channels = 3;
+ var waitmask = bppmask[this.rgb_state.wmidx];
+
+ var a;
+ var run_index = 0;
+ var stopidx = 0;
+ var run_end = 0;
+ var c;
+
+ if (!i)
+ {
+ cur_row[rgb32_pixel_pad] = 0;
+
+ c = 0;
+ do {
+ a = golomb_decoding_8bpc(this.channels[c].buckets_ptrs[this.channels[c].correlate_row.zero].bestcode, this.io_word);
+ this.channels[c].correlate_row.row[0] = a.rc;
+ cur_row[2-c] = (family_8bpc.xlatL2U[this.channels[c].correlate_row.row[0]] + prev_row[2-c]) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ } while (++c < n_channels);
+
+ if (this.rgb_state.waitcnt) {
+ --this.rgb_state.waitcnt;
+ } else {
+ this.rgb_state.waitcnt = (this.rgb_state.tabrand() & waitmask);
+ c = 0;
+ do {
+ this.channels[c].buckets_ptrs[this.channels[c].correlate_row.zero].update_model_8bpc(this.rgb_state, this.channels[c].correlate_row.row[0], bpc);
+ } while (++c < n_channels);
+ }
+ stopidx = ++i + this.rgb_state.waitcnt;
+ } else {
+ stopidx = i + this.rgb_state.waitcnt;
+ }
+ for (;;) {
+ var rc = 0;
+ while (stopidx < end && !rc) {
+ for (; i <= stopidx && !rc; i++) {
+ var pixel = i * rgb32_pixel_size;
+ var pixelm1 = (i-1) * rgb32_pixel_size;
+ var pixelm2 = (i-2) * rgb32_pixel_size;
+
+ if ( prev_row[pixelm1+rgb32_pixel_r] == prev_row[pixel+rgb32_pixel_r] && prev_row[pixelm1+rgb32_pixel_g] == prev_row[pixel+rgb32_pixel_g] && prev_row[pixelm1 + rgb32_pixel_b] == prev_row[pixel+rgb32_pixel_b])
+ {
+ if (run_index != i && i > 2 && (cur_row[pixelm1+rgb32_pixel_r] == cur_row[pixelm2+rgb32_pixel_r] && cur_row[pixelm1+rgb32_pixel_g] == cur_row[pixelm2+rgb32_pixel_g] && cur_row[pixelm1+rgb32_pixel_b] == cur_row[pixelm2+rgb32_pixel_b]))
+ {
+ /* do run */
+ this.rgb_state.waitcnt = stopidx - i;
+ run_index = i;
+ run_end = i + this.decode_run(this.rgb_state);
+
+ for (; i < run_end; i++) {
+ var pixel = i * rgb32_pixel_size;
+ var pixelm1 = (i-1) * rgb32_pixel_size;
+ cur_row[pixel+rgb32_pixel_pad] = 0;
+ cur_row[pixel+rgb32_pixel_r] = cur_row[pixelm1+rgb32_pixel_r];
+ cur_row[pixel+rgb32_pixel_g] = cur_row[pixelm1+rgb32_pixel_g];
+ cur_row[pixel+rgb32_pixel_b] = cur_row[pixelm1+rgb32_pixel_b];
+ }
+
+ if (i == end) {
+ return;
+ }
+ else
+ {
+ stopidx = i + this.rgb_state.waitcnt;
+ rc = 1;
+ break;
+ }
+ }
+ }
+
+ c = 0;
+ cur_row[pixel+rgb32_pixel_pad] = 0;
+ do {
+ var cc = this.channels[c];
+ var cr = cc.correlate_row;
+
+ a = golomb_decoding_8bpc(cc.buckets_ptrs[cr.row[i-1]].bestcode, this.io_word);
+ cr.row[i] = a.rc;
+ cur_row[pixel+(2-c)] = (family_8bpc.xlatL2U[a.rc] + ((cur_row[pixelm1+(2-c)] + prev_row[pixel+(2-c)]) >> 1)) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ } while (++c < n_channels);
+ }
+ if (rc)
+ break;
+
+ c = 0;
+ do {
+ this.channels[c].buckets_ptrs[this.channels[c].correlate_row.row[stopidx - 1]].update_model_8bpc(this.rgb_state, this.channels[c].correlate_row.row[stopidx], bpc);
+ } while (++c < n_channels);
+
+ stopidx = i + (this.rgb_state.tabrand() & waitmask);
+ }
+
+ for (; i < end && !rc; i++) {
+ var pixel = i * rgb32_pixel_size;
+ var pixelm1 = (i-1) * rgb32_pixel_size;
+ var pixelm2 = (i-2) * rgb32_pixel_size;
+
+ if (prev_row[pixelm1+rgb32_pixel_r] == prev_row[pixel+rgb32_pixel_r] && prev_row[pixelm1+rgb32_pixel_g] == prev_row[pixel+rgb32_pixel_g] && prev_row[pixelm1+rgb32_pixel_b] == prev_row[pixel+rgb32_pixel_b])
+ {
+ if (run_index != i && i > 2 && (cur_row[pixelm1+rgb32_pixel_r] == cur_row[pixelm2+rgb32_pixel_r] && cur_row[pixelm1+rgb32_pixel_g] == cur_row[pixelm2+rgb32_pixel_g] && cur_row[pixelm1+rgb32_pixel_b] == cur_row[pixelm2+rgb32_pixel_b]))
+ {
+ /* do run */
+ this.rgb_state.waitcnt = stopidx - i;
+ run_index = i;
+ run_end = i + this.decode_run(this.rgb_state);
+
+ for (; i < run_end; i++) {
+ var pixel = i * rgb32_pixel_size;
+ var pixelm1 = (i-1) * rgb32_pixel_size;
+ cur_row[pixel+rgb32_pixel_pad] = 0;
+ cur_row[pixel+rgb32_pixel_r] = cur_row[pixelm1+rgb32_pixel_r];
+ cur_row[pixel+rgb32_pixel_g] = cur_row[pixelm1+rgb32_pixel_g];
+ cur_row[pixel+rgb32_pixel_b] = cur_row[pixelm1+rgb32_pixel_b];
+ }
+
+ if (i == end) {
+ return;
+ }
+ else
+ {
+ stopidx = i + this.rgb_state.waitcnt;
+ rc = 1;
+ break;
+ }
+ }
+ }
+
+ cur_row[pixel+rgb32_pixel_pad] = 0;
+ c = 0;
+ do
+ {
+ a = golomb_decoding_8bpc(this.channels[c].buckets_ptrs[this.channels[c].correlate_row.row[i-1]].bestcode, this.io_word);
+ this.channels[c].correlate_row.row[i] = a.rc;
+ cur_row[pixel+(2-c)] = (family_8bpc.xlatL2U[a.rc] + ((cur_row[pixelm1+(2-c)] + prev_row[pixel+(2-c)]) >> 1)) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ } while (++c < n_channels);
+ }
+
+ if (!rc)
+ {
+ this.rgb_state.waitcnt = stopidx - end;
+ return;
+ }
+ }
+}
+
+QuicEncoder.prototype.decode_run = function(state)
+{
+ var runlen = 0;
+
+ do {
+ var hits;
+ var x = (~(this.io_word >>> 24)>>>0)&0xff;
+ var temp = zeroLUT[x];
+
+ for (hits = 1; hits <= temp; hits++) {
+ runlen += state.melcorder;
+
+ if (state.melcstate < 32) {
+ state.melclen = J[++state.melcstate];
+ state.melcorder = (1 << state.melclen);
+ }
+ }
+ if (temp != 8) {
+ this.decode_eatbits(temp + 1);
+
+ break;
+ }
+ this.decode_eatbits(8);
+ } while (true);
+
+ if (state.melclen) {
+ runlen += this.io_word >>> (32 - state.melclen);
+ this.decode_eatbits(state.melclen);
+ }
+
+ if (state.melcstate) {
+ state.melclen = J[--state.melcstate];
+ state.melcorder = (1 << state.melclen);
+ }
+
+ return runlen;
+}
+
+QuicEncoder.prototype.quic_rgb32_uncompress_row = function (prev_row, cur_row)
+{
+ var bpc = 8;
+ var bpc_mask = 0xff;
+ var pos = 0;
+ var width = this.width;
+
+ while ((wmimax > this.rgb_state.wmidx) && (this.rgb_state.wmileft <= width)) {
+ if (this.rgb_state.wmileft) {
+ this.quic_rgb32_uncompress_row_seg(prev_row, cur_row, pos,
+ pos + this.rgb_state.wmileft, bpc, bpc_mask);
+ pos += this.rgb_state.wmileft;
+ width -= this.rgb_state.wmileft;
+ }
+
+ this.rgb_state.wmidx++;
+ this.rgb_state.set_wm_trigger();
+ this.rgb_state.wmileft = wminext;
+ }
+
+ if (width) {
+ this.quic_rgb32_uncompress_row_seg(prev_row, cur_row, pos,
+ pos + width, bpc, bpc_mask);
+ if (wmimax > this.rgb_state.wmidx) {
+ this.rgb_state.wmileft -= width;
+ }
+ }
+}
+
+QuicEncoder.prototype.quic_four_uncompress_row0_seg = function (channel, i,
+ correlate_row, cur_row, end, waitmask,
+ bpc, bpc_mask)
+{
+ var stopidx;
+ var a;
+
+ if (i == 0) {
+ a = golomb_decoding_8bpc(channel.buckets_ptrs[correlate_row.zero].bestcode, this.io_word);
+ correlate_row.row[0] = a.rc;
+ cur_row[rgb32_pixel_pad] = family_8bpc.xlatL2U[a.rc];
+ this.decode_eatbits(a.codewordlen);
+
+ if (channel.state.waitcnt) {
+ --channel.state.waitcnt;
+ } else {
+ channel.state.waitcnt = (channel.state.tabrand() & waitmask);
+ channel.buckets_ptrs[correlate_row.zero].update_model_8bpc(channel.state, correlate_row.row[0], bpc);
+ }
+ stopidx = ++i + channel.state.waitcnt;
+ } else {
+ stopidx = i + channel.state.waitcnt;
+ }
+
+ while (stopidx < end) {
+ var pbucket;
+
+ for (; i <= stopidx; i++) {
+ pbucket = channel.buckets_ptrs[correlate_row.row[i - 1]];
+
+ a = golomb_decoding_8bpc(pbucket.bestcode, this.io_word);
+ correlate_row.row[i] = a.rc;
+ cur_row[(i*rgb32_pixel_size)+rgb32_pixel_pad] = (family_8bpc.xlatL2U[a.rc] + cur_row[((i-1)*rgb32_pixel_size)+rgb32_pixel_pad]) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ }
+
+ pbucket.update_model_8bpc(channel.state, correlate_row.row[stopidx], bpc);
+
+ stopidx = i + (channel.state.tabrand() & waitmask);
+ }
+
+ for (; i < end; i++) {
+ a = golomb_decoding_8bpc(channel.buckets_ptrs[correlate_row.row[i-1]].bestcode, this.io_word);
+
+ correlate_row.row[i] = a.rc;
+ cur_row[(i*rgb32_pixel_size)+rgb32_pixel_pad] = (family_8bpc.xlatL2U[a.rc] + cur_row[((i-1)*rgb32_pixel_size)+rgb32_pixel_pad]) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ }
+ channel.state.waitcnt = stopidx - end;
+}
+
+QuicEncoder.prototype.quic_four_uncompress_row0 = function(channel, cur_row)
+{
+ var bpc = 8;
+ var bpc_mask = 0xff;
+ var correlate_row = channel.correlate_row;
+ var pos = 0;
+ var width = this.width;
+
+ while ((wmimax > channel.state.wmidx) && (channel.state.wmileft <= width)) {
+ if (channel.state.wmileft) {
+ this.quic_four_uncompress_row0_seg(channel, pos, correlate_row, cur_row,
+ pos + channel.state.wmileft, bppmask[channel.state.wmidx],
+ bpc, bpc_mask);
+ pos += channel.state.wmileft;
+ width -= channel.state.wmileft;
+ }
+
+ channel.state.wmidx++;
+ channel.state.set_wm_trigger();
+ channel.state.wmileft = wminext;
+ }
+
+ if (width) {
+ this.quic_four_uncompress_row0_seg(channel, pos, correlate_row, cur_row, pos + width,
+ bppmask[channel.state.wmidx], bpc, bpc_mask);
+ if (wmimax > channel.state.wmidx) {
+ channel.state.wmileft -= width;
+ }
+ }
+}
+
+QuicEncoder.prototype.quic_four_uncompress_row_seg = function (channel,
+ correlate_row, prev_row, cur_row, i,
+ end, bpc, bpc_mask)
+{
+ var waitmask = bppmask[channel.state.wmidx];
+ var stopidx;
+
+ var run_index = 0;
+ var run_end;
+
+ var a;
+
+ if (i == 0) {
+ a = golomb_decoding_8bpc(channel.buckets_ptrs[correlate_row.zero].bestcode, this.io_word);
+
+ correlate_row.row[0] = a.rc
+ cur_row[rgb32_pixel_pad] = (family_8bpc.xlatL2U[a.rc] + prev_row[rgb32_pixel_pad]) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+
+ if (channel.state.waitcnt) {
+ --channel.state.waitcnt;
+ } else {
+ channel.state.waitcnt = (channel.state.tabrand() & waitmask);
+ channel.buckets_ptrs[correlate_row.zero].update_model_8bpc(channel.state, correlate_row.row[0], bpc);
+ }
+ stopidx = ++i + channel.state.waitcnt;
+ } else {
+ stopidx = i + channel.state.waitcnt;
+ }
+ for (;;) {
+ var rc = 0;
+ while (stopidx < end && !rc) {
+ var pbucket;
+ for (; i <= stopidx && !rc; i++) {
+ var pixel = i * rgb32_pixel_size;
+ var pixelm1 = (i-1) * rgb32_pixel_size;
+ var pixelm2 = (i-2) * rgb32_pixel_size;
+
+ if (prev_row[pixelm1+rgb32_pixel_pad] == prev_row[pixel+rgb32_pixel_pad])
+ {
+ if (run_index != i && i > 2 && cur_row[pixelm1+rgb32_pixel_pad] == cur_row[pixelm2+rgb32_pixel_pad])
+ {
+ /* do run */
+ channel.state.waitcnt = stopidx - i;
+ run_index = i;
+
+ run_end = i + this.decode_run(channel.state);
+
+ for (; i < run_end; i++) {
+ var pixel = i * rgb32_pixel_size;
+ var pixelm1 = (i-1) * rgb32_pixel_size;
+ cur_row[pixel+rgb32_pixel_pad] = cur_row[pixelm1+rgb32_pixel_pad];
+ }
+
+ if (i == end) {
+ return;
+ }
+ else
+ {
+ stopidx = i + channel.state.waitcnt;
+ rc = 1;
+ break;
+ }
+ }
+ }
+
+ pbucket = channel.buckets_ptrs[correlate_row.row[i - 1]];
+ a = golomb_decoding_8bpc(pbucket.bestcode, this.io_word);
+ correlate_row.row[i] = a.rc
+ cur_row[pixel+rgb32_pixel_pad] = (family_8bpc.xlatL2U[a.rc] + ((cur_row[pixelm1+rgb32_pixel_pad] + prev_row[pixel+rgb32_pixel_pad]) >> 1)) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ }
+ if (rc)
+ break;
+
+ pbucket.update_model_8bpc(channel.state, correlate_row.row[stopidx], bpc);
+
+ stopidx = i + (channel.state.tabrand() & waitmask);
+ }
+
+ for (; i < end && !rc; i++) {
+ var pixel = i * rgb32_pixel_size;
+ var pixelm1 = (i-1) * rgb32_pixel_size;
+ var pixelm2 = (i-2) * rgb32_pixel_size;
+ if (prev_row[pixelm1+rgb32_pixel_pad] == prev_row[pixel+rgb32_pixel_pad])
+ {
+ if (run_index != i && i > 2 && cur_row[pixelm1+rgb32_pixel_pad] == cur_row[pixelm2+rgb32_pixel_pad])
+ {
+ /* do run */
+ channel.state.waitcnt = stopidx - i;
+ run_index = i;
+
+ run_end = i + this.decode_run(channel.state);
+
+ for (; i < run_end; i++) {
+ var pixel = i * rgb32_pixel_size;
+ var pixelm1 = (i-1) * rgb32_pixel_size;
+ cur_row[pixel+rgb32_pixel_pad] = cur_row[pixelm1+rgb32_pixel_pad];
+ }
+
+ if (i == end) {
+ return;
+ }
+ else
+ {
+ stopidx = i + channel.state.waitcnt;
+ rc = 1;
+ break;
+ }
+ }
+ }
+
+ a = golomb_decoding_8bpc(channel.buckets_ptrs[correlate_row.row[i-1]].bestcode, this.io_word);
+ correlate_row.row[i] = a.rc;
+ cur_row[pixel+rgb32_pixel_pad] = (family_8bpc.xlatL2U[a.rc] + ((cur_row[pixelm1+rgb32_pixel_pad] + prev_row[pixel+rgb32_pixel_pad]) >> 1)) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ }
+
+ if (!rc)
+ {
+ channel.state.waitcnt = stopidx - end;
+ return;
+ }
+ }
+}
+
+QuicEncoder.prototype.quic_four_uncompress_row = function(channel, prev_row,
+ cur_row)
+{
+ var bpc = 8;
+ var bpc_mask = 0xff;
+ var correlate_row = channel.correlate_row;
+ var pos = 0;
+ var width = this.width;
+
+ while ((wmimax > channel.state.wmidx) && (channel.state.wmileft <= width)) {
+ if (channel.state.wmileft) {
+ this.quic_four_uncompress_row_seg(channel, correlate_row, prev_row, cur_row, pos,
+ pos + channel.state.wmileft, bpc, bpc_mask);
+ pos += channel.state.wmileft;
+ width -= channel.state.wmileft;
+ }
+
+ channel.state.wmidx++;
+ channel.state.set_wm_trigger();
+ channel.state.wmileft = wminext;
+ }
+
+ if (width) {
+ this.quic_four_uncompress_row_seg(channel, correlate_row, prev_row, cur_row, pos,
+ pos + width, bpc, bpc_mask);
+ if (wmimax > channel.state.wmidx) {
+ channel.state.wmileft -= width;
+ }
+ }
+}
+
+/* We need to be generating rgb32 or rgba */
+QuicEncoder.prototype.quic_decode = function(buf, stride)
+{
+ var row;
+
+ switch (this.type)
+ {
+ case QUIC_IMAGE_TYPE_RGB32:
+ case QUIC_IMAGE_TYPE_RGB24:
+ this.channels[0].correlate_row.zero = 0;
+ this.channels[1].correlate_row.zero = 0;
+ this.channels[2].correlate_row.zero = 0;
+ this.quic_rgb32_uncompress_row0(buf);
+
+ this.rows_completed++;
+ for (row = 1; row < this.height; row++)
+ {
+ var prev = buf;
+ buf = prev.subarray(stride);
+ this.channels[0].correlate_row.zero = this.channels[0].correlate_row.row[0];
+ this.channels[1].correlate_row.zero = this.channels[1].correlate_row.row[0];
+ this.channels[2].correlate_row.zero = this.channels[2].correlate_row.row[0];
+ this.quic_rgb32_uncompress_row(prev, buf);
+ this.rows_completed++;
+ };
+ break;
+ case QUIC_IMAGE_TYPE_RGB16:
+ console.log("quic: unsupported output format\n");
+ return false;
+ break;
+ case QUIC_IMAGE_TYPE_RGBA:
+ this.channels[0].correlate_row.zero = 0;
+ this.channels[1].correlate_row.zero = 0;
+ this.channels[2].correlate_row.zero = 0;
+ this.quic_rgb32_uncompress_row0(buf);
+
+ this.channels[3].correlate_row.zero = 0;
+ this.quic_four_uncompress_row0(this.channels[3], buf);
+
+ this.rows_completed++;
+ for (row = 1; row < this.height; row++) {
+ var prev = buf;
+ buf = prev.subarray(stride);
+
+ this.channels[0].correlate_row.zero = this.channels[0].correlate_row.row[0];
+ this.channels[1].correlate_row.zero = this.channels[1].correlate_row.row[0];
+ this.channels[2].correlate_row.zero = this.channels[2].correlate_row.row[0];
+ this.quic_rgb32_uncompress_row(prev, buf);
+
+ this.channels[3].correlate_row.zero = this.channels[3].correlate_row.row[0];
+ this.quic_four_uncompress_row(encoder.channels[3], prev, buf);
+ this.rows_completed++;
+ }
+ break;
+
+ case QUIC_IMAGE_TYPE_GRAY:
+ console.log("quic: unsupported output format\n");
+ return false;
+ break;
+
+ case QUIC_IMAGE_TYPE_INVALID:
+ default:
+ console.log("quic: bad image type\n");
+ return false;
+ }
+ return true;
+}
+
+QuicEncoder.prototype.simple_quic_decode = function(buf)
+{
+ var stride = 4; /* FIXME - proper stride calc please */
+ if (!this.quic_decode_begin(buf))
+ return undefined;
+ if (this.type != QUIC_IMAGE_TYPE_RGB32 && this.type != QUIC_IMAGE_TYPE_RGB24
+ && this.type != QUIC_IMAGE_TYPE_RGBA)
+ return undefined;
+ var out = new Uint8Array(this.width*this.height*4);
+ out[0] = 69;
+ if (this.quic_decode( out, (this.width * stride)))
+ return out;
+ return undefined;
+}
+
+function SpiceQuic()
+{
+}
+
+SpiceQuic.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ if (!encoder)
+ throw("quic: no quic encoder");
+ this.data_size = dv.getUint32(at, true);
+ at += 4;
+ var buf = new Uint8Array(mb.slice(at));
+ this.outptr = encoder.simple_quic_decode(buf);
+ if (this.outptr)
+ {
+ this.type = encoder.type;
+ this.width = encoder.width;
+ this.height = encoder.height;
+ }
+ at += buf.length;
+ return at;
+ },
+}
+
+function convert_spice_quic_to_web(context, spice_quic)
+{
+ var ret = context.createImageData(spice_quic.width, spice_quic.height);
+ var i;
+ for (i = 0; i < (ret.width * ret.height * 4); i+=4)
+ {
+ ret.data[i + 0] = spice_quic.outptr[i + 2];
+ ret.data[i + 1] = spice_quic.outptr[i + 1];
+ ret.data[i + 2] = spice_quic.outptr[i + 0];
+ if (spice_quic.type !== QUIC_IMAGE_TYPE_RGBA)
+ ret.data[i + 3] = 255;
+ else
+ ret.data[i + 3] = 255 - spice_quic.outptr[i + 3];
+ }
+ return ret;
+}
+
+/* Module initialization */
+if (need_init)
+{
+ need_init = false;
+
+ family_init(family_8bpc, 8, DEFmaxclen);
+ family_init(family_5bpc, 5, DEFmaxclen);
+ /* init_zeroLUT */
+ var i, j, k, l;
+
+ j = k = 1;
+ l = 8;
+ for (i = 0; i < 256; ++i) {
+ zeroLUT[i] = l;
+ --k;
+ if (k == 0) {
+ k = j;
+ --l;
+ j *= 2;
+ }
+ }
+
+ encoder = new QuicEncoder;
+
+ if (!encoder)
+ throw("quic: failed to create encoder");
+}
diff --git a/ui/js/spice/rng.js b/ui/js/spice/rng.js
new file mode 100644
index 0000000..efbf382
--- /dev/null
+++ b/ui/js/spice/rng.js
@@ -0,0 +1,102 @@
+// Downloaded from http://www-cs-students.stanford.edu/~tjw/jsbn/ by Jeremy White on 6/1/2012
+
+/*
+ * Copyright (c) 2003-2005 Tom Wu
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
+ * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * In addition, the following condition applies:
+ *
+ * All redistributions must retain an intact copy of this copyright notice
+ * and disclaimer.
+ */
+
+
+// Random number generator - requires a PRNG backend, e.g. prng4.js
+
+// For best results, put code like
+// <body onClick='rng_seed_time();' onKeyPress='rng_seed_time();'>
+// in your main HTML document.
+
+var rng_state;
+var rng_pool;
+var rng_pptr;
+
+// Mix in a 32-bit integer into the pool
+function rng_seed_int(x) {
+ rng_pool[rng_pptr++] ^= x & 255;
+ rng_pool[rng_pptr++] ^= (x >> 8) & 255;
+ rng_pool[rng_pptr++] ^= (x >> 16) & 255;
+ rng_pool[rng_pptr++] ^= (x >> 24) & 255;
+ if(rng_pptr >= rng_psize) rng_pptr -= rng_psize;
+}
+
+// Mix in the current time (w/milliseconds) into the pool
+function rng_seed_time() {
+ rng_seed_int(new Date().getTime());
+}
+
+// Initialize the pool with junk if needed.
+if(rng_pool == null) {
+ rng_pool = new Array();
+ rng_pptr = 0;
+ var t;
+ if(navigator.appName == "Netscape" && navigator.appVersion < "5" && window.crypto) {
+ // Extract entropy (256 bits) from NS4 RNG if available
+ var z = window.crypto.random(32);
+ for(t = 0; t < z.length; ++t)
+ rng_pool[rng_pptr++] = z.charCodeAt(t) & 255;
+ }
+ while(rng_pptr < rng_psize) { // extract some randomness from Math.random()
+ t = Math.floor(65536 * Math.random());
+ rng_pool[rng_pptr++] = t >>> 8;
+ rng_pool[rng_pptr++] = t & 255;
+ }
+ rng_pptr = 0;
+ rng_seed_time();
+ //rng_seed_int(window.screenX);
+ //rng_seed_int(window.screenY);
+}
+
+function rng_get_byte() {
+ if(rng_state == null) {
+ rng_seed_time();
+ rng_state = prng_newstate();
+ rng_state.init(rng_pool);
+ for(rng_pptr = 0; rng_pptr < rng_pool.length; ++rng_pptr)
+ rng_pool[rng_pptr] = 0;
+ rng_pptr = 0;
+ //rng_pool = null;
+ }
+ // TODO: allow reseeding after first request
+ return rng_state.next();
+}
+
+function rng_get_bytes(ba) {
+ var i;
+ for(i = 0; i < ba.length; ++i) ba[i] = rng_get_byte();
+}
+
+function SecureRandom() {}
+
+SecureRandom.prototype.nextBytes = rng_get_bytes;
diff --git a/ui/js/spice/rsa.js b/ui/js/spice/rsa.js
new file mode 100644
index 0000000..ea0e45b
--- /dev/null
+++ b/ui/js/spice/rsa.js
@@ -0,0 +1,146 @@
+// Downloaded from http://www-cs-students.stanford.edu/~tjw/jsbn/ by Jeremy White on 6/1/2012
+
+/*
+ * Copyright (c) 2003-2005 Tom Wu
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
+ * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * In addition, the following condition applies:
+ *
+ * All redistributions must retain an intact copy of this copyright notice
+ * and disclaimer.
+ */
+
+
+// Depends on jsbn.js and rng.js
+
+// Version 1.1: support utf-8 encoding in pkcs1pad2
+
+// convert a (hex) string to a bignum object
+function parseBigInt(str,r) {
+ return new BigInteger(str,r);
+}
+
+function linebrk(s,n) {
+ var ret = "";
+ var i = 0;
+ while(i + n < s.length) {
+ ret += s.substring(i,i+n) + "\n";
+ i += n;
+ }
+ return ret + s.substring(i,s.length);
+}
+
+function byte2Hex(b) {
+ if(b < 0x10)
+ return "0" + b.toString(16);
+ else
+ return b.toString(16);
+}
+
+// PKCS#1 (type 2, random) pad input string s to n bytes, and return a bigint
+function pkcs1pad2(s,n) {
+ if(n < s.length + 11) { // TODO: fix for utf-8
+ alert("Message too long for RSA");
+ return null;
+ }
+ var ba = new Array();
+ var i = s.length - 1;
+ while(i >= 0 && n > 0) {
+ var c = s.charCodeAt(i--);
+ if(c < 128) { // encode using utf-8
+ ba[--n] = c;
+ }
+ else if((c > 127) && (c < 2048)) {
+ ba[--n] = (c & 63) | 128;
+ ba[--n] = (c >> 6) | 192;
+ }
+ else {
+ ba[--n] = (c & 63) | 128;
+ ba[--n] = ((c >> 6) & 63) | 128;
+ ba[--n] = (c >> 12) | 224;
+ }
+ }
+ ba[--n] = 0;
+ var rng = new SecureRandom();
+ var x = new Array();
+ while(n > 2) { // random non-zero pad
+ x[0] = 0;
+ while(x[0] == 0) rng.nextBytes(x);
+ ba[--n] = x[0];
+ }
+ ba[--n] = 2;
+ ba[--n] = 0;
+ return new BigInteger(ba);
+}
+
+// "empty" RSA key constructor
+function RSAKey() {
+ this.n = null;
+ this.e = 0;
+ this.d = null;
+ this.p = null;
+ this.q = null;
+ this.dmp1 = null;
+ this.dmq1 = null;
+ this.coeff = null;
+}
+
+// Set the public key fields N and e from hex strings
+function RSASetPublic(N,E) {
+ if(N != null && E != null && N.length > 0 && E.length > 0) {
+ this.n = parseBigInt(N,16);
+ this.e = parseInt(E,16);
+ }
+ else
+ alert("Invalid RSA public key");
+}
+
+// Perform raw public operation on "x": return x^e (mod n)
+function RSADoPublic(x) {
+ return x.modPowInt(this.e, this.n);
+}
+
+// Return the PKCS#1 RSA encryption of "text" as an even-length hex string
+function RSAEncrypt(text) {
+ var m = pkcs1pad2(text,(this.n.bitLength()+7)>>3);
+ if(m == null) return null;
+ var c = this.doPublic(m);
+ if(c == null) return null;
+ var h = c.toString(16);
+ if((h.length & 1) == 0) return h; else return "0" + h;
+}
+
+// Return the PKCS#1 RSA encryption of "text" as a Base64-encoded string
+//function RSAEncryptB64(text) {
+// var h = this.encrypt(text);
+// if(h) return hex2b64(h); else return null;
+//}
+
+// protected
+RSAKey.prototype.doPublic = RSADoPublic;
+
+// public
+RSAKey.prototype.setPublic = RSASetPublic;
+RSAKey.prototype.encrypt = RSAEncrypt;
+//RSAKey.prototype.encrypt_b64 = RSAEncryptB64;
diff --git a/ui/js/spice/sha1.js b/ui/js/spice/sha1.js
new file mode 100644
index 0000000..363c83d
--- /dev/null
+++ b/ui/js/spice/sha1.js
@@ -0,0 +1,346 @@
+/*
+ * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
+ * in FIPS 180-1
+ * Version 2.2 Copyright Paul Johnston 2000 - 2009.
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for details.
+ */
+
+ /* Downloaded 6/1/2012 from the above address by Jeremy White.
+ License reproduce here for completeness:
+
+Copyright (c) 1998 - 2009, Paul Johnston & Contributors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ */
+
+/*
+ * Configurable variables. You may need to tweak these to be compatible with
+ * the server-side, but the defaults work in most cases.
+ */
+var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
+var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
+
+/*
+ * These are the functions you'll usually want to call
+ * They take string arguments and return either hex or base-64 encoded strings
+ */
+function hex_sha1(s) { return rstr2hex(rstr_sha1(str2rstr_utf8(s))); }
+function b64_sha1(s) { return rstr2b64(rstr_sha1(str2rstr_utf8(s))); }
+function any_sha1(s, e) { return rstr2any(rstr_sha1(str2rstr_utf8(s)), e); }
+function hex_hmac_sha1(k, d)
+ { return rstr2hex(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d))); }
+function b64_hmac_sha1(k, d)
+ { return rstr2b64(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d))); }
+function any_hmac_sha1(k, d, e)
+ { return rstr2any(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d)), e); }
+
+/*
+ * Perform a simple self-test to see if the VM is working
+ */
+function sha1_vm_test()
+{
+ return hex_sha1("abc").toLowerCase() == "a9993e364706816aba3e25717850c26c9cd0d89d";
+}
+
+/*
+ * Calculate the SHA1 of a raw string
+ */
+function rstr_sha1(s)
+{
+ return binb2rstr(binb_sha1(rstr2binb(s), s.length * 8));
+}
+
+/*
+ * Calculate the HMAC-SHA1 of a key and some data (raw strings)
+ */
+function rstr_hmac_sha1(key, data)
+{
+ var bkey = rstr2binb(key);
+ if(bkey.length > 16) bkey = binb_sha1(bkey, key.length * 8);
+
+ var ipad = Array(16), opad = Array(16);
+ for(var i = 0; i < 16; i++)
+ {
+ ipad[i] = bkey[i] ^ 0x36363636;
+ opad[i] = bkey[i] ^ 0x5C5C5C5C;
+ }
+
+ var hash = binb_sha1(ipad.concat(rstr2binb(data)), 512 + data.length * 8);
+ return binb2rstr(binb_sha1(opad.concat(hash), 512 + 160));
+}
+
+/*
+ * Convert a raw string to a hex string
+ */
+function rstr2hex(input)
+{
+ try { hexcase } catch(e) { hexcase=0; }
+ var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+ var output = "";
+ var x;
+ for(var i = 0; i < input.length; i++)
+ {
+ x = input.charCodeAt(i);
+ output += hex_tab.charAt((x >>> 4) & 0x0F)
+ + hex_tab.charAt( x & 0x0F);
+ }
+ return output;
+}
+
+/*
+ * Convert a raw string to a base-64 string
+ */
+function rstr2b64(input)
+{
+ try { b64pad } catch(e) { b64pad=''; }
+ var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ var output = "";
+ var len = input.length;
+ for(var i = 0; i < len; i += 3)
+ {
+ var triplet = (input.charCodeAt(i) << 16)
+ | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)
+ | (i + 2 < len ? input.charCodeAt(i+2) : 0);
+ for(var j = 0; j < 4; j++)
+ {
+ if(i * 8 + j * 6 > input.length * 8) output += b64pad;
+ else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);
+ }
+ }
+ return output;
+}
+
+/*
+ * Convert a raw string to an arbitrary string encoding
+ */
+function rstr2any(input, encoding)
+{
+ var divisor = encoding.length;
+ var remainders = Array();
+ var i, q, x, quotient;
+
+ /* Convert to an array of 16-bit big-endian values, forming the dividend */
+ var dividend = Array(Math.ceil(input.length / 2));
+ for(i = 0; i < dividend.length; i++)
+ {
+ dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
+ }
+
+ /*
+ * Repeatedly perform a long division. The binary array forms the dividend,
+ * the length of the encoding is the divisor. Once computed, the quotient
+ * forms the dividend for the next step. We stop when the dividend is zero.
+ * All remainders are stored for later use.
+ */
+ while(dividend.length > 0)
+ {
+ quotient = Array();
+ x = 0;
+ for(i = 0; i < dividend.length; i++)
+ {
+ x = (x << 16) + dividend[i];
+ q = Math.floor(x / divisor);
+ x -= q * divisor;
+ if(quotient.length > 0 || q > 0)
+ quotient[quotient.length] = q;
+ }
+ remainders[remainders.length] = x;
+ dividend = quotient;
+ }
+
+ /* Convert the remainders to the output string */
+ var output = "";
+ for(i = remainders.length - 1; i >= 0; i--)
+ output += encoding.charAt(remainders[i]);
+
+ /* Append leading zero equivalents */
+ var full_length = Math.ceil(input.length * 8 /
+ (Math.log(encoding.length) / Math.log(2)))
+ for(i = output.length; i < full_length; i++)
+ output = encoding[0] + output;
+
+ return output;
+}
+
+/*
+ * Encode a string as utf-8.
+ * For efficiency, this assumes the input is valid utf-16.
+ */
+function str2rstr_utf8(input)
+{
+ var output = "";
+ var i = -1;
+ var x, y;
+
+ while(++i < input.length)
+ {
+ /* Decode utf-16 surrogate pairs */
+ x = input.charCodeAt(i);
+ y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
+ if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)
+ {
+ x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
+ i++;
+ }
+
+ /* Encode output as utf-8 */
+ if(x <= 0x7F)
+ output += String.fromCharCode(x);
+ else if(x <= 0x7FF)
+ output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),
+ 0x80 | ( x & 0x3F));
+ else if(x <= 0xFFFF)
+ output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),
+ 0x80 | ((x >>> 6 ) & 0x3F),
+ 0x80 | ( x & 0x3F));
+ else if(x <= 0x1FFFFF)
+ output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),
+ 0x80 | ((x >>> 12) & 0x3F),
+ 0x80 | ((x >>> 6 ) & 0x3F),
+ 0x80 | ( x & 0x3F));
+ }
+ return output;
+}
+
+/*
+ * Encode a string as utf-16
+ */
+function str2rstr_utf16le(input)
+{
+ var output = "";
+ for(var i = 0; i < input.length; i++)
+ output += String.fromCharCode( input.charCodeAt(i) & 0xFF,
+ (input.charCodeAt(i) >>> 8) & 0xFF);
+ return output;
+}
+
+function str2rstr_utf16be(input)
+{
+ var output = "";
+ for(var i = 0; i < input.length; i++)
+ output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF,
+ input.charCodeAt(i) & 0xFF);
+ return output;
+}
+
+/*
+ * Convert a raw string to an array of big-endian words
+ * Characters >255 have their high-byte silently ignored.
+ */
+function rstr2binb(input)
+{
+ var output = Array(input.length >> 2);
+ for(var i = 0; i < output.length; i++)
+ output[i] = 0;
+ for(var i = 0; i < input.length * 8; i += 8)
+ output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (24 - i % 32);
+ return output;
+}
+
+/*
+ * Convert an array of big-endian words to a string
+ */
+function binb2rstr(input)
+{
+ var output = "";
+ for(var i = 0; i < input.length * 32; i += 8)
+ output += String.fromCharCode((input[i>>5] >>> (24 - i % 32)) & 0xFF);
+ return output;
+}
+
+/*
+ * Calculate the SHA-1 of an array of big-endian words, and a bit length
+ */
+function binb_sha1(x, len)
+{
+ /* append padding */
+ x[len >> 5] |= 0x80 << (24 - len % 32);
+ x[((len + 64 >> 9) << 4) + 15] = len;
+
+ var w = Array(80);
+ var a = 1732584193;
+ var b = -271733879;
+ var c = -1732584194;
+ var d = 271733878;
+ var e = -1009589776;
+
+ for(var i = 0; i < x.length; i += 16)
+ {
+ var olda = a;
+ var oldb = b;
+ var oldc = c;
+ var oldd = d;
+ var olde = e;
+
+ for(var j = 0; j < 80; j++)
+ {
+ if(j < 16) w[j] = x[i + j];
+ else w[j] = bit_rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
+ var t = safe_add(safe_add(bit_rol(a, 5), sha1_ft(j, b, c, d)),
+ safe_add(safe_add(e, w[j]), sha1_kt(j)));
+ e = d;
+ d = c;
+ c = bit_rol(b, 30);
+ b = a;
+ a = t;
+ }
+
+ a = safe_add(a, olda);
+ b = safe_add(b, oldb);
+ c = safe_add(c, oldc);
+ d = safe_add(d, oldd);
+ e = safe_add(e, olde);
+ }
+ return Array(a, b, c, d, e);
+
+}
+
+/*
+ * Perform the appropriate triplet combination function for the current
+ * iteration
+ */
+function sha1_ft(t, b, c, d)
+{
+ if(t < 20) return (b & c) | ((~b) & d);
+ if(t < 40) return b ^ c ^ d;
+ if(t < 60) return (b & c) | (b & d) | (c & d);
+ return b ^ c ^ d;
+}
+
+/*
+ * Determine the appropriate additive constant for the current iteration
+ */
+function sha1_kt(t)
+{
+ return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 :
+ (t < 60) ? -1894007588 : -899497514;
+}
+
+/*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+function safe_add(x, y)
+{
+ var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+ var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+ return (msw << 16) | (lsw & 0xFFFF);
+}
+
+/*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+function bit_rol(num, cnt)
+{
+ return (num << cnt) | (num >>> (32 - cnt));
+}
diff --git a/ui/js/spice/spiceconn.js b/ui/js/spice/spiceconn.js
new file mode 100644
index 0000000..7d26913
--- /dev/null
+++ b/ui/js/spice/spiceconn.js
@@ -0,0 +1,447 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*----------------------------------------------------------------------------
+** SpiceConn
+** This is the base Javascript class for establishing and
+** managing a connection to a Spice Server.
+** It is used to provide core functionality to the Spice main,
+** display, inputs, and cursor channels. See main.js for
+** usage.
+**--------------------------------------------------------------------------*/
+function SpiceConn(o)
+{
+ if (o === undefined || o.uri === undefined || ! o.uri)
+ throw new Error("You must specify a uri");
+
+ this.ws = new WebSocket(o.uri, 'binary');
+
+ if (! this.ws.binaryType)
+ throw new Error("WebSocket doesn't support binaryType. Try a different browser.");
+
+ this.connection_id = o.connection_id !== undefined ? o.connection_id : 0;
+ this.type = o.type !== undefined ? o.type : SPICE_CHANNEL_MAIN;
+ this.chan_id = o.chan_id !== undefined ? o.chan_id : 0;
+ if (o.parent !== undefined)
+ {
+ this.parent = o.parent;
+ this.message_id = o.parent.message_id;
+ this.password = o.parent.password;
+ }
+ if (o.screen_id !== undefined)
+ this.screen_id = o.screen_id;
+ if (o.dump_id !== undefined)
+ this.dump_id = o.dump_id;
+ if (o.message_id !== undefined)
+ this.message_id = o.message_id;
+ if (o.password !== undefined)
+ this.password = o.password;
+ if (o.onerror !== undefined)
+ this.onerror = o.onerror;
+
+ this.state = "connecting";
+ this.ws.parent = this;
+ this.wire_reader = new SpiceWireReader(this, this.process_inbound);
+ this.messages_sent = 0;
+ this.warnings = [];
+
+ this.ws.addEventListener('open', function(e) {
+ DEBUG > 0 && console.log(">> WebSockets.onopen");
+ DEBUG > 0 && console.log("id " + this.parent.connection_id +"; type " + this.parent.type);
+
+ /***********************************************************************
+ ** WHERE IT ALL REALLY BEGINS
+ ***********************************************************************/
+ this.parent.send_hdr();
+ this.parent.wire_reader.request(SpiceLinkHeader.prototype.buffer_size());
+ this.parent.state = "start";
+ });
+ this.ws.addEventListener('error', function(e) {
+ this.parent.log_err(">> WebSockets.onerror" + e.toString());
+ this.parent.report_error(e);
+ });
+ this.ws.addEventListener('close', function(e) {
+ DEBUG > 0 && console.log(">> WebSockets.onclose");
+ DEBUG > 0 && console.log("id " + this.parent.connection_id +"; type " + this.parent.type);
+ DEBUG > 0 && console.log(e);
+ if (this.parent.state != "closing" && this.parent.state != "error" && this.parent.onerror !== undefined)
+ {
+ var e;
+ if (this.parent.state == "connecting")
+ e = new Error("Connection refused.");
+ else if (this.parent.state == "start" || this.parent.state == "link")
+ e = new Error("Unexpected protocol mismatch.");
+ else if (this.parent.state == "ticket")
+ e = new Error("Bad password.");
+ else
+ e = new Error("Unexpected close while " + this.parent.state);
+
+ this.parent.onerror(e);
+ this.parent.log_err(e.toString());
+ }
+ });
+
+ if (this.ws.readyState == 2 || this.ws.readyState == 3)
+ throw new Error("Unable to connect to " + o.uri);
+
+ this.timeout = window.setTimeout(spiceconn_timeout, SPICE_CONNECT_TIMEOUT, this);
+}
+
+SpiceConn.prototype =
+{
+ send_hdr : function ()
+ {
+ var hdr = new SpiceLinkHeader;
+ var msg = new SpiceLinkMess;
+
+ msg.connection_id = this.connection_id;
+ msg.channel_type = this.type;
+ // FIXME - we're not setting a channel_id...
+ msg.common_caps.push(
+ (1 << SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION) |
+ (1 << SPICE_COMMON_CAP_MINI_HEADER)
+ );
+
+ hdr.size = msg.buffer_size();
+
+ var mb = new ArrayBuffer(hdr.buffer_size() + msg.buffer_size());
+ hdr.to_buffer(mb);
+ msg.to_buffer(mb, hdr.buffer_size());
+
+ DEBUG > 1 && console.log("Sending header:");
+ DEBUG > 2 && hexdump_buffer(mb);
+ this.ws.send(mb);
+ },
+
+ send_ticket: function(ticket)
+ {
+ var hdr = new SpiceLinkAuthTicket();
+ hdr.auth_mechanism = SPICE_COMMON_CAP_AUTH_SPICE;
+ // FIXME - we need to implement RSA to make this work right
+ hdr.encrypted_data = ticket;
+ var mb = new ArrayBuffer(hdr.buffer_size());
+
+ hdr.to_buffer(mb);
+ DEBUG > 1 && console.log("Sending ticket:");
+ DEBUG > 2 && hexdump_buffer(mb);
+ this.ws.send(mb);
+ },
+
+ send_msg: function(msg)
+ {
+ var mb = new ArrayBuffer(msg.buffer_size());
+ msg.to_buffer(mb);
+ this.messages_sent++;
+ DEBUG > 0 && console.log(">> hdr " + this.channel_type() + " type " + msg.type + " size " + mb.byteLength);
+ DEBUG > 2 && hexdump_buffer(mb);
+ this.ws.send(mb);
+ },
+
+ process_inbound: function(mb, saved_header)
+ {
+ DEBUG > 2 && console.log(this.type + ": processing message of size " + mb.byteLength + "; state is " + this.state);
+ if (this.state == "ready")
+ {
+ if (saved_header == undefined)
+ {
+ var msg = new SpiceMiniData(mb);
+
+ if (msg.type > 500)
+ {
+ alert("Something has gone very wrong; we think we have message of type " + msg.type);
+ debugger;
+ }
+
+ if (msg.size == 0)
+ {
+ this.process_message(msg);
+ this.wire_reader.request(SpiceMiniData.prototype.buffer_size());
+ }
+ else
+ {
+ this.wire_reader.request(msg.size);
+ this.wire_reader.save_header(msg);
+ }
+ }
+ else
+ {
+ saved_header.data = mb;
+ this.process_message(saved_header);
+ this.wire_reader.request(SpiceMiniData.prototype.buffer_size());
+ this.wire_reader.save_header(undefined);
+ }
+ }
+
+ else if (this.state == "start")
+ {
+ this.reply_hdr = new SpiceLinkHeader(mb);
+ if (this.reply_hdr.magic != SPICE_MAGIC)
+ {
+ this.state = "error";
+ var e = new Error('Error: magic mismatch: ' + this.reply_hdr.magic);
+ this.report_error(e);
+ }
+ else
+ {
+ // FIXME - Determine major/minor version requirements
+ this.wire_reader.request(this.reply_hdr.size);
+ this.state = "link";
+ }
+ }
+
+ else if (this.state == "link")
+ {
+ this.reply_link = new SpiceLinkReply(mb);
+ // FIXME - Screen the caps - require minihdr at least, right?
+ if (this.reply_link.error)
+ {
+ this.state = "error";
+ var e = new Error('Error: reply link error ' + this.reply_link.error);
+ this.report_error(e);
+ }
+ else
+ {
+ this.send_ticket(rsa_encrypt(this.reply_link.pub_key, this.password + String.fromCharCode(0)));
+ this.state = "ticket";
+ this.wire_reader.request(SpiceLinkAuthReply.prototype.buffer_size());
+ }
+ }
+
+ else if (this.state == "ticket")
+ {
+ this.auth_reply = new SpiceLinkAuthReply(mb);
+ if (this.auth_reply.auth_code == SPICE_LINK_ERR_OK)
+ {
+ DEBUG > 0 && console.log(this.type + ': Connected');
+
+ if (this.type == SPICE_CHANNEL_DISPLAY)
+ {
+ // FIXME - pixmap and glz dictionary config info?
+ var dinit = new SpiceMsgcDisplayInit();
+ var reply = new SpiceMiniData();
+ reply.build_msg(SPICE_MSGC_DISPLAY_INIT, dinit);
+ DEBUG > 0 && console.log("Request display init");
+ this.send_msg(reply);
+ }
+ this.state = "ready";
+ this.wire_reader.request(SpiceMiniData.prototype.buffer_size());
+ if (this.timeout)
+ {
+ window.clearTimeout(this.timeout);
+ delete this.timeout;
+ }
+ }
+ else
+ {
+ this.state = "error";
+ if (this.auth_reply.auth_code == SPICE_LINK_ERR_PERMISSION_DENIED)
+ {
+ var e = new Error("Permission denied.");
+ }
+ else
+ {
+ var e = new Error("Unexpected link error " + this.auth_reply.auth_code);
+ }
+ this.report_error(e);
+ }
+ }
+ },
+
+ process_common_messages : function(msg)
+ {
+ if (msg.type == SPICE_MSG_SET_ACK)
+ {
+ var ack = new SpiceMsgSetAck(msg.data);
+ // FIXME - what to do with generation?
+ this.ack_window = ack.window;
+ DEBUG > 1 && console.log(this.type + ": set ack to " + ack.window);
+ this.msgs_until_ack = this.ack_window;
+ var ackack = new SpiceMsgcAckSync(ack);
+ var reply = new SpiceMiniData();
+ reply.build_msg(SPICE_MSGC_ACK_SYNC, ackack);
+ this.send_msg(reply);
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_PING)
+ {
+ DEBUG > 1 && console.log("ping!");
+ var pong = new SpiceMiniData;
+ pong.type = SPICE_MSGC_PONG;
+ if (msg.data)
+ {
+ pong.data = msg.data.slice(0, 12);
+ }
+ pong.size = pong.buffer_size();
+ this.send_msg(pong);
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_NOTIFY)
+ {
+ // FIXME - Visibility + what
+ var notify = new SpiceMsgNotify(msg.data);
+ if (notify.severity == SPICE_NOTIFY_SEVERITY_ERROR)
+ this.log_err(notify.message);
+ else if (notify.severity == SPICE_NOTIFY_SEVERITY_WARN )
+ this.log_warn(notify.message);
+ else
+ this.log_info(notify.message);
+ return true;
+ }
+
+ return false;
+
+ },
+
+ process_message: function(msg)
+ {
+ var rc;
+ DEBUG > 0 && console.log("<< hdr " + this.channel_type() + " type " + msg.type + " size " + (msg.data && msg.data.byteLength));
+ rc = this.process_common_messages(msg);
+ if (rc)
+ return rc;
+
+ if (this.process_channel_message)
+ rc = this.process_channel_message(msg);
+ else
+ {
+ this.log_err(this.type + ": No message handlers for this channel; message " + msg.type);
+ return false;
+ }
+
+ if (! rc)
+ this.log_warn(this.type + ": Unknown message type " + msg.type + "!");
+
+ if (this.msgs_until_ack !== undefined && this.ack_window)
+ {
+ this.msgs_until_ack--;
+ if (this.msgs_until_ack <= 0)
+ {
+ this.msgs_until_ack = this.ack_window;
+ var ack = new SpiceMiniData();
+ ack.type = SPICE_MSGC_ACK;
+ this.send_msg(ack);
+ DEBUG > 1 && console.log(this.type + ": sent ack");
+ }
+ }
+
+ return rc;
+ },
+
+ channel_type: function()
+ {
+ if (this.type == SPICE_CHANNEL_MAIN)
+ return "main";
+ else if (this.type == SPICE_CHANNEL_DISPLAY)
+ return "display";
+ else if (this.type == SPICE_CHANNEL_INPUTS)
+ return "inputs";
+ else if (this.type == SPICE_CHANNEL_CURSOR)
+ return "cursor";
+ return "unknown-" + this.type;
+
+ },
+
+ log_info: function()
+ {
+ var msg = Array.prototype.join.call(arguments, " ");
+ console.log(msg);
+ if (this.message_id)
+ {
+ var p = document.createElement("p");
+ p.appendChild(document.createTextNode(msg));
+ p.className += "spice-message-info";
+ document.getElementById(this.message_id).appendChild(p);
+ }
+ },
+
+ log_warn: function()
+ {
+ var msg = Array.prototype.join.call(arguments, " ");
+ console.log("WARNING: " + msg);
+ if (this.message_id)
+ {
+ var p = document.createElement("p");
+ p.appendChild(document.createTextNode(msg));
+ p.className += "spice-message-warning";
+ document.getElementById(this.message_id).appendChild(p);
+ }
+ },
+
+ log_err: function()
+ {
+ var msg = Array.prototype.join.call(arguments, " ");
+ console.log("ERROR: " + msg);
+ if (this.message_id)
+ {
+ var p = document.createElement("p");
+ p.appendChild(document.createTextNode(msg));
+ p.className += "spice-message-error";
+ document.getElementById(this.message_id).appendChild(p);
+ }
+ },
+
+ known_unimplemented: function(type, msg)
+ {
+ if ( (!this.warnings[type]) || DEBUG > 1)
+ {
+ var str = "";
+ if (DEBUG <= 1)
+ str = " [ further notices suppressed ]";
+ this.log_warn("Unimplemented function " + type + "(" + msg + ")" + str);
+ this.warnings[type] = true;
+ }
+ },
+
+ report_error: function(e)
+ {
+ this.log_err(e.toString());
+ if (this.onerror != undefined)
+ this.onerror(e);
+ else
+ throw(e);
+ },
+
+ cleanup: function()
+ {
+ if (this.timeout)
+ {
+ window.clearTimeout(this.timeout);
+ delete this.timeout;
+ }
+ if (this.ws)
+ {
+ this.ws.close();
+ this.ws = undefined;
+ }
+ },
+
+ handle_timeout: function()
+ {
+ var e = new Error("Connection timed out.");
+ this.report_error(e);
+ },
+}
+
+function spiceconn_timeout(sc)
+{
+ SpiceConn.prototype.handle_timeout.call(sc);
+}
diff --git a/ui/js/spice/spicedataview.js b/ui/js/spice/spicedataview.js
new file mode 100644
index 0000000..0699ed5
--- /dev/null
+++ b/ui/js/spice/spicedataview.js
@@ -0,0 +1,96 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*----------------------------------------------------------------------------
+** SpiceDataView
+** FIXME FIXME
+** This is used because Firefox does not have DataView yet.
+** We should use DataView if we have it, because it *has* to
+** be faster than this code
+**--------------------------------------------------------------------------*/
+function SpiceDataView(buffer, byteOffset, byteLength)
+{
+ if (byteOffset !== undefined)
+ {
+ if (byteLength !== undefined)
+ this.u8 = new Uint8Array(buffer, byteOffset, byteLength);
+ else
+ this.u8 = new Uint8Array(buffer, byteOffset);
+ }
+ else
+ this.u8 = new Uint8Array(buffer);
+};
+
+SpiceDataView.prototype = {
+ getUint8: function(byteOffset)
+ {
+ return this.u8[byteOffset];
+ },
+ getUint16: function(byteOffset, littleEndian)
+ {
+ var low = 1, high = 0;
+ if (littleEndian)
+ {
+ low = 0;
+ high = 1;
+ }
+
+ return (this.u8[byteOffset + high] << 8) | this.u8[byteOffset + low];
+ },
+ getUint32: function(byteOffset, littleEndian)
+ {
+ var low = 2, high = 0;
+ if (littleEndian)
+ {
+ low = 0;
+ high = 2;
+ }
+
+ return (this.getUint16(byteOffset + high, littleEndian) << 16) |
+ this.getUint16(byteOffset + low, littleEndian);
+ },
+ setUint8: function(byteOffset, b)
+ {
+ this.u8[byteOffset] = (b & 0xff);
+ },
+ setUint16: function(byteOffset, i, littleEndian)
+ {
+ var low = 1, high = 0;
+ if (littleEndian)
+ {
+ low = 0;
+ high = 1;
+ }
+ this.u8[byteOffset + high] = (i & 0xffff) >> 8;
+ this.u8[byteOffset + low] = (i & 0x00ff);
+ },
+ setUint32: function(byteOffset, w, littleEndian)
+ {
+ var low = 2, high = 0;
+ if (littleEndian)
+ {
+ low = 0;
+ high = 2;
+ }
+
+ this.setUint16(byteOffset + high, (w & 0xffffffff) >> 16, littleEndian);
+ this.setUint16(byteOffset + low, (w & 0x0000ffff), littleEndian);
+ },
+}
diff --git a/ui/js/spice/spicemsg.js b/ui/js/spice/spicemsg.js
new file mode 100644
index 0000000..dac4b30
--- /dev/null
+++ b/ui/js/spice/spicemsg.js
@@ -0,0 +1,883 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*----------------------------------------------------------------------------
+** Spice messages
+** This file contains classes for passing messages to and from
+** a spice server. This file should arguably be generated from
+** spice.proto, but it was instead put together by hand.
+**--------------------------------------------------------------------------*/
+function SpiceLinkHeader(a, at)
+{
+ this.magic = SPICE_MAGIC;
+ this.major_version = SPICE_VERSION_MAJOR;
+ this.minor_version = SPICE_VERSION_MINOR;
+ this.size = 0;
+ if (a !== undefined)
+ this.from_buffer(a, at);
+}
+
+SpiceLinkHeader.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.magic = "";
+ for (var i = 0; i < 4; i++)
+ this.magic += String.fromCharCode(dv.getUint8(at + i));
+ at += 4;
+
+ this.major_version = dv.getUint32(at, true); at += 4;
+ this.minor_version = dv.getUint32(at, true); at += 4;
+ this.size = dv.getUint32(at, true); at += 4;
+ },
+
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ for (var i = 0; i < 4; i++)
+ dv.setUint8(at + i, this.magic.charCodeAt(i));
+ at += 4;
+
+ dv.setUint32(at, this.major_version, true); at += 4;
+ dv.setUint32(at, this.minor_version, true); at += 4;
+ dv.setUint32(at, this.size, true); at += 4;
+ },
+ buffer_size: function()
+ {
+ return 16;
+ },
+}
+
+function SpiceLinkMess(a, at)
+{
+ this.connection_id = 0;
+ this.channel_type = 0;
+ this.channel_id = 0;
+ this.common_caps = [];
+ this.channel_caps = [];
+
+ if (a !== undefined)
+ this.from_buffer(a, at);
+}
+
+SpiceLinkMess.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var i;
+ var orig_at = at;
+ var dv = new SpiceDataView(a);
+ this.connection_id = dv.getUint32(at, true); at += 4;
+ this.channel_type = dv.getUint8(at, true); at++;
+ this.channel_id = dv.getUint8(at, true); at++;
+ var num_common_caps = dv.getUint32(at, true); at += 4;
+ var num_channel_caps = dv.getUint32(at, true); at += 4;
+ var caps_offset = dv.getUint32(at, true); at += 4;
+
+ at = orig_at + caps_offset;
+ this.common_caps = [];
+ for (i = 0; i < num_common_caps; i++)
+ {
+ this.common_caps.unshift(dv.getUint32(at, true)); at += 4;
+ }
+
+ this.channel_caps = [];
+ for (i = 0; i < num_channel_caps; i++)
+ {
+ this.channel_caps.unshift(dv.getUint32(at, true)); at += 4;
+ }
+ },
+
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var orig_at = at;
+ var i;
+ var dv = new SpiceDataView(a);
+ dv.setUint32(at, this.connection_id, true); at += 4;
+ dv.setUint8(at, this.channel_type, true); at++;
+ dv.setUint8(at, this.channel_id, true); at++;
+ dv.setUint32(at, this.common_caps.length, true); at += 4;
+ dv.setUint32(at, this.channel_caps.length, true); at += 4;
+ dv.setUint32(at, (at - orig_at) + 4, true); at += 4;
+
+ for (i = 0; i < this.common_caps.length; i++)
+ {
+ dv.setUint32(at, this.common_caps[i], true); at += 4;
+ }
+
+ for (i = 0; i < this.channel_caps.length; i++)
+ {
+ dv.setUint32(at, this.channel_caps[i], true); at += 4;
+ }
+ },
+ buffer_size: function()
+ {
+ return 18 + (4 * this.common_caps.length) + (4 * this.channel_caps.length);
+ }
+}
+
+function SpiceLinkReply(a, at)
+{
+ this.error = 0;
+ this.pub_key = undefined;
+ this.common_caps = [];
+ this.channel_caps = [];
+
+ if (a !== undefined)
+ this.from_buffer(a, at);
+}
+
+SpiceLinkReply.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var i;
+ var orig_at = at;
+ var dv = new SpiceDataView(a);
+ this.error = dv.getUint32(at, true); at += 4;
+
+ this.pub_key = create_rsa_from_mb(a, at);
+ at += SPICE_TICKET_PUBKEY_BYTES;
+
+ var num_common_caps = dv.getUint32(at, true); at += 4;
+ var num_channel_caps = dv.getUint32(at, true); at += 4;
+ var caps_offset = dv.getUint32(at, true); at += 4;
+
+ at = orig_at + caps_offset;
+ this.common_caps = [];
+ for (i = 0; i < num_common_caps; i++)
+ {
+ this.common_caps.unshift(dv.getUint32(at, true)); at += 4;
+ }
+
+ this.channel_caps = [];
+ for (i = 0; i < num_channel_caps; i++)
+ {
+ this.channel_caps.unshift(dv.getUint32(at, true)); at += 4;
+ }
+ },
+}
+
+function SpiceLinkAuthTicket(a, at)
+{
+ this.auth_mechanism = 0;
+ this.encrypted_data = undefined;
+}
+
+SpiceLinkAuthTicket.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var i;
+ var dv = new SpiceDataView(a);
+ dv.setUint32(at, this.auth_mechanism, true); at += 4;
+ for (i = 0; i < SPICE_TICKET_KEY_PAIR_LENGTH / 8; i++)
+ {
+ if (this.encrypted_data && i < this.encrypted_data.length)
+ dv.setUint8(at, this.encrypted_data[i], true);
+ else
+ dv.setUint8(at, 0, true);
+ at++;
+ }
+ },
+ buffer_size: function()
+ {
+ return 4 + (SPICE_TICKET_KEY_PAIR_LENGTH / 8);
+ }
+}
+
+function SpiceLinkAuthReply(a, at)
+{
+ this.auth_code = 0;
+ if (a !== undefined)
+ this.from_buffer(a, at);
+}
+
+SpiceLinkAuthReply.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.auth_code = dv.getUint32(at, true); at += 4;
+ },
+ buffer_size: function()
+ {
+ return 4;
+ }
+}
+
+function SpiceMiniData(a, at)
+{
+ this.type = 0;
+ this.size = 0;
+ this.data = undefined;
+ if (a !== undefined)
+ this.from_buffer(a, at);
+}
+
+SpiceMiniData.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var i;
+ var dv = new SpiceDataView(a);
+ this.type = dv.getUint16(at, true); at += 2;
+ this.size = dv.getUint32(at, true); at += 4;
+ if (a.byteLength > at)
+ {
+ this.data = a.slice(at);
+ at += this.data.byteLength;
+ }
+ },
+ to_buffer : function(a, at)
+ {
+ at = at || 0;
+ var i;
+ var dv = new SpiceDataView(a);
+ dv.setUint16(at, this.type, true); at += 2;
+ dv.setUint32(at, this.data ? this.data.byteLength : 0, true); at += 4;
+ if (this.data && this.data.byteLength > 0)
+ {
+ var u8arr = new Uint8Array(this.data);
+ for (i = 0; i < u8arr.length; i++, at++)
+ dv.setUint8(at, u8arr[i], true);
+ }
+ },
+ build_msg : function(in_type, extra)
+ {
+ this.type = in_type;
+ this.size = extra.buffer_size();
+ this.data = new ArrayBuffer(this.size);
+ extra.to_buffer(this.data);
+ },
+ buffer_size: function()
+ {
+ if (this.data)
+ return 6 + this.data.byteLength;
+ else
+ return 6;
+ },
+}
+
+function SpiceMsgChannels(a, at)
+{
+ this.num_of_channels = 0;
+ this.channels = [];
+ if (a !== undefined)
+ this.from_buffer(a, at);
+}
+
+SpiceMsgChannels.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var i;
+ var dv = new SpiceDataView(a);
+ this.num_of_channels = dv.getUint32(at, true); at += 4;
+ for (i = 0; i < this.num_of_channels; i++)
+ {
+ var chan = new SpiceChannelId();
+ at = chan.from_dv(dv, at, a);
+ this.channels.push(chan);
+ }
+ },
+}
+
+function SpiceMsgMainInit(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgMainInit.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.session_id = dv.getUint32(at, true); at += 4;
+ this.display_channels_hint = dv.getUint32(at, true); at += 4;
+ this.supported_mouse_modes = dv.getUint32(at, true); at += 4;
+ this.current_mouse_mode = dv.getUint32(at, true); at += 4;
+ this.agent_connected = dv.getUint32(at, true); at += 4;
+ this.agent_tokens = dv.getUint32(at, true); at += 4;
+ this.multi_media_time = dv.getUint32(at, true); at += 4;
+ this.ram_hint = dv.getUint32(at, true); at += 4;
+ },
+}
+
+function SpiceMsgMainMouseMode(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgMainMouseMode.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.supported_modes = dv.getUint16(at, true); at += 2;
+ this.current_mode = dv.getUint16(at, true); at += 2;
+ },
+}
+
+function SpiceMsgSetAck(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgSetAck.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.generation = dv.getUint32(at, true); at += 4;
+ this.window = dv.getUint32(at, true); at += 4;
+ },
+}
+
+function SpiceMsgcAckSync(ack)
+{
+ this.generation = ack.generation;
+}
+
+SpiceMsgcAckSync.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ dv.setUint32(at, this.generation, true); at += 4;
+ },
+ buffer_size: function()
+ {
+ return 4;
+ }
+}
+
+function SpiceMsgcMainMouseModeRequest(mode)
+{
+ this.mode = mode;
+}
+
+SpiceMsgcMainMouseModeRequest.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ dv.setUint16(at, this.mode, true); at += 2;
+ },
+ buffer_size: function()
+ {
+ return 2;
+ }
+}
+
+function SpiceMsgNotify(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgNotify.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var i;
+ var dv = new SpiceDataView(a);
+ this.time_stamp = [];
+ this.time_stamp[0] = dv.getUint32(at, true); at += 4;
+ this.time_stamp[1] = dv.getUint32(at, true); at += 4;
+ this.severity = dv.getUint32(at, true); at += 4;
+ this.visibility = dv.getUint32(at, true); at += 4;
+ this.what = dv.getUint32(at, true); at += 4;
+ this.message_len = dv.getUint32(at, true); at += 4;
+ this.message = "";
+ for (i = 0; i < this.message_len; i++)
+ {
+ var c = dv.getUint8(at, true); at++;
+ this.message += String.fromCharCode(c);
+ }
+ },
+}
+
+function SpiceMsgcDisplayInit()
+{
+ this.pixmap_cache_id = 1;
+ this.glz_dictionary_id = 0;
+ this.pixmap_cache_size = 10 * 1024 * 1024;
+ this.glz_dictionary_window_size = 0;
+}
+
+SpiceMsgcDisplayInit.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ dv.setUint8(at, this.pixmap_cache_id, true); at++;
+ dv.setUint32(at, 0, true); at += 4;
+ dv.setUint32(at, this.pixmap_cache_size, true); at += 4;
+ dv.setUint8(at, this.glz_dictionary_id, true); at++;
+ dv.setUint32(at, this.glz_dictionary_window_size, true); at += 4;
+ },
+ buffer_size: function()
+ {
+ return 14;
+ }
+}
+
+function SpiceMsgDisplayBase()
+{
+}
+
+SpiceMsgDisplayBase.prototype =
+{
+ from_dv : function(dv, at, mb)
+ {
+ this.surface_id = dv.getUint32(at, true); at += 4;
+ this.box = new SpiceRect;
+ at = this.box.from_dv(dv, at, mb);
+ this.clip = new SpiceClip;
+ return this.clip.from_dv(dv, at, mb);
+ },
+}
+
+function SpiceMsgDisplayDrawCopy(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgDisplayDrawCopy.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.base = new SpiceMsgDisplayBase;
+ at = this.base.from_dv(dv, at, a);
+ this.data = new SpiceCopy;
+ return this.data.from_dv(dv, at, a);
+ },
+}
+
+function SpiceMsgDisplayDrawFill(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgDisplayDrawFill.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.base = new SpiceMsgDisplayBase;
+ at = this.base.from_dv(dv, at, a);
+ this.data = new SpiceFill;
+ return this.data.from_dv(dv, at, a);
+ },
+}
+
+function SpiceMsgDisplayCopyBits(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgDisplayCopyBits.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.base = new SpiceMsgDisplayBase;
+ at = this.base.from_dv(dv, at, a);
+ this.src_pos = new SpicePoint;
+ return this.src_pos.from_dv(dv, at, a);
+ },
+}
+
+
+function SpiceMsgSurfaceCreate(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgSurfaceCreate.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.surface = new SpiceSurface;
+ return this.surface.from_dv(dv, at, a);
+ },
+}
+
+function SpiceMsgSurfaceDestroy(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgSurfaceDestroy.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.surface_id = dv.getUint32(at, true); at += 4;
+ },
+}
+
+function SpiceMsgInputsInit(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgInputsInit.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.keyboard_modifiers = dv.getUint16(at, true); at += 2;
+ return at;
+ },
+}
+
+function SpiceMsgInputsKeyModifiers(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgInputsKeyModifiers.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.keyboard_modifiers = dv.getUint16(at, true); at += 2;
+ return at;
+ },
+}
+
+function SpiceMsgCursorInit(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgCursorInit.prototype =
+{
+ from_buffer: function(a, at, mb)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.position = new SpicePoint16;
+ at = this.position.from_dv(dv, at, mb);
+ this.trail_length = dv.getUint16(at, true); at += 2;
+ this.trail_frequency = dv.getUint16(at, true); at += 2;
+ this.visible = dv.getUint8(at, true); at ++;
+ this.cursor = new SpiceCursor;
+ return this.cursor.from_dv(dv, at, a);
+ },
+}
+
+
+function SpiceMsgCursorSet(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgCursorSet.prototype =
+{
+ from_buffer: function(a, at, mb)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.position = new SpicePoint16;
+ at = this.position.from_dv(dv, at, mb);
+ this.visible = dv.getUint8(at, true); at ++;
+ this.cursor = new SpiceCursor;
+ return this.cursor.from_dv(dv, at, a);
+ },
+}
+
+
+function SpiceMsgcMousePosition(sc, e)
+{
+ // FIXME - figure out how to correctly compute display_id
+ this.display_id = 0;
+ this.buttons_state = sc.buttons_state;
+ if (e)
+ {
+ this.x = e.clientX - sc.display.surfaces[sc.display.primary_surface].canvas.offsetLeft + document.body.scrollLeft;
+ this.y = e.clientY - sc.display.surfaces[sc.display.primary_surface].canvas.offsetTop + document.body.scrollTop;
+ sc.mousex = this.x;
+ sc.mousey = this.y;
+ }
+ else
+ {
+ this.x = this.y = this.buttons_state = 0;
+ }
+}
+
+SpiceMsgcMousePosition.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ dv.setUint32(at, this.x, true); at += 4;
+ dv.setUint32(at, this.y, true); at += 4;
+ dv.setUint16(at, this.buttons_state, true); at += 2;
+ dv.setUint8(at, this.display_id, true); at += 1;
+ return at;
+ },
+ buffer_size: function()
+ {
+ return 11;
+ }
+}
+
+function SpiceMsgcMouseMotion(sc, e)
+{
+ // FIXME - figure out how to correctly compute display_id
+ this.display_id = 0;
+ this.buttons_state = sc.buttons_state;
+ if (e)
+ {
+ this.x = e.clientX - sc.display.surfaces[sc.display.primary_surface].canvas.offsetLeft;
+ this.y = e.clientY - sc.display.surfaces[sc.display.primary_surface].canvas.offsetTop;
+
+ if (sc.mousex !== undefined)
+ {
+ this.x -= sc.mousex;
+ this.y -= sc.mousey;
+ }
+ sc.mousex = e.clientX - sc.display.surfaces[sc.display.primary_surface].canvas.offsetLeft;
+ sc.mousey = e.clientY - sc.display.surfaces[sc.display.primary_surface].canvas.offsetTop;
+ }
+ else
+ {
+ this.x = this.y = this.buttons_state = 0;
+ }
+}
+
+/* Use the same functions as for MousePosition */
+SpiceMsgcMouseMotion.prototype.to_buffer = SpiceMsgcMousePosition.prototype.to_buffer;
+SpiceMsgcMouseMotion.prototype.buffer_size = SpiceMsgcMousePosition.prototype.buffer_size;
+
+function SpiceMsgcMousePress(sc, e)
+{
+ if (e)
+ {
+ this.button = e.button + 1;
+ this.buttons_state = 1 << e.button;
+ sc.buttons_state = this.buttons_state;
+ }
+ else
+ {
+ this.button = SPICE_MOUSE_BUTTON_LEFT;
+ this.buttons_state = SPICE_MOUSE_BUTTON_MASK_LEFT;
+ }
+}
+
+SpiceMsgcMousePress.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ dv.setUint8(at, this.button, true); at ++;
+ dv.setUint16(at, this.buttons_state, true); at += 2;
+ return at;
+ },
+ buffer_size: function()
+ {
+ return 3;
+ }
+}
+
+function SpiceMsgcMouseRelease(sc, e)
+{
+ if (e)
+ {
+ this.button = e.button + 1;
+ this.buttons_state = 0;
+ sc.buttons_state = this.buttons_state;
+ }
+ else
+ {
+ this.button = SPICE_MOUSE_BUTTON_LEFT;
+ this.buttons_state = 0;
+ }
+}
+
+/* Use the same functions as for MousePress */
+SpiceMsgcMouseRelease.prototype.to_buffer = SpiceMsgcMousePress.prototype.to_buffer;
+SpiceMsgcMouseRelease.prototype.buffer_size = SpiceMsgcMousePress.prototype.buffer_size;
+
+
+function SpiceMsgcKeyDown(e)
+{
+ if (e)
+ {
+ this.code = keycode_to_start_scan(e.keyCode);
+ }
+ else
+ {
+ this.code = 0;
+ }
+}
+
+SpiceMsgcKeyDown.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ dv.setUint32(at, this.code, true); at += 4;
+ return at;
+ },
+ buffer_size: function()
+ {
+ return 4;
+ }
+}
+
+function SpiceMsgcKeyUp(e)
+{
+ if (e)
+ {
+ this.code = keycode_to_end_scan(e.keyCode);
+ }
+ else
+ {
+ this.code = 0;
+ }
+}
+
+/* Use the same functions as for KeyDown */
+SpiceMsgcKeyUp.prototype.to_buffer = SpiceMsgcKeyDown.prototype.to_buffer;
+SpiceMsgcKeyUp.prototype.buffer_size = SpiceMsgcKeyDown.prototype.buffer_size;
+
+function SpiceMsgDisplayStreamCreate(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgDisplayStreamCreate.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.surface_id = dv.getUint32(at, true); at += 4;
+ this.id = dv.getUint32(at, true); at += 4;
+ this.flags = dv.getUint8(at, true); at += 1;
+ this.codec_type = dv.getUint8(at, true); at += 1;
+ /*stamp */ at += 8;
+ this.stream_width = dv.getUint32(at, true); at += 4;
+ this.stream_height = dv.getUint32(at, true); at += 4;
+ this.src_width = dv.getUint32(at, true); at += 4;
+ this.src_height = dv.getUint32(at, true); at += 4;
+
+ this.dest = new SpiceRect;
+ at = this.dest.from_dv(dv, at, a);
+ this.clip = new SpiceClip;
+ this.clip.from_dv(dv, at, a);
+ },
+}
+
+function SpiceStreamDataHeader(a, at)
+{
+}
+
+SpiceStreamDataHeader.prototype =
+{
+ from_dv : function(dv, at, mb)
+ {
+ this.id = dv.getUint32(at, true); at += 4;
+ this.multi_media_time = dv.getUint32(at, true); at += 4;
+ return at;
+ },
+}
+
+function SpiceMsgDisplayStreamData(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgDisplayStreamData.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.base = new SpiceStreamDataHeader;
+ at = this.base.from_dv(dv, at, a);
+ this.data_size = dv.getUint32(at, true); at += 4;
+ this.data = dv.u8.subarray(at, at + this.data_size);
+ },
+}
+
+function SpiceMsgDisplayStreamClip(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgDisplayStreamClip.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.id = dv.getUint32(at, true); at += 4;
+ this.clip = new SpiceClip;
+ this.clip.from_dv(dv, at, a);
+ },
+}
+
+function SpiceMsgDisplayStreamDestroy(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgDisplayStreamDestroy.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.id = dv.getUint32(at, true); at += 4;
+ },
+}
diff --git a/ui/js/spice/spicetype.js b/ui/js/spice/spicetype.js
new file mode 100644
index 0000000..b88ba28
--- /dev/null
+++ b/ui/js/spice/spicetype.js
@@ -0,0 +1,480 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*----------------------------------------------------------------------------
+** Spice types
+** This file contains classes for common spice types.
+** Generally, they are used as helpers in reading and writing messages
+** to and from the server.
+**--------------------------------------------------------------------------*/
+
+function SpiceChannelId()
+{
+}
+SpiceChannelId.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.type = dv.getUint8(at, true); at ++;
+ this.id = dv.getUint8(at, true); at ++;
+ return at;
+ },
+}
+
+function SpiceRect()
+{
+}
+
+SpiceRect.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.top = dv.getUint32(at, true); at += 4;
+ this.left = dv.getUint32(at, true); at += 4;
+ this.bottom = dv.getUint32(at, true); at += 4;
+ this.right = dv.getUint32(at, true); at += 4;
+ return at;
+ },
+ is_same_size : function(r)
+ {
+ if ((this.bottom - this.top) == (r.bottom - r.top) &&
+ (this.right - this.left) == (r.right - r.left) )
+ return true;
+
+ return false;
+ },
+}
+
+function SpiceClipRects()
+{
+}
+
+SpiceClipRects.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ var i;
+ this.num_rects = dv.getUint32(at, true); at += 4;
+ if (this.num_rects > 0)
+ this.rects = [];
+ for (i = 0; i < this.num_rects; i++)
+ {
+ this.rects[i] = new SpiceRect();
+ at = this.rects[i].from_dv(dv, at, mb);
+ }
+ return at;
+ },
+}
+
+function SpiceClip()
+{
+}
+
+SpiceClip.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.type = dv.getUint8(at, true); at ++;
+ if (this.type == SPICE_CLIP_TYPE_RECTS)
+ {
+ this.rects = new SpiceClipRects();
+ at = this.rects.from_dv(dv, at, mb);
+ }
+ return at;
+ },
+}
+
+function SpiceImageDescriptor()
+{
+}
+
+SpiceImageDescriptor.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.id = dv.getUint32(at, true); at += 4;
+ this.id += (dv.getUint32(at, true) << 32); at += 4;
+ this.type = dv.getUint8(at, true); at ++;
+ this.flags = dv.getUint8(at, true); at ++;
+ this.width = dv.getUint32(at, true); at += 4;
+ this.height= dv.getUint32(at, true); at += 4;
+ return at;
+ },
+}
+
+function SpicePalette()
+{
+}
+
+SpicePalette.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ var i;
+ this.unique = [];
+ this.unique[0] = dv.getUint32(at, true); at += 4;
+ this.unique[1] = dv.getUint32(at, true); at += 4;
+ this.num_ents = dv.getUint16(at, true); at += 2;
+ this.ents = [];
+ for (i = 0; i < this.num_ents; i++)
+ {
+ this.ents[i] = dv.getUint32(at, true); at += 4;
+ }
+ return at;
+ },
+}
+
+function SpiceBitmap()
+{
+}
+
+SpiceBitmap.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.format = dv.getUint8(at, true); at++;
+ this.flags = dv.getUint8(at, true); at++;
+ this.x = dv.getUint32(at, true); at += 4;
+ this.y = dv.getUint32(at, true); at += 4;
+ this.stride = dv.getUint32(at, true); at += 4;
+ if (this.flags & SPICE_BITMAP_FLAGS_PAL_FROM_CACHE)
+ {
+ this.palette_id = [];
+ this.palette_id[0] = dv.getUint32(at, true); at += 4;
+ this.palette_id[1] = dv.getUint32(at, true); at += 4;
+ }
+ else
+ {
+ var offset = dv.getUint32(at, true); at += 4;
+ if (offset == 0)
+ this.palette = null;
+ else
+ {
+ this.palette = new SpicePalette;
+ this.palette.from_dv(dv, offset, mb);
+ }
+ }
+ // FIXME - should probably constrain this to the offset
+ // of palette, if non zero
+ this.data = mb.slice(at);
+ at += this.data.byteLength;
+ return at;
+ },
+}
+
+function SpiceImage()
+{
+}
+
+SpiceImage.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.descriptor = new SpiceImageDescriptor;
+ at = this.descriptor.from_dv(dv, at, mb);
+
+ if (this.descriptor.type == SPICE_IMAGE_TYPE_LZ_RGB)
+ {
+ this.lz_rgb = new Object();
+ this.lz_rgb.length = dv.getUint32(at, true); at += 4;
+ var initial_at = at;
+ this.lz_rgb.magic = "";
+ for (var i = 3; i >= 0; i--)
+ this.lz_rgb.magic += String.fromCharCode(dv.getUint8(at + i));
+ at += 4;
+
+ // NOTE: The endian change is *correct*
+ this.lz_rgb.version = dv.getUint32(at); at += 4;
+ this.lz_rgb.type = dv.getUint32(at); at += 4;
+ this.lz_rgb.width = dv.getUint32(at); at += 4;
+ this.lz_rgb.height = dv.getUint32(at); at += 4;
+ this.lz_rgb.stride = dv.getUint32(at); at += 4;
+ this.lz_rgb.top_down = dv.getUint32(at); at += 4;
+
+ var header_size = at - initial_at;
+
+ this.lz_rgb.data = mb.slice(at, this.lz_rgb.length + at - header_size);
+ at += this.lz_rgb.data.byteLength;
+
+ }
+
+ if (this.descriptor.type == SPICE_IMAGE_TYPE_BITMAP)
+ {
+ this.bitmap = new SpiceBitmap;
+ at = this.bitmap.from_dv(dv, at, mb);
+ }
+
+ if (this.descriptor.type == SPICE_IMAGE_TYPE_SURFACE)
+ {
+ this.surface_id = dv.getUint32(at, true); at += 4;
+ }
+
+ if (this.descriptor.type == SPICE_IMAGE_TYPE_JPEG)
+ {
+ this.jpeg = new Object;
+ this.jpeg.data_size = dv.getUint32(at, true); at += 4;
+ this.jpeg.data = mb.slice(at);
+ at += this.jpeg.data.byteLength;
+ }
+
+ if (this.descriptor.type == SPICE_IMAGE_TYPE_JPEG_ALPHA)
+ {
+ this.jpeg_alpha = new Object;
+ this.jpeg_alpha.flags = dv.getUint8(at, true); at += 1;
+ this.jpeg_alpha.jpeg_size = dv.getUint32(at, true); at += 4;
+ this.jpeg_alpha.data_size = dv.getUint32(at, true); at += 4;
+ this.jpeg_alpha.data = mb.slice(at, this.jpeg_alpha.jpeg_size + at);
+ at += this.jpeg_alpha.data.byteLength;
+ // Alpha channel is an LZ image
+ this.jpeg_alpha.alpha = new Object();
+ this.jpeg_alpha.alpha.length = this.jpeg_alpha.data_size - this.jpeg_alpha.jpeg_size;
+ var initial_at = at;
+ this.jpeg_alpha.alpha.magic = "";
+ for (var i = 3; i >= 0; i--)
+ this.jpeg_alpha.alpha.magic += String.fromCharCode(dv.getUint8(at + i));
+ at += 4;
+
+ // NOTE: The endian change is *correct*
+ this.jpeg_alpha.alpha.version = dv.getUint32(at); at += 4;
+ this.jpeg_alpha.alpha.type = dv.getUint32(at); at += 4;
+ this.jpeg_alpha.alpha.width = dv.getUint32(at); at += 4;
+ this.jpeg_alpha.alpha.height = dv.getUint32(at); at += 4;
+ this.jpeg_alpha.alpha.stride = dv.getUint32(at); at += 4;
+ this.jpeg_alpha.alpha.top_down = dv.getUint32(at); at += 4;
+
+ var header_size = at - initial_at;
+
+ this.jpeg_alpha.alpha.data = mb.slice(at, this.jpeg_alpha.alpha.length + at - header_size);
+ at += this.jpeg_alpha.alpha.data.byteLength;
+ }
+
+ if (this.descriptor.type == SPICE_IMAGE_TYPE_QUIC)
+ {
+ this.quic = new SpiceQuic;
+ at = this.quic.from_dv(dv, at, mb);
+ }
+ return at;
+ },
+}
+
+
+function SpiceQMask()
+{
+}
+
+SpiceQMask.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.flags = dv.getUint8(at, true); at++;
+ this.pos = new SpicePoint;
+ at = this.pos.from_dv(dv, at, mb);
+ var offset = dv.getUint32(at, true); at += 4;
+ if (offset == 0)
+ {
+ this.bitmap = null;
+ return at;
+ }
+
+ this.bitmap = new SpiceImage;
+ return this.bitmap.from_dv(dv, offset, mb);
+ },
+}
+
+
+function SpicePattern()
+{
+}
+
+SpicePattern.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ var offset = dv.getUint32(at, true); at += 4;
+ if (offset == 0)
+ {
+ this.pat = null;
+ }
+ else
+ {
+ this.pat = new SpiceImage;
+ this.pat.from_dv(dv, offset, mb);
+ }
+
+ this.pos = new SpicePoint;
+ return this.pos.from_dv(dv, at, mb);
+ }
+}
+
+function SpiceBrush()
+{
+}
+
+SpiceBrush.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.type = dv.getUint8(at, true); at ++;
+ if (this.type == SPICE_BRUSH_TYPE_SOLID)
+ {
+ this.color = dv.getUint32(at, true); at += 4;
+ }
+ else if (this.type == SPICE_BRUSH_TYPE_PATTERN)
+ {
+ this.pattern = new SpicePattern;
+ at = this.pattern.from_dv(dv, at, mb);
+ }
+ return at;
+ },
+}
+
+function SpiceFill()
+{
+}
+
+SpiceFill.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.brush = new SpiceBrush;
+ at = this.brush.from_dv(dv, at, mb);
+ this.rop_descriptor = dv.getUint16(at, true); at += 2;
+ this.mask = new SpiceQMask;
+ return this.mask.from_dv(dv, at, mb);
+ },
+}
+
+
+function SpiceCopy()
+{
+}
+
+SpiceCopy.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ var offset = dv.getUint32(at, true); at += 4;
+ if (offset == 0)
+ {
+ this.src_bitmap = null;
+ }
+ else
+ {
+ this.src_bitmap = new SpiceImage;
+ this.src_bitmap.from_dv(dv, offset, mb);
+ }
+ this.src_area = new SpiceRect;
+ at = this.src_area.from_dv(dv, at, mb);
+ this.rop_descriptor = dv.getUint16(at, true); at += 2;
+ this.scale_mode = dv.getUint8(at, true); at ++;
+ this.mask = new SpiceQMask;
+ return this.mask.from_dv(dv, at, mb);
+ },
+}
+
+function SpicePoint16()
+{
+}
+
+SpicePoint16.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.x = dv.getUint16(at, true); at += 2;
+ this.y = dv.getUint16(at, true); at += 2;
+ return at;
+ },
+}
+
+function SpicePoint()
+{
+}
+
+SpicePoint.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.x = dv.getUint32(at, true); at += 4;
+ this.y = dv.getUint32(at, true); at += 4;
+ return at;
+ },
+}
+
+function SpiceCursorHeader()
+{
+}
+
+SpiceCursorHeader.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.unique = [];
+ this.unique[0] = dv.getUint32(at, true); at += 4;
+ this.unique[1] = dv.getUint32(at, true); at += 4;
+ this.type = dv.getUint8(at, true); at ++;
+ this.width = dv.getUint16(at, true); at += 2;
+ this.height = dv.getUint16(at, true); at += 2;
+ this.hot_spot_x = dv.getUint16(at, true); at += 2;
+ this.hot_spot_y = dv.getUint16(at, true); at += 2;
+ return at;
+ },
+}
+
+function SpiceCursor()
+{
+}
+
+SpiceCursor.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.flags = dv.getUint16(at, true); at += 2;
+ if (this.flags & SPICE_CURSOR_FLAGS_NONE)
+ this.header = null;
+ else
+ {
+ this.header = new SpiceCursorHeader;
+ at = this.header.from_dv(dv, at, mb);
+ this.data = mb.slice(at);
+ at += this.data.byteLength;
+ }
+ return at;
+ },
+}
+
+function SpiceSurface()
+{
+}
+
+SpiceSurface.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.surface_id = dv.getUint32(at, true); at += 4;
+ this.width = dv.getUint32(at, true); at += 4;
+ this.height = dv.getUint32(at, true); at += 4;
+ this.format = dv.getUint32(at, true); at += 4;
+ this.flags = dv.getUint32(at, true); at += 4;
+ return at;
+ },
+}
+
+/* FIXME - SpiceImage types lz_plt, jpeg, zlib_glz, and jpeg_alpha are
+ completely unimplemented */
diff --git a/ui/js/spice/ticket.js b/ui/js/spice/ticket.js
new file mode 100644
index 0000000..96577a3
--- /dev/null
+++ b/ui/js/spice/ticket.js
@@ -0,0 +1,250 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+var SHA_DIGEST_LENGTH = 20;
+
+/*----------------------------------------------------------------------------
+** General ticket RSA encryption functions - just good enough to
+** support what we need to send back an encrypted ticket.
+**--------------------------------------------------------------------------*/
+
+
+/*----------------------------------------------------------------------------
+** OAEP padding functions. Inspired by the OpenSSL implementation.
+**--------------------------------------------------------------------------*/
+function MGF1(mask, seed)
+{
+ var i, j, outlen;
+ for (i = 0, outlen = 0; outlen < mask.length; i++)
+ {
+ var combo_buf = new String;
+
+ for (j = 0; j < seed.length; j++)
+ combo_buf += String.fromCharCode(seed[j]);
+ combo_buf += String.fromCharCode((i >> 24) & 255);
+ combo_buf += String.fromCharCode((i >> 16) & 255);
+ combo_buf += String.fromCharCode((i >> 8) & 255);
+ combo_buf += String.fromCharCode((i) & 255);
+
+ var combo_hash = rstr_sha1(combo_buf);
+ for (j = 0; j < combo_hash.length && outlen < mask.length; j++, outlen++)
+ {
+ mask[outlen] = combo_hash.charCodeAt(j);
+ }
+ }
+}
+
+
+function RSA_padding_add_PKCS1_OAEP(tolen, from, param)
+{
+ var seed = new Array(SHA_DIGEST_LENGTH);
+ var rand = new SecureRandom();
+ rand.nextBytes(seed);
+
+ var dblen = tolen - 1 - seed.length;
+ var db = new Array(dblen);
+ var padlen = dblen - from.length - 1;
+ var i;
+
+ if (param === undefined)
+ param = "";
+
+ if (padlen < SHA_DIGEST_LENGTH)
+ {
+ console.log("Error - data too large for key size.");
+ return null;
+ }
+
+ for (i = 0; i < padlen; i++)
+ db[i] = 0;
+
+ var param_hash = rstr_sha1(param);
+ for (i = 0; i < param_hash.length; i++)
+ db[i] = param_hash.charCodeAt(i);
+
+ db[padlen] = 1;
+ for (i = 0; i < from.length; i++)
+ db[i + padlen + 1] = from.charCodeAt(i);
+
+ var dbmask = new Array(dblen);
+ if (MGF1(dbmask, seed) < 0)
+ return null;
+
+ for (i = 0; i < dbmask.length; i++)
+ db[i] ^= dbmask[i];
+
+
+ var seedmask = Array(SHA_DIGEST_LENGTH);
+ if (MGF1(seedmask, db) < 0)
+ return null;
+
+ for (i = 0; i < seedmask.length; i++)
+ seed[i] ^= seedmask[i];
+
+ var ret = new String;
+ ret += String.fromCharCode(0);
+ for (i = 0; i < seed.length; i++)
+ ret += String.fromCharCode(seed[i]);
+ for (i = 0; i < db.length; i++)
+ ret += String.fromCharCode(db[i]);
+ return ret;
+}
+
+
+function asn_get_length(u8, at)
+{
+ var len = u8[at++];
+ if (len > 0x80)
+ {
+ if (len != 0x81)
+ {
+ console.log("Error: we lazily don't support keys bigger than 255 bytes. It'd be easy to fix.");
+ return null;
+ }
+ len = u8[at++];
+ }
+
+ return [ at, len];
+}
+
+function find_sequence(u8, at)
+{
+ var lenblock;
+ at = at || 0;
+ if (u8[at++] != 0x30)
+ {
+ console.log("Error: public key should start with a sequence flag.");
+ return null;
+ }
+
+ lenblock = asn_get_length(u8, at);
+ if (! lenblock)
+ return null;
+ return lenblock;
+}
+
+/*----------------------------------------------------------------------------
+** Extract an RSA key from a memory buffer
+**--------------------------------------------------------------------------*/
+function create_rsa_from_mb(mb, at)
+{
+ var u8 = new Uint8Array(mb);
+ var lenblock;
+ var seq;
+ var ba;
+ var i;
+ var ret;
+
+ /* We have a sequence which contains a sequence followed by a bit string */
+ seq = find_sequence(u8, at);
+ if (! seq)
+ return null;
+
+ at = seq[0];
+ seq = find_sequence(u8, at);
+ if (! seq)
+ return null;
+
+ /* Skip over the contained sequence */
+ at = seq[0] + seq[1];
+ if (u8[at++] != 0x3)
+ {
+ console.log("Error: expecting bit string next.");
+ return null;
+ }
+
+ /* Get the bit string, which is *itself* a sequence. Having fun yet? */
+ lenblock = asn_get_length(u8, at);
+ if (! lenblock)
+ return null;
+
+ at = lenblock[0];
+ if (u8[at] != 0 && u8[at + 1] != 0x30)
+ {
+ console.log("Error: unexpected values in bit string.");
+ return null;
+ }
+
+ /* Okay, now we have a sequence of two binary values, we hope. */
+ seq = find_sequence(u8, at + 1);
+ if (! seq)
+ return null;
+
+ at = seq[0];
+ if (u8[at++] != 0x02)
+ {
+ console.log("Error: expecting integer n next.");
+ return null;
+ }
+ lenblock = asn_get_length(u8, at);
+ if (! lenblock)
+ return null;
+ at = lenblock[0];
+
+ ba = new Array(lenblock[1]);
+ for (i = 0; i < lenblock[1]; i++)
+ ba[i] = u8[at + i];
+
+ ret = new RSAKey();
+ ret.n = new BigInteger(ba);
+
+ at += lenblock[1];
+
+ if (u8[at++] != 0x02)
+ {
+ console.log("Error: expecting integer e next.");
+ return null;
+ }
+ lenblock = asn_get_length(u8, at);
+ if (! lenblock)
+ return null;
+ at = lenblock[0];
+
+ ret.e = u8[at++];
+ for (i = 1; i < lenblock[1]; i++)
+ {
+ ret.e <<= 8;
+ ret.e |= u8[at++];
+ }
+
+ return ret;
+}
+
+function rsa_encrypt(rsa, str)
+{
+ var i;
+ var ret = [];
+ var oaep = RSA_padding_add_PKCS1_OAEP((rsa.n.bitLength()+7)>>3, str);
+ if (! oaep)
+ return null;
+
+ var ba = new Array(oaep.length);
+
+ for (i = 0; i < oaep.length; i++)
+ ba[i] = oaep.charCodeAt(i);
+ var bigint = new BigInteger(ba);
+ var enc = rsa.doPublic(bigint);
+ var h = enc.toString(16);
+ if ((h.length & 1) != 0)
+ h = "0" + h;
+ for (i = 0; i < h.length; i += 2)
+ ret[i / 2] = parseInt(h.substring(i, i + 2), 16);
+ return ret;
+}
diff --git a/ui/js/spice/utils.js b/ui/js/spice/utils.js
new file mode 100644
index 0000000..5ef23d6
--- /dev/null
+++ b/ui/js/spice/utils.js
@@ -0,0 +1,261 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*----------------------------------------------------------------------------
+** Utility settings and functions for Spice
+**--------------------------------------------------------------------------*/
+var DEBUG = 0;
+var DUMP_DRAWS = false;
+var DUMP_CANVASES = false;
+
+
+/*----------------------------------------------------------------------------
+** combine_array_buffers
+** Combine two array buffers.
+** FIXME - this can't be optimal. See wire.js about eliminating the need.
+**--------------------------------------------------------------------------*/
+function combine_array_buffers(a1, a2)
+{
+ var in1 = new Uint8Array(a1);
+ var in2 = new Uint8Array(a2);
+ var ret = new ArrayBuffer(a1.byteLength + a2.byteLength);
+ var out = new Uint8Array(ret);
+ var o = 0;
+ var i;
+ for (i = 0; i < in1.length; i++)
+ out[o++] = in1[i];
+ for (i = 0; i < in2.length; i++)
+ out[o++] = in2[i];
+
+ return ret;
+}
+
+/*----------------------------------------------------------------------------
+** hexdump_buffer
+**--------------------------------------------------------------------------*/
+function hexdump_buffer(a)
+{
+ var mg = new Uint8Array(a);
+ var hex = "";
+ var str = "";
+ var last_zeros = 0;
+ for (var i = 0; i < mg.length; i++)
+ {
+ var h = Number(mg[i]).toString(16);
+ if (h.length == 1)
+ hex += "0";
+ hex += h + " ";
+
+ str += String.fromCharCode(mg[i]);
+
+ if ((i % 16 == 15) || (i == (mg.length - 1)))
+ {
+ while (i % 16 != 15)
+ {
+ hex += " ";
+ i++;
+ }
+
+ if (last_zeros == 0)
+ console.log(hex + " | " + str);
+
+ if (hex == "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ")
+ {
+ if (last_zeros == 1)
+ {
+ console.log(".");
+ last_zeros++;
+ }
+ else if (last_zeros == 0)
+ last_zeros++;
+ }
+ else
+ last_zeros = 0;
+
+ hex = str = "";
+ }
+ }
+}
+
+/*----------------------------------------------------------------------------
+** Converting keycodes to AT scancodes is very hard.
+** luckly there are some resources on the web and in the Xorg driver that help
+** us figure out what browser depenend keycodes match to what scancodes.
+**
+** This will most likely not work for non US keyboard and browsers other than
+** modern Chrome and FireFox.
+**--------------------------------------------------------------------------*/
+var common_scanmap = [];
+common_scanmap['Q'.charCodeAt(0)] = KEY_Q;
+common_scanmap['W'.charCodeAt(0)] = KEY_W;
+common_scanmap['E'.charCodeAt(0)] = KEY_E;
+common_scanmap['R'.charCodeAt(0)] = KEY_R;
+common_scanmap['T'.charCodeAt(0)] = KEY_T;
+common_scanmap['Y'.charCodeAt(0)] = KEY_Y;
+common_scanmap['U'.charCodeAt(0)] = KEY_U;
+common_scanmap['I'.charCodeAt(0)] = KEY_I;
+common_scanmap['O'.charCodeAt(0)] = KEY_O;
+common_scanmap['P'.charCodeAt(0)] = KEY_P;
+common_scanmap['A'.charCodeAt(0)] = KEY_A;
+common_scanmap['S'.charCodeAt(0)] = KEY_S;
+common_scanmap['D'.charCodeAt(0)] = KEY_D;
+common_scanmap['F'.charCodeAt(0)] = KEY_F;
+common_scanmap['G'.charCodeAt(0)] = KEY_G;
+common_scanmap['H'.charCodeAt(0)] = KEY_H;
+common_scanmap['J'.charCodeAt(0)] = KEY_J;
+common_scanmap['K'.charCodeAt(0)] = KEY_K;
+common_scanmap['L'.charCodeAt(0)] = KEY_L;
+common_scanmap['Z'.charCodeAt(0)] = KEY_Z;
+common_scanmap['X'.charCodeAt(0)] = KEY_X;
+common_scanmap['C'.charCodeAt(0)] = KEY_C;
+common_scanmap['V'.charCodeAt(0)] = KEY_V;
+common_scanmap['B'.charCodeAt(0)] = KEY_B;
+common_scanmap['N'.charCodeAt(0)] = KEY_N;
+common_scanmap['M'.charCodeAt(0)] = KEY_M;
+common_scanmap[' '.charCodeAt(0)] = KEY_Space;
+common_scanmap[13] = KEY_Enter;
+common_scanmap[27] = KEY_Escape;
+common_scanmap[8] = KEY_BackSpace;
+common_scanmap[9] = KEY_Tab;
+common_scanmap[16] = KEY_ShiftL;
+common_scanmap[17] = KEY_LCtrl;
+common_scanmap[18] = KEY_Alt;
+common_scanmap[20] = KEY_CapsLock;
+common_scanmap[144] = KEY_NumLock;
+common_scanmap[112] = KEY_F1;
+common_scanmap[113] = KEY_F2;
+common_scanmap[114] = KEY_F3;
+common_scanmap[115] = KEY_F4;
+common_scanmap[116] = KEY_F5;
+common_scanmap[117] = KEY_F6;
+common_scanmap[118] = KEY_F7;
+common_scanmap[119] = KEY_F8;
+common_scanmap[120] = KEY_F9;
+common_scanmap[121] = KEY_F10;
+common_scanmap[122] = KEY_F11;
+common_scanmap[123] = KEY_F12;
+
+/* These externded scancodes do not line up with values from atKeynames */
+common_scanmap[42] = 99;
+common_scanmap[19] = 101; // Break
+common_scanmap[111] = 0xE035; // KP_Divide
+common_scanmap[106] = 0xE037; // KP_Multiply
+common_scanmap[36] = 0xE047; // Home
+common_scanmap[38] = 0xE048; // Up
+common_scanmap[33] = 0xE049; // PgUp
+common_scanmap[37] = 0xE04B; // Left
+common_scanmap[39] = 0xE04D; // Right
+common_scanmap[35] = 0xE04F; // End
+common_scanmap[40] = 0xE050; // Down
+common_scanmap[34] = 0xE051; // PgDown
+common_scanmap[45] = 0xE052; // Insert
+common_scanmap[46] = 0xE053; // Delete
+common_scanmap[44] = 0x2A37; // Print
+
+/* These are not common between ALL browsers but are between Firefox and DOM3 */
+common_scanmap['1'.charCodeAt(0)] = KEY_1;
+common_scanmap['2'.charCodeAt(0)] = KEY_2;
+common_scanmap['3'.charCodeAt(0)] = KEY_3;
+common_scanmap['4'.charCodeAt(0)] = KEY_4;
+common_scanmap['5'.charCodeAt(0)] = KEY_5;
+common_scanmap['6'.charCodeAt(0)] = KEY_6;
+common_scanmap['7'.charCodeAt(0)] = KEY_7;
+common_scanmap['8'.charCodeAt(0)] = KEY_8;
+common_scanmap['9'.charCodeAt(0)] = KEY_9;
+common_scanmap['0'.charCodeAt(0)] = KEY_0;
+common_scanmap[145] = KEY_ScrollLock;
+common_scanmap[103] = KEY_KP_7;
+common_scanmap[104] = KEY_KP_8;
+common_scanmap[105] = KEY_KP_9;
+common_scanmap[100] = KEY_KP_4;
+common_scanmap[101] = KEY_KP_5;
+common_scanmap[102] = KEY_KP_6;
+common_scanmap[107] = KEY_KP_Plus;
+common_scanmap[97] = KEY_KP_1;
+common_scanmap[98] = KEY_KP_2;
+common_scanmap[99] = KEY_KP_3;
+common_scanmap[96] = KEY_KP_0;
+common_scanmap[110] = KEY_KP_Decimal;
+common_scanmap[191] = KEY_Slash;
+common_scanmap[190] = KEY_Period;
+common_scanmap[188] = KEY_Comma;
+common_scanmap[220] = KEY_BSlash;
+common_scanmap[192] = KEY_Tilde;
+common_scanmap[222] = KEY_Quote;
+common_scanmap[219] = KEY_LBrace;
+common_scanmap[221] = KEY_RBrace;
+
+common_scanmap[91] = 0xE05B; //KEY_LMeta
+common_scanmap[92] = 0xE05C; //KEY_RMeta
+common_scanmap[93] = 0xE05D; //KEY_Menu
+
+/* Firefox/Mozilla codes */
+var firefox_scanmap = [];
+firefox_scanmap[109] = KEY_Minus;
+firefox_scanmap[61] = KEY_Equal;
+firefox_scanmap[59] = KEY_SemiColon;
+
+/* DOM3 codes */
+var DOM_scanmap = [];
+DOM_scanmap[189] = KEY_Minus;
+DOM_scanmap[187] = KEY_Equal;
+DOM_scanmap[186] = KEY_SemiColon;
+
+function get_scancode(code)
+{
+ if (common_scanmap[code] === undefined)
+ {
+ if (navigator.userAgent.indexOf("Firefox") != -1)
+ return firefox_scanmap[code];
+ else
+ return DOM_scanmap[code];
+ }
+ else
+ return common_scanmap[code];
+}
+
+function keycode_to_start_scan(code)
+{
+ var scancode = get_scancode(code);
+ if (scancode === undefined)
+ {
+ alert('no map for ' + code);
+ return 0;
+ }
+
+ if (scancode < 0x100) {
+ return scancode;
+ } else {
+ return 0xe0 | ((scancode - 0x100) << 8);
+ }
+}
+
+function keycode_to_end_scan(code)
+{
+ var scancode = get_scancode(code);
+ if (scancode === undefined)
+ return 0;
+
+ if (scancode < 0x100) {
+ return scancode | 0x80;
+ } else {
+ return 0x80e0 | ((scancode - 0x100) << 8);
+ }
+}
diff --git a/ui/js/spice/wire.js b/ui/js/spice/wire.js
new file mode 100644
index 0000000..2c7f096
--- /dev/null
+++ b/ui/js/spice/wire.js
@@ -0,0 +1,123 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*--------------------------------------------------------------------------------------
+** SpiceWireReader
+** This class will receive messages from a WebSocket and relay it to a given
+** callback. It will optionally save and pass along a header, useful in processing
+** the mini message format.
+**--------------------------------------------------------------------------------------*/
+function SpiceWireReader(sc, callback)
+{
+ this.sc = sc;
+ this.callback = callback;
+ this.needed = 0;
+
+ this.buffers = [];
+
+ this.sc.ws.wire_reader = this;
+ this.sc.ws.binaryType = "arraybuffer";
+ this.sc.ws.addEventListener('message', wire_blob_catcher);
+}
+
+SpiceWireReader.prototype =
+{
+
+ /*------------------------------------------------------------------------
+ ** Process messages coming in from our WebSocket
+ **----------------------------------------------------------------------*/
+ inbound: function (mb)
+ {
+ var at;
+
+ /* Just buffer if we don't need anything yet */
+ if (this.needed == 0)
+ {
+ this.buffers.push(mb);
+ return;
+ }
+
+ /* Optimization - if we have just one inbound block, and it's
+ suitable for our needs, just use it. */
+ if (this.buffers.length == 0 && mb.byteLength >= this.needed)
+ {
+ if (mb.byteLength > this.needed)
+ {
+ this.buffers.push(mb.slice(this.needed));
+ mb = mb.slice(0, this.needed);
+ }
+ this.callback.call(this.sc, mb,
+ this.saved_msg_header || undefined);
+ }
+ else
+ {
+ this.buffers.push(mb);
+ }
+
+
+ /* If we have fragments that add up to what we need, combine them */
+ /* FIXME - it would be faster to revise the processing code to handle
+ ** multiple fragments directly. Essentially, we should be
+ ** able to do this without any slice() or combine_array_buffers() calls */
+ while (this.buffers.length > 1 && this.buffers[0].byteLength < this.needed)
+ {
+ var mb1 = this.buffers.shift();
+ var mb2 = this.buffers.shift();
+
+ this.buffers.unshift(combine_array_buffers(mb1, mb2));
+ }
+
+
+ while (this.buffers.length > 0 && this.buffers[0].byteLength >= this.needed)
+ {
+ mb = this.buffers.shift();
+ if (mb.byteLength > this.needed)
+ {
+ this.buffers.unshift(mb.slice(this.needed));
+ mb = mb.slice(0, this.needed);
+ }
+ this.callback.call(this.sc, mb,
+ this.saved_msg_header || undefined);
+ }
+
+ },
+
+ request: function(n)
+ {
+ this.needed = n;
+ },
+
+ save_header: function(h)
+ {
+ this.saved_msg_header = h;
+ },
+
+ clear_header: function()
+ {
+ this.saved_msg_header = undefined;
+ },
+}
+
+function wire_blob_catcher(e)
+{
+ DEBUG > 1 && console.log(">> WebSockets.onmessage");
+ DEBUG > 1 && console.log("id " + this.wire_reader.sc.connection_id +"; type " + this.wire_reader.sc.type);
+ SpiceWireReader.prototype.inbound.call(this.wire_reader, e.data);
+}
diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js
index fbcf4a2..a18288f 100644
--- a/ui/js/src/kimchi.api.js
+++ b/ui/js/src/kimchi.api.js
@@ -309,6 +309,27 @@ var kimchi = {
});
},
+ spiceToVM : function(vm) {
+ kimchi.requestJSON({
+ url : '/config',
+ type : 'GET',
+ dataType : 'json'
+ }).done(function(data, textStatus, xhr) {
+ http_port = data['http_port'];
+ kimchi.requestJSON({
+ url : "/vms/" + encodeURIComponent(vm) + "/connect",
+ type : "POST",
+ dataType : "json"
+ }).done(function(data, textStatus, xhr) {
+ url = 'http://' + location.hostname + ':' + http_port;
+ url += "/spice.html?port=" + data.graphics.port + "&listen=" + data.graphics.listen;
+ window.open(url);
+ });
+ }).error(function() {
+ kimchi.message.error(i18n['msg.fail.get.config']);
+ });
+ },
+
listVMs : function(suc, err) {
kimchi.requestJSON({
url : kimchi.url + 'vms',
diff --git a/ui/js/src/kimchi.guest_main.js b/ui/js/src/kimchi.guest_main.js
index a36c59c..bff5d8f 100644
--- a/ui/js/src/kimchi.guest_main.js
+++ b/ui/js/src/kimchi.guest_main.js
@@ -119,6 +119,10 @@ kimchi.initVmButtonsAction = function() {
kimchi.vncToVM($(this).data('vm'));
});
+ $(".vm-spice").on("click", function(event) {
+ kimchi.spiceToVM($(this).data('vm'));
+ });
+
kimchi.init_button_stat();
};
@@ -127,13 +131,25 @@ kimchi.init_button_stat = function() {
$('.vm-action').each(function() {
var vm_action = $(this);
var vm_vnc = vm_action.find('.vm-vnc');
- if ((vm_action.data('graphics') === 'vnc')
- && (vm_action.data('vmstate') === 'running')) {
- vm_vnc.removeAttr('disabled');
+ var vm_spice = vm_action.find('.vm-spice');
+ if (vm_action.data('graphics') === 'vnc') {
+ vm_spice.hide();
+ if (vm_action.data('vmstate') === 'running') {
+ vm_vnc.removeAttr('disabled');
+ } else {
+ vm_vnc.attr('disabled', 'disabled');
+ }
+ } else if (vm_action.data('graphics') === 'spice') {
+ vm_vnc.hide();
+ if (vm_action.data('vmstate') === 'running') {
+ vm_spice.removeAttr('disabled');
+ } else {
+ vm_spice.attr('disabled', 'disabled');
+ }
} else {
- vm_vnc.attr('disabled', 'disabled');
+ vm_vnc.hide();
+ vm_spice.hide();
}
-
var editButton = vm_action.find('.vm-edit');
editButton.prop('disabled', vm_action.data('vmstate') !== 'shutoff');
})
diff --git a/ui/pages/guest.html.tmpl b/ui/pages/guest.html.tmpl
index 6bd6853..6d83d57 100644
--- a/ui/pages/guest.html.tmpl
+++ b/ui/pages/guest.html.tmpl
@@ -58,6 +58,7 @@
<span class="text">$_("Actions")</span><span class="arrow"></span>
<div class="popover actionsheet right-side" style="width: 250px">
<button class="button-big vm-vnc" data-vm="{name}"><span class="text">VNC</span></button>
+ <button class="button-big vm-spice" data-vm="{name}"><span class="text">SPICE</span></button>
<button class="button-big vm-edit" data-vm="{name}"><span class="text">$_("Edit")</span></button>
<a class="button-big red vm-delete" data-vm="{name}">$_("Delete")</a>
</div>
diff --git a/ui/pages/spice.html.tmpl b/ui/pages/spice.html.tmpl
new file mode 100644
index 0000000..07e7510
--- /dev/null
+++ b/ui/pages/spice.html.tmpl
@@ -0,0 +1,138 @@
+<!--
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+
+ --------------------------------------------------
+ Spice Javascript client template.
+ Refer to main.js for more detailed information
+ --------------------------------------------------
+
+-->
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
+ <title>Spice Javascript client</title>
+ <script src="js/spice/enums.js"></script>
+ <script src="js/spice/atKeynames.js"></script>
+ <script src="js/spice/utils.js"></script>
+ <script src="js/spice/png.js"></script>
+ <script src="js/spice/lz.js"></script>
+ <script src="js/spice/quic.js"></script>
+ <script src="js/spice/bitmap.js"></script>
+ <script src="js/spice/spicedataview.js"></script>
+ <script src="js/spice/spicetype.js"></script>
+ <script src="js/spice/spicemsg.js"></script>
+ <script src="js/spice/wire.js"></script>
+ <script src="js/spice/spiceconn.js"></script>
+ <script src="js/spice/display.js"></script>
+ <script src="js/spice/main.js"></script>
+ <script src="js/spice/inputs.js"></script>
+ <script src="js/spice/cursor.js"></script>
+ <script src="js/spice/jsbn.js"></script>
+ <script src="js/spice/rsa.js"></script>
+ <script src="js/spice/prng4.js"></script>
+ <script src="js/spice/rng.js"></script>
+ <script src="js/spice/sha1.js"></script>
+ <script src="js/spice/ticket.js"></script>
+ <link rel="stylesheet" type="text/css" href="css/spice/spice.css">
+ </head>
+ <script>
+ var host = null, port = null;
+ var sc;
+ function spice_error(e) {
+ disconnect();
+ }
+
+ function connect() {
+ var host, port, password, scheme = "ws://", uri;
+ host = getParameter("listen");
+ port = getParameter("port");
+ document.getElementById("host").value = host;
+ document.getElementById("port").value = port;
+ if ((!host) || (!port)) {
+ console.log("must set host and port");
+ return;
+ }
+
+ if (sc) {
+ sc.stop();
+ }
+
+ uri = scheme + host + ":" + port;
+ try {
+ sc = new SpiceMainConn({
+ uri : uri,
+ screen_id : "spice-screen",
+ dump_id : "debug-div",
+ message_id : "message-div",
+ password : "",
+ onerror : spice_error
+ });
+ } catch (e) {
+ alert(e.toString());
+ disconnect();
+ }
+
+ }
+
+ function disconnect()
+ {
+ console.log(">> disconnect");
+ if (sc) {
+ sc.stop();
+ }
+ console.log("<< disconnect");
+ }
+
+ function getParameter(name) {
+ var paramStr = location.search;
+ if (paramStr.length == 0)
+ return null;
+ if (paramStr.charAt(0) != '?')
+ return null;
+ paramStr = unescape(paramStr);
+ paramStr = paramStr.substring(1);
+ if (paramStr.length == 0)
+ return null;
+ var params = paramStr.split('&');
+ for ( var i = 0; i < params.length; i++) {
+ var parts = params[i].split('=', 2);
+ if (parts[0] == name) {
+ if (parts.length < 2 || typeof (parts[1]) == "undefined" || parts[1] == "undefined" || parts[1] == "null")
+ return "";
+ return parts[1];
+ }
+ }
+ return null;
+ }
+ </script>
+ <body onload="connect();" onunload="disconnect();">
+ <div id="login">
+ <span class="logo">SPICE</span>
+ <label for="host">Host:</label> <input id="host" value="localhost" type="text" disabled="disabled"> <!-- localhost -->
+ <label for="port">Port:</label> <input id="port" value="5959" type="text" disabled="disabled">
+ </div>
+ <div id="spice-area">
+ <div id="spice-screen" class="spice-screen"></div>
+ </div>
+ <div id="message-div" class="spice-message"></div>
+ <div id="debug-div">
+ <!-- If DUMPXXX is turned on, dumped images will go here -->
+ </div>
+ </body>
+</html>
\ No newline at end of file
--
1.7.1
1
0
[PATCH V5] Open 8000 and 8001 port by default for distro packages
by taget@linux.vnet.ibm.com 30 Dec '13
by taget@linux.vnet.ibm.com 30 Dec '13
30 Dec '13
From: Eli <taget(a)linux.vnet.ibm.com>
V5 - V4 changes:
1. Add cover-letter. (Aline)
2. Move clean up rules into if condition. (Aline)
3. Fix typo (Aline)
V4 - V3 changes:
1 Fix typo in firewalld.xml (Rodrigo)
V3 - V2 changes:
1.Rename kimchid.xml to firewalld.xml (Mark)
2.Remove firewalld from serivce require (Mark)
3.Fix typo
V2 - V1 changes:
1.Add firewalld sevice configure file kimchid.xml to help open iptables port (Mark)
2.Add Ubuntu iptables rule (Royce)
Eli Qiao (1):
spec: Open 8000 and 8001 port by default
contrib/DEBIAN/control.in | 3 ++-
contrib/DEBIAN/postinst | 2 ++
contrib/DEBIAN/postrm | 2 ++
contrib/kimchi.spec.fedora.in | 19 +++++++++++++++++++
contrib/kimchi.spec.suse.in | 10 ++++++++--
src/Makefile.am | 1 +
src/firewalld.xml | 7 +++++++
7 files changed, 41 insertions(+), 3 deletions(-)
create mode 100644 src/firewalld.xml
--
1.8.2.1
1
1
30 Dec '13
From: Royce Lv <lvroyce(a)linux.vnet.ibm.com>
NOTE: this patchset depends on storage server support patchset.
Tested in Fedora 19, due to libvirt bug, ubuntu 13.10 cannot be tested, fixing.
Usage:
GET /storageservers/<ip or hostname>/storagetargets?target_type=netfs
Royce Lv (3):
storage target: Update API.md
storage target: Update controller and json schema
storage target: Add model support
docs/API.md | 9 +++++++++
src/kimchi/API.json | 11 +++++++++++
src/kimchi/controller.py | 30 ++++++++++++++++++++++++++++--
src/kimchi/model.py | 40 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 88 insertions(+), 2 deletions(-)
--
1.8.1.2
1
3
v1-v2 add spice.html.tmpl
This patch add the front end support of spice.
1 If there were a spice vm in host, show a "spice" button
but not "vnc" in guest page.
2 click "spice" we can show a screen just like the demo
http://www.spice-space.org/spice-html5/spice.html
Signed-off-by: zhoumeina <zhoumein(a)linux.vnet.ibm.com>
---
ui/js/spice/atKeynames.js | 183 ++++++
ui/js/spice/bitmap.js | 51 ++
ui/js/spice/cursor.js | 92 +++
ui/js/spice/display.js | 806 ++++++++++++++++++++++++
ui/js/spice/enums.js | 282 +++++++++
ui/js/spice/inputs.js | 251 ++++++++
ui/js/spice/jsbn.js | 589 ++++++++++++++++++
ui/js/spice/lz.js | 166 +++++
ui/js/spice/main.js | 176 ++++++
ui/js/spice/png.js | 256 ++++++++
ui/js/spice/prng4.js | 79 +++
ui/js/spice/quic.js | 1335 ++++++++++++++++++++++++++++++++++++++++
ui/js/spice/rng.js | 102 +++
ui/js/spice/rsa.js | 146 +++++
ui/js/spice/sha1.js | 346 +++++++++++
ui/js/spice/spiceconn.js | 447 ++++++++++++++
ui/js/spice/spicedataview.js | 96 +++
ui/js/spice/spicemsg.js | 883 ++++++++++++++++++++++++++
ui/js/spice/spicetype.js | 480 +++++++++++++++
ui/js/spice/ticket.js | 250 ++++++++
ui/js/spice/utils.js | 261 ++++++++
ui/js/spice/wire.js | 123 ++++
ui/js/src/kimchi.api.js | 21 +
ui/js/src/kimchi.guest_main.js | 26 +-
ui/pages/guest.html.tmpl | 1 +
ui/pages/spice.html.tmpl | 138 +++++
26 files changed, 7581 insertions(+), 5 deletions(-)
create mode 100644 ui/js/spice/atKeynames.js
create mode 100644 ui/js/spice/bitmap.js
create mode 100644 ui/js/spice/cursor.js
create mode 100644 ui/js/spice/display.js
create mode 100644 ui/js/spice/enums.js
create mode 100644 ui/js/spice/inputs.js
create mode 100644 ui/js/spice/jsbn.js
create mode 100644 ui/js/spice/lz.js
create mode 100644 ui/js/spice/main.js
create mode 100644 ui/js/spice/png.js
create mode 100644 ui/js/spice/prng4.js
create mode 100644 ui/js/spice/quic.js
create mode 100644 ui/js/spice/rng.js
create mode 100644 ui/js/spice/rsa.js
create mode 100644 ui/js/spice/sha1.js
create mode 100644 ui/js/spice/spiceconn.js
create mode 100644 ui/js/spice/spicedataview.js
create mode 100644 ui/js/spice/spicemsg.js
create mode 100644 ui/js/spice/spicetype.js
create mode 100644 ui/js/spice/ticket.js
create mode 100644 ui/js/spice/utils.js
create mode 100644 ui/js/spice/wire.js
create mode 100644 ui/pages/spice.html.tmpl
diff --git a/ui/js/spice/atKeynames.js b/ui/js/spice/atKeynames.js
new file mode 100644
index 0000000..e1e27fd
--- /dev/null
+++ b/ui/js/spice/atKeynames.js
@@ -0,0 +1,183 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Aric Stewart <aric(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+/*
+ * Copyright 1990,91 by Thomas Roell, Dinkelscherben, Germany.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of Thomas Roell not be used in
+ * advertising or publicity pertaining to distribution of the software without
+ * specific, written prior permission. Thomas Roell makes no representations
+ * about the suitability of this software for any purpose. It is provided
+ * "as is" without express or implied warranty.
+ *
+ * THOMAS ROELL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THOMAS ROELL BE LIABLE FOR ANY SPECIAL, 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.
+ *
+ */
+/*
+ * Copyright (c) 1994-2003 by The XFree86 Project, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Except as contained in this notice, the name of the copyright holder(s)
+ * and author(s) shall not be used in advertising or otherwise to promote
+ * the sale, use or other dealings in this Software without prior written
+ * authorization from the copyright holder(s) and author(s).
+ */
+
+/*
+ * NOTE: The AT/MF keyboards can generate (via the 8042) two (MF: three)
+ * sets of scancodes. Set3 can only be generated by a MF keyboard.
+ * Set2 sends a makecode for keypress, and the same code prefixed by a
+ * F0 for keyrelease. This is a little bit ugly to handle. Thus we use
+ * here for X386 the PC/XT compatible Set1. This set uses 8bit scancodes.
+ * Bit 7 ist set if the key is released. The code E0 switches to a
+ * different meaning to add the new MF cursorkeys, while not breaking old
+ * applications. E1 is another special prefix. Since I assume that there
+ * will be further versions of PC/XT scancode compatible keyboards, we
+ * may be in trouble one day.
+ *
+ * IDEA: 1) Use Set2 on AT84 keyboards and translate it to MF Set3.
+ * 2) Use the keyboards native set and translate it to common keysyms.
+ */
+
+/*
+ * definition of the AT84/MF101/MF102 Keyboard:
+ * ============================================================
+ * Defined Key Cap Glyphs Pressed value
+ * Key Name Main Also (hex) (dec)
+ * ---------------- ---------- ------- ------ ------
+ */
+
+var KEY_Escape =/* Escape 0x01 */ 1
+var KEY_1 =/* 1 ! 0x02 */ 2
+var KEY_2 =/* 2 @ 0x03 */ 3
+var KEY_3 =/* 3 # 0x04 */ 4
+var KEY_4 =/* 4 $ 0x05 */ 5
+var KEY_5 =/* 5 % 0x06 */ 6
+var KEY_6 =/* 6 ^ 0x07 */ 7
+var KEY_7 =/* 7 & 0x08 */ 8
+var KEY_8 =/* 8 * 0x09 */ 9
+var KEY_9 =/* 9 ( 0x0a */ 10
+var KEY_0 =/* 0 ) 0x0b */ 11
+var KEY_Minus =/* - (Minus) _ (Under) 0x0c */ 12
+var KEY_Equal =/* = (Equal) + 0x0d */ 13
+var KEY_BackSpace =/* Back Space 0x0e */ 14
+var KEY_Tab =/* Tab 0x0f */ 15
+var KEY_Q =/* Q 0x10 */ 16
+var KEY_W =/* W 0x11 */ 17
+var KEY_E =/* E 0x12 */ 18
+var KEY_R =/* R 0x13 */ 19
+var KEY_T =/* T 0x14 */ 20
+var KEY_Y =/* Y 0x15 */ 21
+var KEY_U =/* U 0x16 */ 22
+var KEY_I =/* I 0x17 */ 23
+var KEY_O =/* O 0x18 */ 24
+var KEY_P =/* P 0x19 */ 25
+var KEY_LBrace =/* [ { 0x1a */ 26
+var KEY_RBrace =/* ] } 0x1b */ 27
+var KEY_Enter =/* Enter 0x1c */ 28
+var KEY_LCtrl =/* Ctrl(left) 0x1d */ 29
+var KEY_A =/* A 0x1e */ 30
+var KEY_S =/* S 0x1f */ 31
+var KEY_D =/* D 0x20 */ 32
+var KEY_F =/* F 0x21 */ 33
+var KEY_G =/* G 0x22 */ 34
+var KEY_H =/* H 0x23 */ 35
+var KEY_J =/* J 0x24 */ 36
+var KEY_K =/* K 0x25 */ 37
+var KEY_L =/* L 0x26 */ 38
+var KEY_SemiColon =/* ;(SemiColon) :(Colon) 0x27 */ 39
+var KEY_Quote =/* ' (Apostr) " (Quote) 0x28 */ 40
+var KEY_Tilde =/* ` (Accent) ~ (Tilde) 0x29 */ 41
+var KEY_ShiftL =/* Shift(left) 0x2a */ 42
+var KEY_BSlash =/* \(BckSlash) |(VertBar)0x2b */ 43
+var KEY_Z =/* Z 0x2c */ 44
+var KEY_X =/* X 0x2d */ 45
+var KEY_C =/* C 0x2e */ 46
+var KEY_V =/* V 0x2f */ 47
+var KEY_B =/* B 0x30 */ 48
+var KEY_N =/* N 0x31 */ 49
+var KEY_M =/* M 0x32 */ 50
+var KEY_Comma =/* , (Comma) < (Less) 0x33 */ 51
+var KEY_Period =/* . (Period) >(Greater)0x34 */ 52
+var KEY_Slash =/* / (Slash) ? 0x35 */ 53
+var KEY_ShiftR =/* Shift(right) 0x36 */ 54
+var KEY_KP_Multiply =/* * 0x37 */ 55
+var KEY_Alt =/* Alt(left) 0x38 */ 56
+var KEY_Space =/* (SpaceBar) 0x39 */ 57
+var KEY_CapsLock =/* CapsLock 0x3a */ 58
+var KEY_F1 =/* F1 0x3b */ 59
+var KEY_F2 =/* F2 0x3c */ 60
+var KEY_F3 =/* F3 0x3d */ 61
+var KEY_F4 =/* F4 0x3e */ 62
+var KEY_F5 =/* F5 0x3f */ 63
+var KEY_F6 =/* F6 0x40 */ 64
+var KEY_F7 =/* F7 0x41 */ 65
+var KEY_F8 =/* F8 0x42 */ 66
+var KEY_F9 =/* F9 0x43 */ 67
+var KEY_F10 =/* F10 0x44 */ 68
+var KEY_NumLock =/* NumLock 0x45 */ 69
+var KEY_ScrollLock =/* ScrollLock 0x46 */ 70
+var KEY_KP_7 =/* 7 Home 0x47 */ 71
+var KEY_KP_8 =/* 8 Up 0x48 */ 72
+var KEY_KP_9 =/* 9 PgUp 0x49 */ 73
+var KEY_KP_Minus =/* - (Minus) 0x4a */ 74
+var KEY_KP_4 =/* 4 Left 0x4b */ 75
+var KEY_KP_5 =/* 5 0x4c */ 76
+var KEY_KP_6 =/* 6 Right 0x4d */ 77
+var KEY_KP_Plus =/* + (Plus) 0x4e */ 78
+var KEY_KP_1 =/* 1 End 0x4f */ 79
+var KEY_KP_2 =/* 2 Down 0x50 */ 80
+var KEY_KP_3 =/* 3 PgDown 0x51 */ 81
+var KEY_KP_0 =/* 0 Insert 0x52 */ 82
+var KEY_KP_Decimal =/* . (Decimal) Delete 0x53 */ 83
+var KEY_SysReqest =/* SysReqest 0x54 */ 84
+ /* NOTUSED 0x55 */
+var KEY_Less =/* < (Less) >(Greater) 0x56 */ 86
+var KEY_F11 =/* F11 0x57 */ 87
+var KEY_F12 =/* F12 0x58 */ 88
+
+var KEY_Prefix0 =/* special 0x60 */ 96
+var KEY_Prefix1 =/* specail 0x61 */ 97
diff --git a/ui/js/spice/bitmap.js b/ui/js/spice/bitmap.js
new file mode 100644
index 0000000..03f5127
--- /dev/null
+++ b/ui/js/spice/bitmap.js
@@ -0,0 +1,51 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+/*----------------------------------------------------------------------------
+** bitmap.js
+** Handle SPICE_IMAGE_TYPE_BITMAP
+**--------------------------------------------------------------------------*/
+function convert_spice_bitmap_to_web(context, spice_bitmap)
+{
+ var ret;
+ var offset, x;
+ var u8 = new Uint8Array(spice_bitmap.data);
+ if (spice_bitmap.format != SPICE_BITMAP_FMT_32BIT &&
+ spice_bitmap.format != SPICE_BITMAP_FMT_RGBA)
+ return undefined;
+
+ ret = context.createImageData(spice_bitmap.x, spice_bitmap.y);
+ for (offset = 0; offset < (spice_bitmap.y * spice_bitmap.stride); )
+ for (x = 0; x < spice_bitmap.x; x++, offset += 4)
+ {
+ ret.data[offset + 0 ] = u8[offset + 2];
+ ret.data[offset + 1 ] = u8[offset + 1];
+ ret.data[offset + 2 ] = u8[offset + 0];
+
+ // FIXME - We effectively treat all images as having SPICE_IMAGE_FLAGS_HIGH_BITS_SET
+ if (spice_bitmap.format == SPICE_BITMAP_FMT_32BIT)
+ ret.data[offset + 3] = 255;
+ else
+ ret.data[offset + 3] = u8[offset];
+ }
+
+ return ret;
+}
diff --git a/ui/js/spice/cursor.js b/ui/js/spice/cursor.js
new file mode 100644
index 0000000..b3184c3
--- /dev/null
+++ b/ui/js/spice/cursor.js
@@ -0,0 +1,92 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+/*----------------------------------------------------------------------------
+** SpiceCursorConn
+** Drive the Spice Cursor Channel
+**--------------------------------------------------------------------------*/
+function SpiceCursorConn()
+{
+ SpiceConn.apply(this, arguments);
+}
+
+SpiceCursorConn.prototype = Object.create(SpiceConn.prototype);
+SpiceCursorConn.prototype.process_channel_message = function(msg)
+{
+ if (msg.type == SPICE_MSG_CURSOR_INIT)
+ {
+ var cursor_init = new SpiceMsgCursorInit(msg.data);
+ DEBUG > 1 && console.log("SpiceMsgCursorInit");
+ if (this.parent && this.parent.inputs &&
+ this.parent.inputs.mouse_mode == SPICE_MOUSE_MODE_SERVER)
+ {
+ // FIXME - this imagines that the server actually
+ // provides the current cursor position,
+ // instead of 0,0. As of May 11, 2012,
+ // that assumption was false :-(.
+ this.parent.inputs.mousex = cursor_init.position.x;
+ this.parent.inputs.mousey = cursor_init.position.y;
+ }
+ // FIXME - We don't handle most of the parameters here...
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_CURSOR_SET)
+ {
+ var cursor_set = new SpiceMsgCursorSet(msg.data);
+ DEBUG > 1 && console.log("SpiceMsgCursorSet");
+ if (cursor_set.flags & SPICE_CURSOR_FLAGS_NONE)
+ {
+ document.getElementById(this.parent.screen_id).style.cursor = "none";
+ return true;
+ }
+
+ if (cursor_set.flags > 0)
+ this.log_warn("FIXME: No support for cursor flags " + cursor_set.flags);
+
+ if (cursor_set.cursor.header.type != SPICE_CURSOR_TYPE_ALPHA)
+ {
+ this.log_warn("FIXME: No support for cursor type " + cursor_set.cursor.header.type);
+ return false;
+ }
+
+ this.set_cursor(cursor_set.cursor);
+
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_CURSOR_HIDE)
+ {
+ DEBUG > 1 && console.log("SpiceMsgCursorHide");
+ document.getElementById(this.parent.screen_id).style.cursor = "none";
+ return true;
+ }
+
+ return false;
+}
+
+SpiceCursorConn.prototype.set_cursor = function(cursor)
+{
+ var pngstr = create_rgba_png(cursor.header.height, cursor.header.width, cursor.data);
+ var curstr = 'url(data:image/png,' + pngstr + ') ' +
+ cursor.header.hot_spot_x + ' ' + cursor.header.hot_spot_y + ", default";
+ document.getElementById(this.parent.screen_id).style.cursor = curstr;
+}
diff --git a/ui/js/spice/display.js b/ui/js/spice/display.js
new file mode 100644
index 0000000..fdf9dc5
--- /dev/null
+++ b/ui/js/spice/display.js
@@ -0,0 +1,806 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+/*----------------------------------------------------------------------------
+** FIXME: putImageData does not support Alpha blending
+** or compositing. So if we have data in an ImageData
+** format, we have to draw it onto a context,
+** and then use drawImage to put it onto the target,
+** as drawImage does alpha.
+**--------------------------------------------------------------------------*/
+function putImageDataWithAlpha(context, d, x, y)
+{
+ var c = document.createElement("canvas");
+ var t = c.getContext("2d");
+ c.setAttribute('width', d.width);
+ c.setAttribute('height', d.height);
+ t.putImageData(d, 0, 0);
+ context.drawImage(c, x, y, d.width, d.height);
+}
+
+/*----------------------------------------------------------------------------
+** FIXME: Spice will send an image with '0' alpha when it is intended to
+** go on a surface w/no alpha. So in that case, we have to strip
+** out the alpha. The test case for this was flux box; in a Xspice
+** server, right click on the desktop to get the menu; the top bar
+** doesn't paint/highlight correctly w/out this change.
+**--------------------------------------------------------------------------*/
+function stripAlpha(d)
+{
+ var i;
+ for (i = 0; i < (d.width * d.height * 4); i += 4)
+ d.data[i + 3] = 255;
+}
+
+/*----------------------------------------------------------------------------
+** SpiceDisplayConn
+** Drive the Spice Display Channel
+**--------------------------------------------------------------------------*/
+function SpiceDisplayConn()
+{
+ SpiceConn.apply(this, arguments);
+}
+
+SpiceDisplayConn.prototype = Object.create(SpiceConn.prototype);
+SpiceDisplayConn.prototype.process_channel_message = function(msg)
+{
+ if (msg.type == SPICE_MSG_DISPLAY_MARK)
+ {
+ // FIXME - DISPLAY_MARK not implemented (may be hard or impossible)
+ this.known_unimplemented(msg.type, "Display Mark");
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_RESET)
+ {
+ DEBUG > 2 && console.log("Display reset");
+ this.surfaces[this.primary_surface].canvas.context.restore();
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_DRAW_COPY)
+ {
+ var draw_copy = new SpiceMsgDisplayDrawCopy(msg.data);
+
+ DEBUG > 1 && this.log_draw("DrawCopy", draw_copy);
+
+ if (! draw_copy.base.box.is_same_size(draw_copy.data.src_area))
+ this.log_warn("FIXME: DrawCopy src_area is a different size than base.box; we do not handle that yet.");
+ if (draw_copy.base.clip.type != SPICE_CLIP_TYPE_NONE)
+ this.log_warn("FIXME: DrawCopy we don't handle clipping yet");
+ if (draw_copy.data.rop_descriptor != SPICE_ROPD_OP_PUT)
+ this.log_warn("FIXME: DrawCopy we don't handle ropd type: " + draw_copy.data.rop_descriptor);
+ if (draw_copy.data.mask.flags)
+ this.log_warn("FIXME: DrawCopy we don't handle mask flag: " + draw_copy.data.mask.flags);
+ if (draw_copy.data.mask.bitmap)
+ this.log_warn("FIXME: DrawCopy we don't handle mask");
+
+ if (draw_copy.data && draw_copy.data.src_bitmap)
+ {
+ if (draw_copy.data.src_bitmap.descriptor.flags &&
+ draw_copy.data.src_bitmap.descriptor.flags != SPICE_IMAGE_FLAGS_CACHE_ME &&
+ draw_copy.data.src_bitmap.descriptor.flags != SPICE_IMAGE_FLAGS_HIGH_BITS_SET)
+ {
+ this.log_warn("FIXME: DrawCopy unhandled image flags: " + draw_copy.data.src_bitmap.descriptor.flags);
+ DEBUG <= 1 && this.log_draw("DrawCopy", draw_copy);
+ }
+
+ if (draw_copy.data.src_bitmap.descriptor.type == SPICE_IMAGE_TYPE_QUIC)
+ {
+ var canvas = this.surfaces[draw_copy.base.surface_id].canvas;
+ if (! draw_copy.data.src_bitmap.quic)
+ {
+ this.log_warn("FIXME: DrawCopy could not handle this QUIC file.");
+ return false;
+ }
+ var source_img = convert_spice_quic_to_web(canvas.context,
+ draw_copy.data.src_bitmap.quic);
+
+ return this.draw_copy_helper(
+ { base: draw_copy.base,
+ src_area: draw_copy.data.src_area,
+ image_data: source_img,
+ tag: "copyquic." + draw_copy.data.src_bitmap.quic.type,
+ has_alpha: (draw_copy.data.src_bitmap.quic.type == QUIC_IMAGE_TYPE_RGBA ? true : false) ,
+ descriptor : draw_copy.data.src_bitmap.descriptor
+ });
+ }
+ else if (draw_copy.data.src_bitmap.descriptor.type == SPICE_IMAGE_TYPE_FROM_CACHE ||
+ draw_copy.data.src_bitmap.descriptor.type == SPICE_IMAGE_TYPE_FROM_CACHE_LOSSLESS)
+ {
+ if (! this.cache || ! this.cache[draw_copy.data.src_bitmap.descriptor.id])
+ {
+ this.log_warn("FIXME: DrawCopy did not find image id " + draw_copy.data.src_bitmap.descriptor.id + " in cache.");
+ return false;
+ }
+
+ return this.draw_copy_helper(
+ { base: draw_copy.base,
+ src_area: draw_copy.data.src_area,
+ image_data: this.cache[draw_copy.data.src_bitmap.descriptor.id],
+ tag: "copycache." + draw_copy.data.src_bitmap.descriptor.id,
+ has_alpha: true, /* FIXME - may want this to be false... */
+ descriptor : draw_copy.data.src_bitmap.descriptor
+ });
+
+ /* FIXME - LOSSLESS CACHE ramifications not understood or handled */
+ }
+ else if (draw_copy.data.src_bitmap.descriptor.type == SPICE_IMAGE_TYPE_SURFACE)
+ {
+ var source_context = this.surfaces[draw_copy.data.src_bitmap.surface_id].canvas.context;
+ var target_context = this.surfaces[draw_copy.base.surface_id].canvas.context;
+
+ var source_img = source_context.getImageData(
+ draw_copy.data.src_area.left, draw_copy.data.src_area.top,
+ draw_copy.data.src_area.right - draw_copy.data.src_area.left,
+ draw_copy.data.src_area.bottom - draw_copy.data.src_area.top);
+ var computed_src_area = new SpiceRect;
+ computed_src_area.top = computed_src_area.left = 0;
+ computed_src_area.right = source_img.width;
+ computed_src_area.bottom = source_img.height;
+
+ /* FIXME - there is a potential optimization here.
+ That is, if the surface is from 0,0, and
+ both surfaces are alpha surfaces, you should
+ be able to just do a drawImage, which should
+ save time. */
+
+ return this.draw_copy_helper(
+ { base: draw_copy.base,
+ src_area: computed_src_area,
+ image_data: source_img,
+ tag: "copysurf." + draw_copy.data.src_bitmap.surface_id,
+ has_alpha: this.surfaces[draw_copy.data.src_bitmap.surface_id].format == SPICE_SURFACE_FMT_32_xRGB ? false : true,
+ descriptor : draw_copy.data.src_bitmap.descriptor
+ });
+
+ return true;
+ }
+ else if (draw_copy.data.src_bitmap.descriptor.type == SPICE_IMAGE_TYPE_JPEG)
+ {
+ if (! draw_copy.data.src_bitmap.jpeg)
+ {
+ this.log_warn("FIXME: DrawCopy could not handle this JPEG file.");
+ return false;
+ }
+
+ // FIXME - how lame is this. Be have it in binary format, and we have
+ // to put it into string to get it back into jpeg. Blech.
+ var tmpstr = "data:image/jpeg,";
+ var img = new Image;
+ var i;
+ var qdv = new Uint8Array(draw_copy.data.src_bitmap.jpeg.data);
+ for (i = 0; i < qdv.length; i++)
+ {
+ tmpstr += '%';
+ if (qdv[i] < 16)
+ tmpstr += '0';
+ tmpstr += qdv[i].toString(16);
+ }
+
+ img.o =
+ { base: draw_copy.base,
+ tag: "jpeg." + draw_copy.data.src_bitmap.surface_id,
+ descriptor : draw_copy.data.src_bitmap.descriptor,
+ sc : this,
+ };
+ img.onload = handle_draw_jpeg_onload;
+ img.src = tmpstr;
+
+ return true;
+ }
+ else if (draw_copy.data.src_bitmap.descriptor.type == SPICE_IMAGE_TYPE_JPEG_ALPHA)
+ {
+ if (! draw_copy.data.src_bitmap.jpeg_alpha)
+ {
+ this.log_warn("FIXME: DrawCopy could not handle this JPEG ALPHA file.");
+ return false;
+ }
+
+ // FIXME - how lame is this. Be have it in binary format, and we have
+ // to put it into string to get it back into jpeg. Blech.
+ var tmpstr = "data:image/jpeg,";
+ var img = new Image;
+ var i;
+ var qdv = new Uint8Array(draw_copy.data.src_bitmap.jpeg_alpha.data);
+ for (i = 0; i < qdv.length; i++)
+ {
+ tmpstr += '%';
+ if (qdv[i] < 16)
+ tmpstr += '0';
+ tmpstr += qdv[i].toString(16);
+ }
+
+ img.o =
+ { base: draw_copy.base,
+ tag: "jpeg." + draw_copy.data.src_bitmap.surface_id,
+ descriptor : draw_copy.data.src_bitmap.descriptor,
+ sc : this,
+ };
+
+ if (this.surfaces[draw_copy.base.surface_id].format == SPICE_SURFACE_FMT_32_ARGB)
+ {
+
+ var canvas = this.surfaces[draw_copy.base.surface_id].canvas;
+ img.alpha_img = convert_spice_lz_to_web(canvas.context,
+ draw_copy.data.src_bitmap.jpeg_alpha.alpha);
+ }
+ img.onload = handle_draw_jpeg_onload;
+ img.src = tmpstr;
+
+ return true;
+ }
+ else if (draw_copy.data.src_bitmap.descriptor.type == SPICE_IMAGE_TYPE_BITMAP)
+ {
+ var canvas = this.surfaces[draw_copy.base.surface_id].canvas;
+ if (! draw_copy.data.src_bitmap.bitmap)
+ {
+ this.log_err("null bitmap");
+ return false;
+ }
+
+ var source_img = convert_spice_bitmap_to_web(canvas.context,
+ draw_copy.data.src_bitmap.bitmap);
+ if (! source_img)
+ {
+ this.log_warn("FIXME: Unable to interpret bitmap of format: " +
+ draw_copy.data.src_bitmap.bitmap.format);
+ return false;
+ }
+
+ return this.draw_copy_helper(
+ { base: draw_copy.base,
+ src_area: draw_copy.data.src_area,
+ image_data: source_img,
+ tag: "bitmap." + draw_copy.data.src_bitmap.bitmap.format,
+ has_alpha: draw_copy.data.src_bitmap.bitmap == SPICE_BITMAP_FMT_32BIT ? false : true,
+ descriptor : draw_copy.data.src_bitmap.descriptor
+ });
+ }
+ else if (draw_copy.data.src_bitmap.descriptor.type == SPICE_IMAGE_TYPE_LZ_RGB)
+ {
+ var canvas = this.surfaces[draw_copy.base.surface_id].canvas;
+ if (! draw_copy.data.src_bitmap.lz_rgb)
+ {
+ this.log_err("null lz_rgb ");
+ return false;
+ }
+
+ if (draw_copy.data.src_bitmap.lz_rgb.top_down != 1)
+ this.log_warn("FIXME: Implement non top down support for lz_rgb");
+
+ var source_img = convert_spice_lz_to_web(canvas.context,
+ draw_copy.data.src_bitmap.lz_rgb);
+ if (! source_img)
+ {
+ this.log_warn("FIXME: Unable to interpret bitmap of type: " +
+ draw_copy.data.src_bitmap.lz_rgb.type);
+ return false;
+ }
+
+ return this.draw_copy_helper(
+ { base: draw_copy.base,
+ src_area: draw_copy.data.src_area,
+ image_data: source_img,
+ tag: "lz_rgb." + draw_copy.data.src_bitmap.lz_rgb.type,
+ has_alpha: draw_copy.data.src_bitmap.lz_rgb.type == LZ_IMAGE_TYPE_RGBA ? true : false ,
+ descriptor : draw_copy.data.src_bitmap.descriptor
+ });
+ }
+ else
+ {
+ this.log_warn("FIXME: DrawCopy unhandled image type: " + draw_copy.data.src_bitmap.descriptor.type);
+ this.log_draw("DrawCopy", draw_copy);
+ return false;
+ }
+ }
+
+ this.log_warn("FIXME: DrawCopy no src_bitmap.");
+ return false;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_DRAW_FILL)
+ {
+ var draw_fill = new SpiceMsgDisplayDrawFill(msg.data);
+
+ DEBUG > 1 && this.log_draw("DrawFill", draw_fill);
+
+ if (draw_fill.data.rop_descriptor != SPICE_ROPD_OP_PUT)
+ this.log_warn("FIXME: DrawFill we don't handle ropd type: " + draw_fill.data.rop_descriptor);
+ if (draw_fill.data.mask.flags)
+ this.log_warn("FIXME: DrawFill we don't handle mask flag: " + draw_fill.data.mask.flags);
+ if (draw_fill.data.mask.bitmap)
+ this.log_warn("FIXME: DrawFill we don't handle mask");
+
+ if (draw_fill.data.brush.type == SPICE_BRUSH_TYPE_SOLID)
+ {
+ // FIXME - do brushes ever have alpha?
+ var color = draw_fill.data.brush.color & 0xffffff;
+ var color_str = "rgb(" + (color >> 16) + ", " + ((color >> 8) & 0xff) + ", " + (color & 0xff) + ")";
+ this.surfaces[draw_fill.base.surface_id].canvas.context.fillStyle = color_str;
+
+ this.surfaces[draw_fill.base.surface_id].canvas.context.fillRect(
+ draw_fill.base.box.left, draw_fill.base.box.top,
+ draw_fill.base.box.right - draw_fill.base.box.left,
+ draw_fill.base.box.bottom - draw_fill.base.box.top);
+
+ if (DUMP_DRAWS && this.parent.dump_id)
+ {
+ var debug_canvas = document.createElement("canvas");
+ debug_canvas.setAttribute('width', this.surfaces[draw_fill.base.surface_id].canvas.width);
+ debug_canvas.setAttribute('height', this.surfaces[draw_fill.base.surface_id].canvas.height);
+ debug_canvas.setAttribute('id', "fillbrush." + draw_fill.base.surface_id + "." + this.surfaces[draw_fill.base.surface_id].draw_count);
+ debug_canvas.getContext("2d").fillStyle = color_str;
+ debug_canvas.getContext("2d").fillRect(
+ draw_fill.base.box.left, draw_fill.base.box.top,
+ draw_fill.base.box.right - draw_fill.base.box.left,
+ draw_fill.base.box.bottom - draw_fill.base.box.top);
+ document.getElementById(this.parent.dump_id).appendChild(debug_canvas);
+ }
+
+ this.surfaces[draw_fill.base.surface_id].draw_count++;
+
+ }
+ else
+ {
+ this.log_warn("FIXME: DrawFill can't handle brush type: " + draw_fill.data.brush.type);
+ }
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_COPY_BITS)
+ {
+ var copy_bits = new SpiceMsgDisplayCopyBits(msg.data);
+
+ DEBUG > 1 && this.log_draw("CopyBits", copy_bits);
+
+ var source_canvas = this.surfaces[copy_bits.base.surface_id].canvas;
+ var source_context = source_canvas.context;
+
+ var width = source_canvas.width - copy_bits.src_pos.x;
+ var height = source_canvas.height - copy_bits.src_pos.y;
+ if (width > (copy_bits.base.box.right - copy_bits.base.box.left))
+ width = copy_bits.base.box.right - copy_bits.base.box.left;
+ if (height > (copy_bits.base.box.bottom - copy_bits.base.box.top))
+ height = copy_bits.base.box.bottom - copy_bits.base.box.top;
+
+ var source_img = source_context.getImageData(
+ copy_bits.src_pos.x, copy_bits.src_pos.y, width, height);
+ //source_context.putImageData(source_img, copy_bits.base.box.left, copy_bits.base.box.top);
+ putImageDataWithAlpha(source_context, source_img, copy_bits.base.box.left, copy_bits.base.box.top);
+
+ if (DUMP_DRAWS && this.parent.dump_id)
+ {
+ var debug_canvas = document.createElement("canvas");
+ debug_canvas.setAttribute('width', width);
+ debug_canvas.setAttribute('height', height);
+ debug_canvas.setAttribute('id', "copybits" + copy_bits.base.surface_id + "." + this.surfaces[copy_bits.base.surface_id].draw_count);
+ debug_canvas.getContext("2d").putImageData(source_img, 0, 0);
+ document.getElementById(this.parent.dump_id).appendChild(debug_canvas);
+ }
+
+
+ this.surfaces[copy_bits.base.surface_id].draw_count++;
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_INVAL_ALL_PALETTES)
+ {
+ this.known_unimplemented(msg.type, "Inval All Palettes");
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_SURFACE_CREATE)
+ {
+ if (! ("surfaces" in this))
+ this.surfaces = [];
+
+ var m = new SpiceMsgSurfaceCreate(msg.data);
+ DEBUG > 1 && console.log(this.type + ": MsgSurfaceCreate id " + m.surface.surface_id
+ + "; " + m.surface.width + "x" + m.surface.height
+ + "; format " + m.surface.format
+ + "; flags " + m.surface.flags);
+ if (m.surface.format != SPICE_SURFACE_FMT_32_xRGB &&
+ m.surface.format != SPICE_SURFACE_FMT_32_ARGB)
+ {
+ this.log_warn("FIXME: cannot handle surface format " + m.surface.format + " yet.");
+ return false;
+ }
+
+ var canvas = document.createElement("canvas");
+ canvas.setAttribute('width', m.surface.width);
+ canvas.setAttribute('height', m.surface.height);
+ canvas.setAttribute('id', "spice_surface_" + m.surface.surface_id);
+ canvas.setAttribute('tabindex', m.surface.surface_id);
+ canvas.context = canvas.getContext("2d");
+
+ if (DUMP_CANVASES && this.parent.dump_id)
+ document.getElementById(this.parent.dump_id).appendChild(canvas);
+
+ m.surface.canvas = canvas;
+ m.surface.draw_count = 0;
+ this.surfaces[m.surface.surface_id] = m.surface;
+
+ if (m.surface.flags & SPICE_SURFACE_FLAGS_PRIMARY)
+ {
+ this.primary_surface = m.surface.surface_id;
+
+ /* This .save() is done entirely to enable SPICE_MSG_DISPLAY_RESET */
+ canvas.context.save();
+ document.getElementById(this.parent.screen_id).appendChild(canvas);
+ document.getElementById(this.parent.screen_id).setAttribute('width', m.surface.width);
+ document.getElementById(this.parent.screen_id).setAttribute('height', m.surface.height);
+ this.hook_events();
+ }
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_SURFACE_DESTROY)
+ {
+ var m = new SpiceMsgSurfaceDestroy(msg.data);
+ DEBUG > 1 && console.log(this.type + ": MsgSurfaceDestroy id " + m.surface_id);
+ this.delete_surface(m.surface_id);
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_STREAM_CREATE)
+ {
+ var m = new SpiceMsgDisplayStreamCreate(msg.data);
+ DEBUG > 1 && console.log(this.type + ": MsgStreamCreate id" + m.id);
+ if (!this.streams)
+ this.streams = new Array();
+ if (this.streams[m.id])
+ console.log("Stream already exists");
+ else
+ this.streams[m.id] = m;
+ if (m.codec_type != SPICE_VIDEO_CODEC_TYPE_MJPEG)
+ console.log("Unhandled stream codec: "+m.codec_type);
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_STREAM_DATA)
+ {
+ var m = new SpiceMsgDisplayStreamData(msg.data);
+ if (!this.streams[m.base.id])
+ {
+ console.log("no stream for data");
+ return false;
+ }
+ if (this.streams[m.base.id].codec_type === SPICE_VIDEO_CODEC_TYPE_MJPEG)
+ {
+ var tmpstr = "data:image/jpeg,";
+ var img = new Image;
+ var i;
+ for (i = 0; i < m.data.length; i++)
+ {
+ tmpstr += '%';
+ if (m.data[i] < 16)
+ tmpstr += '0';
+ tmpstr += m.data[i].toString(16);
+ }
+ var strm_base = new SpiceMsgDisplayBase();
+ strm_base.surface_id = this.streams[m.base.id].surface_id;
+ strm_base.box = this.streams[m.base.id].dest;
+ strm_base.clip = this.streams[m.base.id].clip;
+ img.o =
+ { base: strm_base,
+ tag: "mjpeg." + m.base.id,
+ descriptor: null,
+ sc : this,
+ };
+ img.onload = handle_draw_jpeg_onload;
+ img.src = tmpstr;
+ }
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_STREAM_CLIP)
+ {
+ var m = new SpiceMsgDisplayStreamClip(msg.data);
+ DEBUG > 1 && console.log(this.type + ": MsgStreamClip id" + m.id);
+ this.streams[m.id].clip = m.clip;
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_STREAM_DESTROY)
+ {
+ var m = new SpiceMsgDisplayStreamDestroy(msg.data);
+ DEBUG > 1 && console.log(this.type + ": MsgStreamDestroy id" + m.id);
+ this.streams[m.id] = undefined;
+ return true;
+ }
+
+ return false;
+}
+
+SpiceDisplayConn.prototype.delete_surface = function(surface_id)
+{
+ var canvas = document.getElementById("spice_surface_" + surface_id);
+ if (DUMP_CANVASES && this.parent.dump_id)
+ document.getElementById(this.parent.dump_id).removeChild(canvas);
+ if (this.primary_surface == surface_id)
+ {
+ this.unhook_events();
+ this.primary_surface = undefined;
+ document.getElementById(this.parent.screen_id).removeChild(canvas);
+ }
+
+ delete this.surfaces[surface_id];
+}
+
+
+SpiceDisplayConn.prototype.draw_copy_helper = function(o)
+{
+
+ var canvas = this.surfaces[o.base.surface_id].canvas;
+ if (o.has_alpha)
+ {
+ /* FIXME - This is based on trial + error, not a serious thoughtful
+ analysis of what Spice requires. See display.js for more. */
+ if (this.surfaces[o.base.surface_id].format == SPICE_SURFACE_FMT_32_xRGB)
+ {
+ stripAlpha(o.image_data);
+ canvas.context.putImageData(o.image_data, o.base.box.left, o.base.box.top);
+ }
+ else
+ putImageDataWithAlpha(canvas.context, o.image_data,
+ o.base.box.left, o.base.box.top);
+ }
+ else
+ canvas.context.putImageData(o.image_data, o.base.box.left, o.base.box.top);
+
+ if (o.src_area.left > 0 || o.src_area.top > 0)
+ {
+ this.log_warn("FIXME: DrawCopy not shifting draw copies just yet...");
+ }
+
+ if (o.descriptor && (o.descriptor.flags & SPICE_IMAGE_FLAGS_CACHE_ME))
+ {
+ if (! ("cache" in this))
+ this.cache = [];
+ this.cache[o.descriptor.id] = o.image_data;
+ }
+
+ if (DUMP_DRAWS && this.parent.dump_id)
+ {
+ var debug_canvas = document.createElement("canvas");
+ debug_canvas.setAttribute('width', o.image_data.width);
+ debug_canvas.setAttribute('height', o.image_data.height);
+ debug_canvas.setAttribute('id', o.tag + "." +
+ this.surfaces[o.base.surface_id].draw_count + "." +
+ o.base.surface_id + "@" + o.base.box.left + "x" + o.base.box.top);
+ debug_canvas.getContext("2d").putImageData(o.image_data, 0, 0);
+ document.getElementById(this.parent.dump_id).appendChild(debug_canvas);
+ }
+
+ this.surfaces[o.base.surface_id].draw_count++;
+
+ return true;
+}
+
+
+SpiceDisplayConn.prototype.log_draw = function(prefix, draw)
+{
+ var str = prefix + "." + draw.base.surface_id + "." + this.surfaces[draw.base.surface_id].draw_count + ": ";
+ str += "base.box " + draw.base.box.left + ", " + draw.base.box.top + " to " +
+ draw.base.box.right + ", " + draw.base.box.bottom;
+ str += "; clip.type " + draw.base.clip.type;
+
+ if (draw.data)
+ {
+ if (draw.data.src_area)
+ str += "; src_area " + draw.data.src_area.left + ", " + draw.data.src_area.top + " to "
+ + draw.data.src_area.right + ", " + draw.data.src_area.bottom;
+
+ if (draw.data.src_bitmap && draw.data.src_bitmap != null)
+ {
+ str += "; src_bitmap id: " + draw.data.src_bitmap.descriptor.id;
+ str += "; src_bitmap width " + draw.data.src_bitmap.descriptor.width + ", height " + draw.data.src_bitmap.descriptor.height;
+ str += "; src_bitmap type " + draw.data.src_bitmap.descriptor.type + ", flags " + draw.data.src_bitmap.descriptor.flags;
+ if (draw.data.src_bitmap.surface_id !== undefined)
+ str += "; src_bitmap surface_id " + draw.data.src_bitmap.surface_id;
+ if (draw.data.src_bitmap.quic)
+ str += "; QUIC type " + draw.data.src_bitmap.quic.type +
+ "; width " + draw.data.src_bitmap.quic.width +
+ "; height " + draw.data.src_bitmap.quic.height ;
+ if (draw.data.src_bitmap.lz_rgb)
+ str += "; LZ_RGB length " + draw.data.src_bitmap.lz_rgb.length +
+ "; magic " + draw.data.src_bitmap.lz_rgb.magic +
+ "; version 0x" + draw.data.src_bitmap.lz_rgb.version.toString(16) +
+ "; type " + draw.data.src_bitmap.lz_rgb.type +
+ "; width " + draw.data.src_bitmap.lz_rgb.width +
+ "; height " + draw.data.src_bitmap.lz_rgb.height +
+ "; stride " + draw.data.src_bitmap.lz_rgb.stride +
+ "; top down " + draw.data.src_bitmap.lz_rgb.top_down;
+ }
+ else
+ str += "; src_bitmap is null";
+
+ if (draw.data.brush)
+ {
+ if (draw.data.brush.type == SPICE_BRUSH_TYPE_SOLID)
+ str += "; brush.color 0x" + draw.data.brush.color.toString(16);
+ if (draw.data.brush.type == SPICE_BRUSH_TYPE_PATTERN)
+ {
+ str += "; brush.pat ";
+ if (draw.data.brush.pattern.pat != null)
+ str += "[SpiceImage]";
+ else
+ str += "[null]";
+ str += " at " + draw.data.brush.pattern.pos.x + ", " + draw.data.brush.pattern.pos.y;
+ }
+ }
+
+ str += "; rop_descriptor " + draw.data.rop_descriptor;
+ if (draw.data.scale_mode !== undefined)
+ str += "; scale_mode " + draw.data.scale_mode;
+ str += "; mask.flags " + draw.data.mask.flags;
+ str += "; mask.pos " + draw.data.mask.pos.x + ", " + draw.data.mask.pos.y;
+ if (draw.data.mask.bitmap != null)
+ {
+ str += "; mask.bitmap width " + draw.data.mask.bitmap.descriptor.width + ", height " + draw.data.mask.bitmap.descriptor.height;
+ str += "; mask.bitmap type " + draw.data.mask.bitmap.descriptor.type + ", flags " + draw.data.mask.bitmap.descriptor.flags;
+ }
+ else
+ str += "; mask.bitmap is null";
+ }
+
+ console.log(str);
+}
+
+SpiceDisplayConn.prototype.hook_events = function()
+{
+ if (this.primary_surface !== undefined)
+ {
+ var canvas = this.surfaces[this.primary_surface].canvas;
+ canvas.sc = this.parent;
+ canvas.addEventListener('mousemove', handle_mousemove);
+ canvas.addEventListener('mousedown', handle_mousedown);
+ canvas.addEventListener('contextmenu', handle_contextmenu);
+ canvas.addEventListener('mouseup', handle_mouseup);
+ canvas.addEventListener('keydown', handle_keydown);
+ canvas.addEventListener('keyup', handle_keyup);
+ canvas.addEventListener('mouseout', handle_mouseout);
+ canvas.addEventListener('mouseover', handle_mouseover);
+ canvas.addEventListener('mousewheel', handle_mousewheel);
+ canvas.focus();
+ }
+}
+
+SpiceDisplayConn.prototype.unhook_events = function()
+{
+ if (this.primary_surface !== undefined)
+ {
+ var canvas = this.surfaces[this.primary_surface].canvas;
+ canvas.removeEventListener('mousemove', handle_mousemove);
+ canvas.removeEventListener('mousedown', handle_mousedown);
+ canvas.removeEventListener('contextmenu', handle_contextmenu);
+ canvas.removeEventListener('mouseup', handle_mouseup);
+ canvas.removeEventListener('keydown', handle_keydown);
+ canvas.removeEventListener('keyup', handle_keyup);
+ canvas.removeEventListener('mouseout', handle_mouseout);
+ canvas.removeEventListener('mouseover', handle_mouseover);
+ canvas.removeEventListener('mousewheel', handle_mousewheel);
+ }
+}
+
+
+SpiceDisplayConn.prototype.destroy_surfaces = function()
+{
+ for (var s in this.surfaces)
+ {
+ this.delete_surface(this.surfaces[s].surface_id);
+ }
+
+ this.surfaces = undefined;
+}
+
+
+function handle_mouseover(e)
+{
+ this.focus();
+}
+
+function handle_mouseout(e)
+{
+ this.blur();
+}
+
+function handle_draw_jpeg_onload()
+{
+ var temp_canvas = null;
+ var context;
+
+ /*------------------------------------------------------------
+ ** FIXME:
+ ** The helper should be extended to be able to handle actual HtmlImageElements
+ ** ...and the cache should be modified to do so as well
+ **----------------------------------------------------------*/
+ if (this.o.sc.surfaces[this.o.base.surface_id] === undefined)
+ {
+ // This can happen; if the jpeg image loads after our surface
+ // has been destroyed (e.g. open a menu, close it quickly),
+ // we'll find we have no surface.
+ DEBUG > 2 && this.o.sc.log_info("Discarding jpeg; presumed lost surface " + this.o.base.surface_id);
+ temp_canvas = document.createElement("canvas");
+ temp_canvas.setAttribute('width', this.o.base.box.right);
+ temp_canvas.setAttribute('height', this.o.base.box.bottom);
+ context = temp_canvas.getContext("2d");
+ }
+ else
+ context = this.o.sc.surfaces[this.o.base.surface_id].canvas.context;
+
+ if (this.alpha_img)
+ {
+ var c = document.createElement("canvas");
+ var t = c.getContext("2d");
+ c.setAttribute('width', this.alpha_img.width);
+ c.setAttribute('height', this.alpha_img.height);
+ t.putImageData(this.alpha_img, 0, 0);
+ t.globalCompositeOperation = 'source-in';
+ t.drawImage(this, 0, 0);
+
+ context.drawImage(c, this.o.base.box.left, this.o.base.box.top);
+
+ if (this.o.descriptor &&
+ (this.o.descriptor.flags & SPICE_IMAGE_FLAGS_CACHE_ME))
+ {
+ if (! ("cache" in this.o.sc))
+ this.o.sc.cache = [];
+
+ this.o.sc.cache[this.o.descriptor.id] =
+ t.getImageData(0, 0,
+ this.alpha_img.width,
+ this.alpha_img.height);
+ }
+ }
+ else
+ {
+ context.drawImage(this, this.o.base.box.left, this.o.base.box.top);
+
+ if (this.o.descriptor &&
+ (this.o.descriptor.flags & SPICE_IMAGE_FLAGS_CACHE_ME))
+ {
+ if (! ("cache" in this.o.sc))
+ this.o.sc.cache = [];
+
+ this.o.sc.cache[this.o.descriptor.id] =
+ context.getImageData(this.o.base.box.left, this.o.base.box.top,
+ this.o.base.box.right - this.o.base.box.left,
+ this.o.base.box.bottom - this.o.base.box.top);
+ }
+ }
+
+ if (temp_canvas == null)
+ {
+ if (DUMP_DRAWS && this.o.sc.parent.dump_id)
+ {
+ var debug_canvas = document.createElement("canvas");
+ debug_canvas.setAttribute('id', this.o.tag + "." +
+ this.o.sc.surfaces[this.o.base.surface_id].draw_count + "." +
+ this.o.base.surface_id + "@" + this.o.base.box.left + "x" + this.o.base.box.top);
+ debug_canvas.getContext("2d").drawImage(this, 0, 0);
+ document.getElementById(this.o.sc.parent.dump_id).appendChild(debug_canvas);
+ }
+
+ this.o.sc.surfaces[this.o.base.surface_id].draw_count++;
+ }
+}
diff --git a/ui/js/spice/enums.js b/ui/js/spice/enums.js
new file mode 100644
index 0000000..fd42a14
--- /dev/null
+++ b/ui/js/spice/enums.js
@@ -0,0 +1,282 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+/*----------------------------------------------------------------------------
+** enums.js
+** 'constants' for Spice
+**--------------------------------------------------------------------------*/
+var SPICE_MAGIC = "REDQ";
+var SPICE_VERSION_MAJOR = 2;
+var SPICE_VERSION_MINOR = 2;
+
+var SPICE_CONNECT_TIMEOUT = (30 * 1000);
+
+var SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION = 0;
+var SPICE_COMMON_CAP_AUTH_SPICE = 1;
+var SPICE_COMMON_CAP_AUTH_SASL = 2;
+var SPICE_COMMON_CAP_MINI_HEADER = 3;
+
+var SPICE_TICKET_KEY_PAIR_LENGTH = 1024;
+var SPICE_TICKET_PUBKEY_BYTES = (SPICE_TICKET_KEY_PAIR_LENGTH / 8 + 34);
+
+var SPICE_LINK_ERR_OK = 0,
+ SPICE_LINK_ERR_ERROR = 1,
+ SPICE_LINK_ERR_INVALID_MAGIC = 2,
+ SPICE_LINK_ERR_INVALID_DATA = 3,
+ SPICE_LINK_ERR_VERSION_MISMATCH = 4,
+ SPICE_LINK_ERR_NEED_SECURED = 5,
+ SPICE_LINK_ERR_NEED_UNSECURED = 6,
+ SPICE_LINK_ERR_PERMISSION_DENIED = 7,
+ SPICE_LINK_ERR_BAD_CONNECTION_ID = 8,
+ SPICE_LINK_ERR_CHANNEL_NOT_AVAILABLE = 9;
+
+var SPICE_MSG_MIGRATE = 1;
+var SPICE_MSG_MIGRATE_DATA = 2;
+var SPICE_MSG_SET_ACK = 3;
+var SPICE_MSG_PING = 4;
+var SPICE_MSG_WAIT_FOR_CHANNELS = 5;
+var SPICE_MSG_DISCONNECTING = 6;
+var SPICE_MSG_NOTIFY = 7;
+var SPICE_MSG_LIST = 8;
+
+var SPICE_MSG_MAIN_MIGRATE_BEGIN = 101;
+var SPICE_MSG_MAIN_MIGRATE_CANCEL = 102;
+var SPICE_MSG_MAIN_INIT = 103;
+var SPICE_MSG_MAIN_CHANNELS_LIST = 104;
+var SPICE_MSG_MAIN_MOUSE_MODE = 105;
+var SPICE_MSG_MAIN_MULTI_MEDIA_TIME = 106;
+var SPICE_MSG_MAIN_AGENT_CONNECTED = 107;
+var SPICE_MSG_MAIN_AGENT_DISCONNECTED = 108;
+var SPICE_MSG_MAIN_AGENT_DATA = 109;
+var SPICE_MSG_MAIN_AGENT_TOKEN = 110;
+var SPICE_MSG_MAIN_MIGRATE_SWITCH_HOST = 111;
+var SPICE_MSG_MAIN_MIGRATE_END = 112;
+var SPICE_MSG_MAIN_NAME = 113;
+var SPICE_MSG_MAIN_UUID = 114;
+var SPICE_MSG_END_MAIN = 115;
+
+
+var SPICE_MSGC_ACK_SYNC = 1;
+var SPICE_MSGC_ACK = 2;
+var SPICE_MSGC_PONG = 3;
+var SPICE_MSGC_MIGRATE_FLUSH_MARK = 4;
+var SPICE_MSGC_MIGRATE_DATA = 5;
+var SPICE_MSGC_DISCONNECTING = 6;
+
+
+var SPICE_MSGC_MAIN_CLIENT_INFO = 101;
+var SPICE_MSGC_MAIN_MIGRATE_CONNECTED = 102;
+var SPICE_MSGC_MAIN_MIGRATE_CONNECT_ERROR = 103;
+var SPICE_MSGC_MAIN_ATTACH_CHANNELS = 104;
+var SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST = 105;
+var SPICE_MSGC_MAIN_AGENT_START = 106;
+var SPICE_MSGC_MAIN_AGENT_DATA = 107;
+var SPICE_MSGC_MAIN_AGENT_TOKEN = 108;
+var SPICE_MSGC_MAIN_MIGRATE_END = 109;
+var SPICE_MSGC_END_MAIN = 110;
+
+var SPICE_MSG_DISPLAY_MODE = 101;
+var SPICE_MSG_DISPLAY_MARK = 102;
+var SPICE_MSG_DISPLAY_RESET = 103;
+var SPICE_MSG_DISPLAY_COPY_BITS = 104;
+var SPICE_MSG_DISPLAY_INVAL_LIST = 105;
+var SPICE_MSG_DISPLAY_INVAL_ALL_PIXMAPS = 106;
+var SPICE_MSG_DISPLAY_INVAL_PALETTE = 107;
+var SPICE_MSG_DISPLAY_INVAL_ALL_PALETTES= 108;
+
+var SPICE_MSG_DISPLAY_STREAM_CREATE = 122;
+var SPICE_MSG_DISPLAY_STREAM_DATA = 123;
+var SPICE_MSG_DISPLAY_STREAM_CLIP = 124;
+var SPICE_MSG_DISPLAY_STREAM_DESTROY = 125;
+var SPICE_MSG_DISPLAY_STREAM_DESTROY_ALL= 126;
+
+var SPICE_MSG_DISPLAY_DRAW_FILL = 302;
+var SPICE_MSG_DISPLAY_DRAW_OPAQUE = 303;
+var SPICE_MSG_DISPLAY_DRAW_COPY = 304;
+var SPICE_MSG_DISPLAY_DRAW_BLEND = 305;
+var SPICE_MSG_DISPLAY_DRAW_BLACKNESS = 306;
+var SPICE_MSG_DISPLAY_DRAW_WHITENESS = 307;
+var SPICE_MSG_DISPLAY_DRAW_INVERS = 308;
+var SPICE_MSG_DISPLAY_DRAW_ROP3 = 309;
+var SPICE_MSG_DISPLAY_DRAW_STROKE = 310;
+var SPICE_MSG_DISPLAY_DRAW_TEXT = 311;
+var SPICE_MSG_DISPLAY_DRAW_TRANSPARENT = 312;
+var SPICE_MSG_DISPLAY_DRAW_ALPHA_BLEND = 313;
+var SPICE_MSG_DISPLAY_SURFACE_CREATE = 314;
+var SPICE_MSG_DISPLAY_SURFACE_DESTROY = 315;
+
+var SPICE_MSGC_DISPLAY_INIT = 101;
+
+var SPICE_MSG_INPUTS_INIT = 101;
+var SPICE_MSG_INPUTS_KEY_MODIFIERS = 102;
+
+var SPICE_MSG_INPUTS_MOUSE_MOTION_ACK = 111;
+
+var SPICE_MSGC_INPUTS_KEY_DOWN = 101;
+var SPICE_MSGC_INPUTS_KEY_UP = 102;
+var SPICE_MSGC_INPUTS_KEY_MODIFIERS = 103;
+
+var SPICE_MSGC_INPUTS_MOUSE_MOTION = 111;
+var SPICE_MSGC_INPUTS_MOUSE_POSITION = 112;
+var SPICE_MSGC_INPUTS_MOUSE_PRESS = 113;
+var SPICE_MSGC_INPUTS_MOUSE_RELEASE = 114;
+
+var SPICE_MSG_CURSOR_INIT = 101;
+var SPICE_MSG_CURSOR_RESET = 102;
+var SPICE_MSG_CURSOR_SET = 103;
+var SPICE_MSG_CURSOR_MOVE = 104;
+var SPICE_MSG_CURSOR_HIDE = 105;
+var SPICE_MSG_CURSOR_TRAIL = 106;
+var SPICE_MSG_CURSOR_INVAL_ONE = 107;
+var SPICE_MSG_CURSOR_INVAL_ALL = 108;
+
+
+var SPICE_CHANNEL_MAIN = 1;
+var SPICE_CHANNEL_DISPLAY = 2;
+var SPICE_CHANNEL_INPUTS = 3;
+var SPICE_CHANNEL_CURSOR = 4;
+var SPICE_CHANNEL_PLAYBACK = 5;
+var SPICE_CHANNEL_RECORD = 6;
+var SPICE_CHANNEL_TUNNEL = 7;
+var SPICE_CHANNEL_SMARTCARD = 8;
+var SPICE_CHANNEL_USBREDIR = 9;
+
+var SPICE_SURFACE_FLAGS_PRIMARY = (1 << 0);
+
+var SPICE_NOTIFY_SEVERITY_INFO = 0;
+var SPICE_NOTIFY_SEVERITY_WARN = 1;
+var SPICE_NOTIFY_SEVERITY_ERROR = 2;
+
+var SPICE_MOUSE_MODE_SERVER = (1 << 0),
+ SPICE_MOUSE_MODE_CLIENT = (1 << 1),
+ SPICE_MOUSE_MODE_MASK = 0x3;
+
+var SPICE_CLIP_TYPE_NONE = 0;
+var SPICE_CLIP_TYPE_RECTS = 1;
+
+var SPICE_IMAGE_TYPE_BITMAP = 0;
+var SPICE_IMAGE_TYPE_QUIC = 1;
+var SPICE_IMAGE_TYPE_RESERVED = 2;
+var SPICE_IMAGE_TYPE_LZ_PLT = 100;
+var SPICE_IMAGE_TYPE_LZ_RGB = 101;
+var SPICE_IMAGE_TYPE_GLZ_RGB = 102;
+var SPICE_IMAGE_TYPE_FROM_CACHE = 103;
+var SPICE_IMAGE_TYPE_SURFACE = 104;
+var SPICE_IMAGE_TYPE_JPEG = 105;
+var SPICE_IMAGE_TYPE_FROM_CACHE_LOSSLESS = 106;
+var SPICE_IMAGE_TYPE_ZLIB_GLZ_RGB = 107;
+var SPICE_IMAGE_TYPE_JPEG_ALPHA = 108;
+
+var SPICE_IMAGE_FLAGS_CACHE_ME = (1 << 0),
+ SPICE_IMAGE_FLAGS_HIGH_BITS_SET = (1 << 1),
+ SPICE_IMAGE_FLAGS_CACHE_REPLACE_ME = (1 << 2);
+
+var SPICE_BITMAP_FLAGS_PAL_CACHE_ME = (1 << 0),
+ SPICE_BITMAP_FLAGS_PAL_FROM_CACHE = (1 << 1),
+ SPICE_BITMAP_FLAGS_TOP_DOWN = (1 << 2),
+ SPICE_BITMAP_FLAGS_MASK = 0x7;
+
+var SPICE_BITMAP_FMT_INVALID = 0,
+ SPICE_BITMAP_FMT_1BIT_LE = 1,
+ SPICE_BITMAP_FMT_1BIT_BE = 2,
+ SPICE_BITMAP_FMT_4BIT_LE = 3,
+ SPICE_BITMAP_FMT_4BIT_BE = 4,
+ SPICE_BITMAP_FMT_8BIT = 5,
+ SPICE_BITMAP_FMT_16BIT = 6,
+ SPICE_BITMAP_FMT_24BIT = 7,
+ SPICE_BITMAP_FMT_32BIT = 8,
+ SPICE_BITMAP_FMT_RGBA = 9;
+
+
+var SPICE_CURSOR_FLAGS_NONE = (1 << 0),
+ SPICE_CURSOR_FLAGS_CACHE_ME = (1 << 1),
+ SPICE_CURSOR_FLAGS_FROM_CACHE = (1 << 2),
+ SPICE_CURSOR_FLAGS_MASK = 0x7;
+
+var SPICE_MOUSE_BUTTON_MASK_LEFT = (1 << 0),
+ SPICE_MOUSE_BUTTON_MASK_MIDDLE = (1 << 1),
+ SPICE_MOUSE_BUTTON_MASK_RIGHT = (1 << 2),
+ SPICE_MOUSE_BUTTON_MASK_MASK = 0x7;
+
+var SPICE_MOUSE_BUTTON_INVALID = 0;
+var SPICE_MOUSE_BUTTON_LEFT = 1;
+var SPICE_MOUSE_BUTTON_MIDDLE = 2;
+var SPICE_MOUSE_BUTTON_RIGHT = 3;
+var SPICE_MOUSE_BUTTON_UP = 4;
+var SPICE_MOUSE_BUTTON_DOWN = 5;
+
+var SPICE_BRUSH_TYPE_NONE = 0,
+ SPICE_BRUSH_TYPE_SOLID = 1,
+ SPICE_BRUSH_TYPE_PATTERN = 2;
+
+var SPICE_SURFACE_FMT_INVALID = 0,
+ SPICE_SURFACE_FMT_1_A = 1,
+ SPICE_SURFACE_FMT_8_A = 8,
+ SPICE_SURFACE_FMT_16_555 = 16,
+ SPICE_SURFACE_FMT_32_xRGB = 32,
+ SPICE_SURFACE_FMT_16_565 = 80,
+ SPICE_SURFACE_FMT_32_ARGB = 96;
+
+var SPICE_ROPD_INVERS_SRC = (1 << 0),
+ SPICE_ROPD_INVERS_BRUSH = (1 << 1),
+ SPICE_ROPD_INVERS_DEST = (1 << 2),
+ SPICE_ROPD_OP_PUT = (1 << 3),
+ SPICE_ROPD_OP_OR = (1 << 4),
+ SPICE_ROPD_OP_AND = (1 << 5),
+ SPICE_ROPD_OP_XOR = (1 << 6),
+ SPICE_ROPD_OP_BLACKNESS = (1 << 7),
+ SPICE_ROPD_OP_WHITENESS = (1 << 8),
+ SPICE_ROPD_OP_INVERS = (1 << 9),
+ SPICE_ROPD_INVERS_RES = (1 << 10),
+ SPICE_ROPD_MASK = 0x7ff;
+
+var LZ_IMAGE_TYPE_INVALID = 0,
+ LZ_IMAGE_TYPE_PLT1_LE = 1,
+ LZ_IMAGE_TYPE_PLT1_BE = 2, // PLT stands for palette
+ LZ_IMAGE_TYPE_PLT4_LE = 3,
+ LZ_IMAGE_TYPE_PLT4_BE = 4,
+ LZ_IMAGE_TYPE_PLT8 = 5,
+ LZ_IMAGE_TYPE_RGB16 = 6,
+ LZ_IMAGE_TYPE_RGB24 = 7,
+ LZ_IMAGE_TYPE_RGB32 = 8,
+ LZ_IMAGE_TYPE_RGBA = 9,
+ LZ_IMAGE_TYPE_XXXA = 10;
+
+
+var QUIC_IMAGE_TYPE_INVALID = 0,
+ QUIC_IMAGE_TYPE_GRAY = 1,
+ QUIC_IMAGE_TYPE_RGB16 = 2,
+ QUIC_IMAGE_TYPE_RGB24 = 3,
+ QUIC_IMAGE_TYPE_RGB32 = 4,
+ QUIC_IMAGE_TYPE_RGBA = 5;
+
+var SPICE_INPUT_MOTION_ACK_BUNCH = 4;
+
+
+var SPICE_CURSOR_TYPE_ALPHA = 0,
+ SPICE_CURSOR_TYPE_MONO = 1,
+ SPICE_CURSOR_TYPE_COLOR4 = 2,
+ SPICE_CURSOR_TYPE_COLOR8 = 3,
+ SPICE_CURSOR_TYPE_COLOR16 = 4,
+ SPICE_CURSOR_TYPE_COLOR24 = 5,
+ SPICE_CURSOR_TYPE_COLOR32 = 6;
+
+var SPICE_VIDEO_CODEC_TYPE_MJPEG = 1;
diff --git a/ui/js/spice/inputs.js b/ui/js/spice/inputs.js
new file mode 100644
index 0000000..4d3b28f
--- /dev/null
+++ b/ui/js/spice/inputs.js
@@ -0,0 +1,251 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*----------------------------------------------------------------------------
+ ** Modifier Keystates
+ ** These need to be tracked because focus in and out can get the keyboard
+ ** out of sync.
+ **------------------------------------------------------------------------*/
+var Shift_state = -1;
+var Ctrl_state = -1;
+var Alt_state = -1;
+var Meta_state = -1;
+
+/*----------------------------------------------------------------------------
+** SpiceInputsConn
+** Drive the Spice Inputs channel (e.g. mouse + keyboard)
+**--------------------------------------------------------------------------*/
+function SpiceInputsConn()
+{
+ SpiceConn.apply(this, arguments);
+
+ this.mousex = undefined;
+ this.mousey = undefined;
+ this.button_state = 0;
+ this.waiting_for_ack = 0;
+}
+
+SpiceInputsConn.prototype = Object.create(SpiceConn.prototype);
+SpiceInputsConn.prototype.process_channel_message = function(msg)
+{
+ if (msg.type == SPICE_MSG_INPUTS_INIT)
+ {
+ var inputs_init = new SpiceMsgInputsInit(msg.data);
+ this.keyboard_modifiers = inputs_init.keyboard_modifiers;
+ DEBUG > 1 && console.log("MsgInputsInit - modifier " + this.keyboard_modifiers);
+ // FIXME - We don't do anything with the keyboard modifiers...
+ return true;
+ }
+ if (msg.type == SPICE_MSG_INPUTS_KEY_MODIFIERS)
+ {
+ var key = new SpiceMsgInputsKeyModifiers(msg.data);
+ this.keyboard_modifiers = key.keyboard_modifiers;
+ DEBUG > 1 && console.log("MsgInputsKeyModifiers - modifier " + this.keyboard_modifiers);
+ // FIXME - We don't do anything with the keyboard modifiers...
+ return true;
+ }
+ if (msg.type == SPICE_MSG_INPUTS_MOUSE_MOTION_ACK)
+ {
+ DEBUG > 1 && console.log("mouse motion ack");
+ this.waiting_for_ack -= SPICE_INPUT_MOTION_ACK_BUNCH;
+ return true;
+ }
+ return false;
+}
+
+
+
+function handle_mousemove(e)
+{
+ var msg = new SpiceMiniData();
+ var move;
+ if (this.sc.mouse_mode == SPICE_MOUSE_MODE_CLIENT)
+ {
+ move = new SpiceMsgcMousePosition(this.sc, e)
+ msg.build_msg(SPICE_MSGC_INPUTS_MOUSE_POSITION, move);
+ }
+ else
+ {
+ move = new SpiceMsgcMouseMotion(this.sc, e)
+ msg.build_msg(SPICE_MSGC_INPUTS_MOUSE_MOTION, move);
+ }
+ if (this.sc && this.sc.inputs && this.sc.inputs.state === "ready")
+ {
+ if (this.sc.inputs.waiting_for_ack < (2 * SPICE_INPUT_MOTION_ACK_BUNCH))
+ {
+ this.sc.inputs.send_msg(msg);
+ this.sc.inputs.waiting_for_ack++;
+ }
+ else
+ {
+ DEBUG > 0 && this.sc.log_info("Discarding mouse motion");
+ }
+ }
+}
+
+function handle_mousedown(e)
+{
+ var press = new SpiceMsgcMousePress(this.sc, e)
+ var msg = new SpiceMiniData();
+ msg.build_msg(SPICE_MSGC_INPUTS_MOUSE_PRESS, press);
+ if (this.sc && this.sc.inputs && this.sc.inputs.state === "ready")
+ this.sc.inputs.send_msg(msg);
+
+ e.preventDefault();
+}
+
+function handle_contextmenu(e)
+{
+ e.preventDefault();
+ return false;
+}
+
+function handle_mouseup(e)
+{
+ var release = new SpiceMsgcMouseRelease(this.sc, e)
+ var msg = new SpiceMiniData();
+ msg.build_msg(SPICE_MSGC_INPUTS_MOUSE_RELEASE, release);
+ if (this.sc && this.sc.inputs && this.sc.inputs.state === "ready")
+ this.sc.inputs.send_msg(msg);
+
+ e.preventDefault();
+}
+
+function handle_mousewheel(e)
+{
+ var press = new SpiceMsgcMousePress;
+ var release = new SpiceMsgcMouseRelease;
+ if (e.wheelDelta > 0)
+ press.button = release.button = SPICE_MOUSE_BUTTON_UP;
+ else
+ press.button = release.button = SPICE_MOUSE_BUTTON_DOWN;
+ press.buttons_state = 0;
+ release.buttons_state = 0;
+
+ var msg = new SpiceMiniData();
+ msg.build_msg(SPICE_MSGC_INPUTS_MOUSE_PRESS, press);
+ if (this.sc && this.sc.inputs && this.sc.inputs.state === "ready")
+ this.sc.inputs.send_msg(msg);
+
+ msg.build_msg(SPICE_MSGC_INPUTS_MOUSE_RELEASE, release);
+ if (this.sc && this.sc.inputs && this.sc.inputs.state === "ready")
+ this.sc.inputs.send_msg(msg);
+
+ e.preventDefault();
+}
+
+function handle_keydown(e)
+{
+ var key = new SpiceMsgcKeyDown(e)
+ var msg = new SpiceMiniData();
+ check_and_update_modifiers(e, key.code, this.sc);
+ msg.build_msg(SPICE_MSGC_INPUTS_KEY_DOWN, key);
+ if (this.sc && this.sc.inputs && this.sc.inputs.state === "ready")
+ this.sc.inputs.send_msg(msg);
+
+ e.preventDefault();
+}
+
+function handle_keyup(e)
+{
+ var key = new SpiceMsgcKeyUp(e)
+ var msg = new SpiceMiniData();
+ check_and_update_modifiers(e, key.code, this.sc);
+ msg.build_msg(SPICE_MSGC_INPUTS_KEY_UP, key);
+ if (this.sc && this.sc.inputs && this.sc.inputs.state === "ready")
+ this.sc.inputs.send_msg(msg);
+
+ e.preventDefault();
+}
+
+function update_modifier(state, code, sc)
+{
+ var msg = new SpiceMiniData();
+ if (!state)
+ {
+ var key = new SpiceMsgcKeyUp()
+ key.code =(0x80|code);
+ msg.build_msg(SPICE_MSGC_INPUTS_KEY_UP, key);
+ }
+ else
+ {
+ var key = new SpiceMsgcKeyDown()
+ key.code = code;
+ msg.build_msg(SPICE_MSGC_INPUTS_KEY_DOWN, key);
+ }
+
+ sc.inputs.send_msg(msg);
+}
+
+function check_and_update_modifiers(e, code, sc)
+{
+ if (Shift_state === -1)
+ {
+ Shift_state = e.shiftKey;
+ Ctrl_state = e.ctrlKey;
+ Alt_state = e.altKey;
+ Meta_state = e.metaKey;
+ }
+
+ if (code === KEY_ShiftL)
+ Shift_state = true;
+ else if (code === KEY_Alt)
+ Alt_state = true;
+ else if (code === KEY_LCtrl)
+ Ctrl_state = true;
+ else if (code === 0xE0B5)
+ Meta_state = true;
+ else if (code === (0x80|KEY_ShiftL))
+ Shift_state = false;
+ else if (code === (0x80|KEY_Alt))
+ Alt_state = false;
+ else if (code === (0x80|KEY_LCtrl))
+ Ctrl_state = false;
+ else if (code === (0x80|0xE0B5))
+ Meta_state = false;
+
+ if (sc && sc.inputs && sc.inputs.state === "ready")
+ {
+ if (Shift_state != e.shiftKey)
+ {
+ console.log("Shift state out of sync");
+ update_modifier(e.shiftKey, KEY_ShiftL, sc);
+ Shift_state = e.shiftKey;
+ }
+ if (Alt_state != e.altKey)
+ {
+ console.log("Alt state out of sync");
+ update_modifier(e.altKey, KEY_Alt, sc);
+ Alt_state = e.altKey;
+ }
+ if (Ctrl_state != e.ctrlKey)
+ {
+ console.log("Ctrl state out of sync");
+ update_modifier(e.ctrlKey, KEY_LCtrl, sc);
+ Ctrl_state = e.ctrlKey;
+ }
+ if (Meta_state != e.metaKey)
+ {
+ console.log("Meta state out of sync");
+ update_modifier(e.metaKey, 0xE0B5, sc);
+ Meta_state = e.metaKey;
+ }
+ }
+}
diff --git a/ui/js/spice/jsbn.js b/ui/js/spice/jsbn.js
new file mode 100644
index 0000000..d88ec54
--- /dev/null
+++ b/ui/js/spice/jsbn.js
@@ -0,0 +1,589 @@
+// Downloaded from http://www-cs-students.stanford.edu/~tjw/jsbn/ by Jeremy White on 6/1/2012
+
+/*
+ * Copyright (c) 2003-2005 Tom Wu
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
+ * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * In addition, the following condition applies:
+ *
+ * All redistributions must retain an intact copy of this copyright notice
+ * and disclaimer.
+ */
+
+
+// Basic JavaScript BN library - subset useful for RSA encryption.
+
+// Bits per digit
+var dbits;
+
+// JavaScript engine analysis
+var canary = 0xdeadbeefcafe;
+var j_lm = ((canary&0xffffff)==0xefcafe);
+
+// (public) Constructor
+function BigInteger(a,b,c) {
+ if(a != null)
+ if("number" == typeof a) this.fromNumber(a,b,c);
+ else if(b == null && "string" != typeof a) this.fromString(a,256);
+ else this.fromString(a,b);
+}
+
+// return new, unset BigInteger
+function nbi() { return new BigInteger(null); }
+
+// am: Compute w_j += (x*this_i), propagate carries,
+// c is initial carry, returns final carry.
+// c < 3*dvalue, x < 2*dvalue, this_i < dvalue
+// We need to select the fastest one that works in this environment.
+
+// am1: use a single mult and divide to get the high bits,
+// max digit bits should be 26 because
+// max internal value = 2*dvalue^2-2*dvalue (< 2^53)
+function am1(i,x,w,j,c,n) {
+ while(--n >= 0) {
+ var v = x*this[i++]+w[j]+c;
+ c = Math.floor(v/0x4000000);
+ w[j++] = v&0x3ffffff;
+ }
+ return c;
+}
+// am2 avoids a big mult-and-extract completely.
+// Max digit bits should be <= 30 because we do bitwise ops
+// on values up to 2*hdvalue^2-hdvalue-1 (< 2^31)
+function am2(i,x,w,j,c,n) {
+ var xl = x&0x7fff, xh = x>>15;
+ while(--n >= 0) {
+ var l = this[i]&0x7fff;
+ var h = this[i++]>>15;
+ var m = xh*l+h*xl;
+ l = xl*l+((m&0x7fff)<<15)+w[j]+(c&0x3fffffff);
+ c = (l>>>30)+(m>>>15)+xh*h+(c>>>30);
+ w[j++] = l&0x3fffffff;
+ }
+ return c;
+}
+// Alternately, set max digit bits to 28 since some
+// browsers slow down when dealing with 32-bit numbers.
+function am3(i,x,w,j,c,n) {
+ var xl = x&0x3fff, xh = x>>14;
+ while(--n >= 0) {
+ var l = this[i]&0x3fff;
+ var h = this[i++]>>14;
+ var m = xh*l+h*xl;
+ l = xl*l+((m&0x3fff)<<14)+w[j]+c;
+ c = (l>>28)+(m>>14)+xh*h;
+ w[j++] = l&0xfffffff;
+ }
+ return c;
+}
+if(j_lm && (navigator.appName == "Microsoft Internet Explorer")) {
+ BigInteger.prototype.am = am2;
+ dbits = 30;
+}
+else if(j_lm && (navigator.appName != "Netscape")) {
+ BigInteger.prototype.am = am1;
+ dbits = 26;
+}
+else { // Mozilla/Netscape seems to prefer am3
+ BigInteger.prototype.am = am3;
+ dbits = 28;
+}
+
+BigInteger.prototype.DB = dbits;
+BigInteger.prototype.DM = ((1<<dbits)-1);
+BigInteger.prototype.DV = (1<<dbits);
+
+var BI_FP = 52;
+BigInteger.prototype.FV = Math.pow(2,BI_FP);
+BigInteger.prototype.F1 = BI_FP-dbits;
+BigInteger.prototype.F2 = 2*dbits-BI_FP;
+
+// Digit conversions
+var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz";
+var BI_RC = new Array();
+var rr,vv;
+rr = "0".charCodeAt(0);
+for(vv = 0; vv <= 9; ++vv) BI_RC[rr++] = vv;
+rr = "a".charCodeAt(0);
+for(vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv;
+rr = "A".charCodeAt(0);
+for(vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv;
+
+function int2char(n) { return BI_RM.charAt(n); }
+function intAt(s,i) {
+ var c = BI_RC[s.charCodeAt(i)];
+ return (c==null)?-1:c;
+}
+
+// (protected) copy this to r
+function bnpCopyTo(r) {
+ for(var i = this.t-1; i >= 0; --i) r[i] = this[i];
+ r.t = this.t;
+ r.s = this.s;
+}
+
+// (protected) set from integer value x, -DV <= x < DV
+function bnpFromInt(x) {
+ this.t = 1;
+ this.s = (x<0)?-1:0;
+ if(x > 0) this[0] = x;
+ else if(x < -1) this[0] = x+DV;
+ else this.t = 0;
+}
+
+// return bigint initialized to value
+function nbv(i) { var r = nbi(); r.fromInt(i); return r; }
+
+// (protected) set from string and radix
+function bnpFromString(s,b) {
+ var k;
+ if(b == 16) k = 4;
+ else if(b == 8) k = 3;
+ else if(b == 256) k = 8; // byte array
+ else if(b == 2) k = 1;
+ else if(b == 32) k = 5;
+ else if(b == 4) k = 2;
+ else { this.fromRadix(s,b); return; }
+ this.t = 0;
+ this.s = 0;
+ var i = s.length, mi = false, sh = 0;
+ while(--i >= 0) {
+ var x = (k==8)?s[i]&0xff:intAt(s,i);
+ if(x < 0) {
+ if(s.charAt(i) == "-") mi = true;
+ continue;
+ }
+ mi = false;
+ if(sh == 0)
+ this[this.t++] = x;
+ else if(sh+k > this.DB) {
+ this[this.t-1] |= (x&((1<<(this.DB-sh))-1))<<sh;
+ this[this.t++] = (x>>(this.DB-sh));
+ }
+ else
+ this[this.t-1] |= x<<sh;
+ sh += k;
+ if(sh >= this.DB) sh -= this.DB;
+ }
+ if(k == 8 && (s[0]&0x80) != 0) {
+ this.s = -1;
+ if(sh > 0) this[this.t-1] |= ((1<<(this.DB-sh))-1)<<sh;
+ }
+ this.clamp();
+ if(mi) BigInteger.ZERO.subTo(this,this);
+}
+
+// (protected) clamp off excess high words
+function bnpClamp() {
+ var c = this.s&this.DM;
+ while(this.t > 0 && this[this.t-1] == c) --this.t;
+}
+
+// (public) return string representation in given radix
+function bnToString(b) {
+ if(this.s < 0) return "-"+this.negate().toString(b);
+ var k;
+ if(b == 16) k = 4;
+ else if(b == 8) k = 3;
+ else if(b == 2) k = 1;
+ else if(b == 32) k = 5;
+ else if(b == 4) k = 2;
+ else return this.toRadix(b);
+ var km = (1<<k)-1, d, m = false, r = "", i = this.t;
+ var p = this.DB-(i*this.DB)%k;
+ if(i-- > 0) {
+ if(p < this.DB && (d = this[i]>>p) > 0) { m = true; r = int2char(d); }
+ while(i >= 0) {
+ if(p < k) {
+ d = (this[i]&((1<<p)-1))<<(k-p);
+ d |= this[--i]>>(p+=this.DB-k);
+ }
+ else {
+ d = (this[i]>>(p-=k))&km;
+ if(p <= 0) { p += this.DB; --i; }
+ }
+ if(d > 0) m = true;
+ if(m) r += int2char(d);
+ }
+ }
+ return m?r:"0";
+}
+
+// (public) -this
+function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this,r); return r; }
+
+// (public) |this|
+function bnAbs() { return (this.s<0)?this.negate():this; }
+
+// (public) return + if this > a, - if this < a, 0 if equal
+function bnCompareTo(a) {
+ var r = this.s-a.s;
+ if(r != 0) return r;
+ var i = this.t;
+ r = i-a.t;
+ if(r != 0) return r;
+ while(--i >= 0) if((r=this[i]-a[i]) != 0) return r;
+ return 0;
+}
+
+// returns bit length of the integer x
+function nbits(x) {
+ var r = 1, t;
+ if((t=x>>>16) != 0) { x = t; r += 16; }
+ if((t=x>>8) != 0) { x = t; r += 8; }
+ if((t=x>>4) != 0) { x = t; r += 4; }
+ if((t=x>>2) != 0) { x = t; r += 2; }
+ if((t=x>>1) != 0) { x = t; r += 1; }
+ return r;
+}
+
+// (public) return the number of bits in "this"
+function bnBitLength() {
+ if(this.t <= 0) return 0;
+ return this.DB*(this.t-1)+nbits(this[this.t-1]^(this.s&this.DM));
+}
+
+// (protected) r = this << n*DB
+function bnpDLShiftTo(n,r) {
+ var i;
+ for(i = this.t-1; i >= 0; --i) r[i+n] = this[i];
+ for(i = n-1; i >= 0; --i) r[i] = 0;
+ r.t = this.t+n;
+ r.s = this.s;
+}
+
+// (protected) r = this >> n*DB
+function bnpDRShiftTo(n,r) {
+ for(var i = n; i < this.t; ++i) r[i-n] = this[i];
+ r.t = Math.max(this.t-n,0);
+ r.s = this.s;
+}
+
+// (protected) r = this << n
+function bnpLShiftTo(n,r) {
+ var bs = n%this.DB;
+ var cbs = this.DB-bs;
+ var bm = (1<<cbs)-1;
+ var ds = Math.floor(n/this.DB), c = (this.s<<bs)&this.DM, i;
+ for(i = this.t-1; i >= 0; --i) {
+ r[i+ds+1] = (this[i]>>cbs)|c;
+ c = (this[i]&bm)<<bs;
+ }
+ for(i = ds-1; i >= 0; --i) r[i] = 0;
+ r[ds] = c;
+ r.t = this.t+ds+1;
+ r.s = this.s;
+ r.clamp();
+}
+
+// (protected) r = this >> n
+function bnpRShiftTo(n,r) {
+ r.s = this.s;
+ var ds = Math.floor(n/this.DB);
+ if(ds >= this.t) { r.t = 0; return; }
+ var bs = n%this.DB;
+ var cbs = this.DB-bs;
+ var bm = (1<<bs)-1;
+ r[0] = this[ds]>>bs;
+ for(var i = ds+1; i < this.t; ++i) {
+ r[i-ds-1] |= (this[i]&bm)<<cbs;
+ r[i-ds] = this[i]>>bs;
+ }
+ if(bs > 0) r[this.t-ds-1] |= (this.s&bm)<<cbs;
+ r.t = this.t-ds;
+ r.clamp();
+}
+
+// (protected) r = this - a
+function bnpSubTo(a,r) {
+ var i = 0, c = 0, m = Math.min(a.t,this.t);
+ while(i < m) {
+ c += this[i]-a[i];
+ r[i++] = c&this.DM;
+ c >>= this.DB;
+ }
+ if(a.t < this.t) {
+ c -= a.s;
+ while(i < this.t) {
+ c += this[i];
+ r[i++] = c&this.DM;
+ c >>= this.DB;
+ }
+ c += this.s;
+ }
+ else {
+ c += this.s;
+ while(i < a.t) {
+ c -= a[i];
+ r[i++] = c&this.DM;
+ c >>= this.DB;
+ }
+ c -= a.s;
+ }
+ r.s = (c<0)?-1:0;
+ if(c < -1) r[i++] = this.DV+c;
+ else if(c > 0) r[i++] = c;
+ r.t = i;
+ r.clamp();
+}
+
+// (protected) r = this * a, r != this,a (HAC 14.12)
+// "this" should be the larger one if appropriate.
+function bnpMultiplyTo(a,r) {
+ var x = this.abs(), y = a.abs();
+ var i = x.t;
+ r.t = i+y.t;
+ while(--i >= 0) r[i] = 0;
+ for(i = 0; i < y.t; ++i) r[i+x.t] = x.am(0,y[i],r,i,0,x.t);
+ r.s = 0;
+ r.clamp();
+ if(this.s != a.s) BigInteger.ZERO.subTo(r,r);
+}
+
+// (protected) r = this^2, r != this (HAC 14.16)
+function bnpSquareTo(r) {
+ var x = this.abs();
+ var i = r.t = 2*x.t;
+ while(--i >= 0) r[i] = 0;
+ for(i = 0; i < x.t-1; ++i) {
+ var c = x.am(i,x[i],r,2*i,0,1);
+ if((r[i+x.t]+=x.am(i+1,2*x[i],r,2*i+1,c,x.t-i-1)) >= x.DV) {
+ r[i+x.t] -= x.DV;
+ r[i+x.t+1] = 1;
+ }
+ }
+ if(r.t > 0) r[r.t-1] += x.am(i,x[i],r,2*i,0,1);
+ r.s = 0;
+ r.clamp();
+}
+
+// (protected) divide this by m, quotient and remainder to q, r (HAC 14.20)
+// r != q, this != m. q or r may be null.
+function bnpDivRemTo(m,q,r) {
+ var pm = m.abs();
+ if(pm.t <= 0) return;
+ var pt = this.abs();
+ if(pt.t < pm.t) {
+ if(q != null) q.fromInt(0);
+ if(r != null) this.copyTo(r);
+ return;
+ }
+ if(r == null) r = nbi();
+ var y = nbi(), ts = this.s, ms = m.s;
+ var nsh = this.DB-nbits(pm[pm.t-1]); // normalize modulus
+ if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); }
+ else { pm.copyTo(y); pt.copyTo(r); }
+ var ys = y.t;
+ var y0 = y[ys-1];
+ if(y0 == 0) return;
+ var yt = y0*(1<<this.F1)+((ys>1)?y[ys-2]>>this.F2:0);
+ var d1 = this.FV/yt, d2 = (1<<this.F1)/yt, e = 1<<this.F2;
+ var i = r.t, j = i-ys, t = (q==null)?nbi():q;
+ y.dlShiftTo(j,t);
+ if(r.compareTo(t) >= 0) {
+ r[r.t++] = 1;
+ r.subTo(t,r);
+ }
+ BigInteger.ONE.dlShiftTo(ys,t);
+ t.subTo(y,y); // "negative" y so we can replace sub with am later
+ while(y.t < ys) y[y.t++] = 0;
+ while(--j >= 0) {
+ // Estimate quotient digit
+ var qd = (r[--i]==y0)?this.DM:Math.floor(r[i]*d1+(r[i-1]+e)*d2);
+ if((r[i]+=y.am(0,qd,r,j,0,ys)) < qd) { // Try it out
+ y.dlShiftTo(j,t);
+ r.subTo(t,r);
+ while(r[i] < --qd) r.subTo(t,r);
+ }
+ }
+ if(q != null) {
+ r.drShiftTo(ys,q);
+ if(ts != ms) BigInteger.ZERO.subTo(q,q);
+ }
+ r.t = ys;
+ r.clamp();
+ if(nsh > 0) r.rShiftTo(nsh,r); // Denormalize remainder
+ if(ts < 0) BigInteger.ZERO.subTo(r,r);
+}
+
+// (public) this mod a
+function bnMod(a) {
+ var r = nbi();
+ this.abs().divRemTo(a,null,r);
+ if(this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r,r);
+ return r;
+}
+
+// Modular reduction using "classic" algorithm
+function Classic(m) { this.m = m; }
+function cConvert(x) {
+ if(x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m);
+ else return x;
+}
+function cRevert(x) { return x; }
+function cReduce(x) { x.divRemTo(this.m,null,x); }
+function cMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
+function cSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
+
+Classic.prototype.convert = cConvert;
+Classic.prototype.revert = cRevert;
+Classic.prototype.reduce = cReduce;
+Classic.prototype.mulTo = cMulTo;
+Classic.prototype.sqrTo = cSqrTo;
+
+// (protected) return "-1/this % 2^DB"; useful for Mont. reduction
+// justification:
+// xy == 1 (mod m)
+// xy = 1+km
+// xy(2-xy) = (1+km)(1-km)
+// x[y(2-xy)] = 1-k^2m^2
+// x[y(2-xy)] == 1 (mod m^2)
+// if y is 1/x mod m, then y(2-xy) is 1/x mod m^2
+// should reduce x and y(2-xy) by m^2 at each step to keep size bounded.
+// JS multiply "overflows" differently from C/C++, so care is needed here.
+function bnpInvDigit() {
+ if(this.t < 1) return 0;
+ var x = this[0];
+ if((x&1) == 0) return 0;
+ var y = x&3; // y == 1/x mod 2^2
+ y = (y*(2-(x&0xf)*y))&0xf; // y == 1/x mod 2^4
+ y = (y*(2-(x&0xff)*y))&0xff; // y == 1/x mod 2^8
+ y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff; // y == 1/x mod 2^16
+ // last step - calculate inverse mod DV directly;
+ // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints
+ y = (y*(2-x*y%this.DV))%this.DV; // y == 1/x mod 2^dbits
+ // we really want the negative inverse, and -DV < y < DV
+ return (y>0)?this.DV-y:-y;
+}
+
+// Montgomery reduction
+function Montgomery(m) {
+ this.m = m;
+ this.mp = m.invDigit();
+ this.mpl = this.mp&0x7fff;
+ this.mph = this.mp>>15;
+ this.um = (1<<(m.DB-15))-1;
+ this.mt2 = 2*m.t;
+}
+
+// xR mod m
+function montConvert(x) {
+ var r = nbi();
+ x.abs().dlShiftTo(this.m.t,r);
+ r.divRemTo(this.m,null,r);
+ if(x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r,r);
+ return r;
+}
+
+// x/R mod m
+function montRevert(x) {
+ var r = nbi();
+ x.copyTo(r);
+ this.reduce(r);
+ return r;
+}
+
+// x = x/R mod m (HAC 14.32)
+function montReduce(x) {
+ while(x.t <= this.mt2) // pad x so am has enough room later
+ x[x.t++] = 0;
+ for(var i = 0; i < this.m.t; ++i) {
+ // faster way of calculating u0 = x[i]*mp mod DV
+ var j = x[i]&0x7fff;
+ var u0 = (j*this.mpl+(((j*this.mph+(x[i]>>15)*this.mpl)&this.um)<<15))&x.DM;
+ // use am to combine the multiply-shift-add into one call
+ j = i+this.m.t;
+ x[j] += this.m.am(0,u0,x,i,0,this.m.t);
+ // propagate carry
+ while(x[j] >= x.DV) { x[j] -= x.DV; x[++j]++; }
+ }
+ x.clamp();
+ x.drShiftTo(this.m.t,x);
+ if(x.compareTo(this.m) >= 0) x.subTo(this.m,x);
+}
+
+// r = "x^2/R mod m"; x != r
+function montSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
+
+// r = "xy/R mod m"; x,y != r
+function montMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
+
+Montgomery.prototype.convert = montConvert;
+Montgomery.prototype.revert = montRevert;
+Montgomery.prototype.reduce = montReduce;
+Montgomery.prototype.mulTo = montMulTo;
+Montgomery.prototype.sqrTo = montSqrTo;
+
+// (protected) true iff this is even
+function bnpIsEven() { return ((this.t>0)?(this[0]&1):this.s) == 0; }
+
+// (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79)
+function bnpExp(e,z) {
+ if(e > 0xffffffff || e < 1) return BigInteger.ONE;
+ var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1;
+ g.copyTo(r);
+ while(--i >= 0) {
+ z.sqrTo(r,r2);
+ if((e&(1<<i)) > 0) z.mulTo(r2,g,r);
+ else { var t = r; r = r2; r2 = t; }
+ }
+ return z.revert(r);
+}
+
+// (public) this^e % m, 0 <= e < 2^32
+function bnModPowInt(e,m) {
+ var z;
+ if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m);
+ return this.exp(e,z);
+}
+
+// protected
+BigInteger.prototype.copyTo = bnpCopyTo;
+BigInteger.prototype.fromInt = bnpFromInt;
+BigInteger.prototype.fromString = bnpFromString;
+BigInteger.prototype.clamp = bnpClamp;
+BigInteger.prototype.dlShiftTo = bnpDLShiftTo;
+BigInteger.prototype.drShiftTo = bnpDRShiftTo;
+BigInteger.prototype.lShiftTo = bnpLShiftTo;
+BigInteger.prototype.rShiftTo = bnpRShiftTo;
+BigInteger.prototype.subTo = bnpSubTo;
+BigInteger.prototype.multiplyTo = bnpMultiplyTo;
+BigInteger.prototype.squareTo = bnpSquareTo;
+BigInteger.prototype.divRemTo = bnpDivRemTo;
+BigInteger.prototype.invDigit = bnpInvDigit;
+BigInteger.prototype.isEven = bnpIsEven;
+BigInteger.prototype.exp = bnpExp;
+
+// public
+BigInteger.prototype.toString = bnToString;
+BigInteger.prototype.negate = bnNegate;
+BigInteger.prototype.abs = bnAbs;
+BigInteger.prototype.compareTo = bnCompareTo;
+BigInteger.prototype.bitLength = bnBitLength;
+BigInteger.prototype.mod = bnMod;
+BigInteger.prototype.modPowInt = bnModPowInt;
+
+// "constants"
+BigInteger.ZERO = nbv(0);
+BigInteger.ONE = nbv(1);
diff --git a/ui/js/spice/lz.js b/ui/js/spice/lz.js
new file mode 100644
index 0000000..4292eac
--- /dev/null
+++ b/ui/js/spice/lz.js
@@ -0,0 +1,166 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+/*----------------------------------------------------------------------------
+** lz.js
+** Functions for handling SPICE_IMAGE_TYPE_LZ_RGB
+** Adapted from lz.c .
+**--------------------------------------------------------------------------*/
+function lz_rgb32_decompress(in_buf, at, out_buf, type, default_alpha)
+{
+ var encoder = at;
+ var op = 0;
+ var ctrl;
+ var ctr = 0;
+
+ for (ctrl = in_buf[encoder++]; (op * 4) < out_buf.length; ctrl = in_buf[encoder++])
+ {
+ var ref = op;
+ var len = ctrl >> 5;
+ var ofs = (ctrl & 31) << 8;
+
+//if (type == LZ_IMAGE_TYPE_RGBA)
+//console.log(ctr++ + ": from " + (encoder + 28) + ", ctrl " + ctrl + ", len " + len + ", ofs " + ofs + ", op " + op);
+ if (ctrl >= 32) {
+
+ var code;
+ len--;
+
+ if (len == 7 - 1) {
+ do {
+ code = in_buf[encoder++];
+ len += code;
+ } while (code == 255);
+ }
+ code = in_buf[encoder++];
+ ofs += code;
+
+
+ if (code == 255) {
+ if ((ofs - code) == (31 << 8)) {
+ ofs = in_buf[encoder++] << 8;
+ ofs += in_buf[encoder++];
+ ofs += 8191;
+ }
+ }
+ len += 1;
+ if (type == LZ_IMAGE_TYPE_RGBA)
+ len += 2;
+
+ ofs += 1;
+
+ ref -= ofs;
+ if (ref == (op - 1)) {
+ var b = ref;
+//if (type == LZ_IMAGE_TYPE_RGBA) console.log("alpha " + out_buf[(b*4)+3] + " dupped into pixel " + op + " through pixel " + (op + len));
+ for (; len; --len) {
+ if (type == LZ_IMAGE_TYPE_RGBA)
+ {
+ out_buf[(op*4) + 3] = out_buf[(b*4)+3];
+ }
+ else
+ {
+ for (i = 0; i < 4; i++)
+ out_buf[(op*4) + i] = out_buf[(b*4)+i];
+ }
+ op++;
+ }
+ } else {
+//if (type == LZ_IMAGE_TYPE_RGBA) console.log("alpha copied to pixel " + op + " through " + (op + len) + " from " + ref);
+ for (; len; --len) {
+ if (type == LZ_IMAGE_TYPE_RGBA)
+ {
+ out_buf[(op*4) + 3] = out_buf[(ref*4)+3];
+ }
+ else
+ {
+ for (i = 0; i < 4; i++)
+ out_buf[(op*4) + i] = out_buf[(ref*4)+i];
+ }
+ op++; ref++;
+ }
+ }
+ } else {
+ ctrl++;
+
+ if (type == LZ_IMAGE_TYPE_RGBA)
+ {
+//console.log("alpha " + in_buf[encoder] + " set into pixel " + op);
+ out_buf[(op*4) + 3] = in_buf[encoder++];
+ }
+ else
+ {
+ out_buf[(op*4) + 0] = in_buf[encoder + 2];
+ out_buf[(op*4) + 1] = in_buf[encoder + 1];
+ out_buf[(op*4) + 2] = in_buf[encoder + 0];
+ if (default_alpha)
+ out_buf[(op*4) + 3] = 255;
+ encoder += 3;
+ }
+ op++;
+
+
+ for (--ctrl; ctrl; ctrl--) {
+ if (type == LZ_IMAGE_TYPE_RGBA)
+ {
+//console.log("alpha " + in_buf[encoder] + " set into pixel " + op);
+ out_buf[(op*4) + 3] = in_buf[encoder++];
+ }
+ else
+ {
+ out_buf[(op*4) + 0] = in_buf[encoder + 2];
+ out_buf[(op*4) + 1] = in_buf[encoder + 1];
+ out_buf[(op*4) + 2] = in_buf[encoder + 0];
+ if (default_alpha)
+ out_buf[(op*4) + 3] = 255;
+ encoder += 3;
+ }
+ op++;
+ }
+ }
+
+ }
+ return encoder - 1;
+}
+
+function convert_spice_lz_to_web(context, lz_image)
+{
+ var at;
+ if (lz_image.type === LZ_IMAGE_TYPE_RGB32 || lz_image.type === LZ_IMAGE_TYPE_RGBA)
+ {
+ var u8 = new Uint8Array(lz_image.data);
+ var ret = context.createImageData(lz_image.width, lz_image.height);
+
+ at = lz_rgb32_decompress(u8, 0, ret.data, LZ_IMAGE_TYPE_RGB32, lz_image.type != LZ_IMAGE_TYPE_RGBA);
+ if (lz_image.type == LZ_IMAGE_TYPE_RGBA)
+ lz_rgb32_decompress(u8, at, ret.data, LZ_IMAGE_TYPE_RGBA, false);
+ }
+ else if (lz_image.type === LZ_IMAGE_TYPE_XXXA)
+ {
+ var u8 = new Uint8Array(lz_image.data);
+ var ret = context.createImageData(lz_image.width, lz_image.height);
+ lz_rgb32_decompress(u8, 0, ret.data, LZ_IMAGE_TYPE_RGBA, false);
+ }
+ else
+ return undefined;
+
+ return ret;
+}
diff --git a/ui/js/spice/main.js b/ui/js/spice/main.js
new file mode 100644
index 0000000..e7eb1ed
--- /dev/null
+++ b/ui/js/spice/main.js
@@ -0,0 +1,176 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*----------------------------------------------------------------------------
+** SpiceMainConn
+** This is the master Javascript class for establishing and
+** managing a connection to a Spice Server.
+**
+** Invocation: You must pass an object with properties as follows:
+** uri (required) Uri of a WebSocket listener that is
+** connected to a spice server.
+** password (required) Password to send to the spice server
+** message_id (optional) Identifier of an element in the DOM
+** where SpiceConn will write messages.
+** It will use classes spice-messages-x,
+** where x is one of info, warning, or error.
+** screen_id (optional) Identifier of an element in the DOM
+** where SpiceConn will create any new
+** client screens. This is the main UI.
+** dump_id (optional) If given, an element to use for
+** dumping every single image + canvas drawn.
+** Sometimes useful for debugging.
+** onerror (optional) If given, a function to receive async
+** errors. Note that you should also catch
+** errors for ones that occur inline
+**
+** Throws error if there are troubles. Requires a modern (by 2012 standards)
+** browser, including WebSocket and WebSocket.binaryType == arraybuffer
+**
+**--------------------------------------------------------------------------*/
+function SpiceMainConn()
+{
+ if (typeof WebSocket === "undefined")
+ throw new Error("WebSocket unavailable. You need to use a different browser.");
+
+ SpiceConn.apply(this, arguments);
+
+}
+
+SpiceMainConn.prototype = Object.create(SpiceConn.prototype);
+SpiceMainConn.prototype.process_channel_message = function(msg)
+{
+ if (msg.type == SPICE_MSG_MAIN_INIT)
+ {
+ this.log_info("Connected to " + this.ws.url);
+ this.main_init = new SpiceMsgMainInit(msg.data);
+ this.connection_id = this.main_init.session_id;
+
+ if (DEBUG > 0)
+ {
+ // FIXME - there is a lot here we don't handle; mouse modes, agent,
+ // ram_hint, multi_media_time
+ this.log_info("session id " + this.main_init.session_id +
+ " ; display_channels_hint " + this.main_init.display_channels_hint +
+ " ; supported_mouse_modes " + this.main_init.supported_mouse_modes +
+ " ; current_mouse_mode " + this.main_init.current_mouse_mode +
+ " ; agent_connected " + this.main_init.agent_connected +
+ " ; agent_tokens " + this.main_init.agent_tokens +
+ " ; multi_media_time " + this.main_init.multi_media_time +
+ " ; ram_hint " + this.main_init.ram_hint);
+ }
+
+ this.mouse_mode = this.main_init.current_mouse_mode;
+ if (this.main_init.current_mouse_mode != SPICE_MOUSE_MODE_CLIENT &&
+ (this.main_init.supported_mouse_modes & SPICE_MOUSE_MODE_CLIENT))
+ {
+ var mode_request = new SpiceMsgcMainMouseModeRequest(SPICE_MOUSE_MODE_CLIENT);
+ var mr = new SpiceMiniData();
+ mr.build_msg(SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST, mode_request);
+ this.send_msg(mr);
+ }
+
+ var attach = new SpiceMiniData;
+ attach.type = SPICE_MSGC_MAIN_ATTACH_CHANNELS;
+ attach.size = attach.buffer_size();
+ this.send_msg(attach);
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_MAIN_MOUSE_MODE)
+ {
+ var mode = new SpiceMsgMainMouseMode(msg.data);
+ DEBUG > 0 && this.log_info("Mouse supported modes " + mode.supported_modes + "; current " + mode.current_mode);
+ this.mouse_mode = mode.current_mode;
+ if (this.inputs)
+ this.inputs.mouse_mode = mode.current_mode;
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_MAIN_CHANNELS_LIST)
+ {
+ var i;
+ var chans;
+ DEBUG > 0 && console.log("channels");
+ chans = new SpiceMsgChannels(msg.data);
+ for (i = 0; i < chans.channels.length; i++)
+ {
+ var conn = {
+ uri: this.ws.url,
+ parent: this,
+ connection_id : this.connection_id,
+ type : chans.channels[i].type,
+ chan_id : chans.channels[i].id
+ };
+ if (chans.channels[i].type == SPICE_CHANNEL_DISPLAY)
+ this.display = new SpiceDisplayConn(conn);
+ else if (chans.channels[i].type == SPICE_CHANNEL_INPUTS)
+ {
+ this.inputs = new SpiceInputsConn(conn);
+ this.inputs.mouse_mode = this.mouse_mode;
+ }
+ else if (chans.channels[i].type == SPICE_CHANNEL_CURSOR)
+ this.cursor = new SpiceCursorConn(conn);
+ else
+ {
+ this.log_err("Channel type " + chans.channels[i].type + " unknown.");
+ if (! ("extra_channels" in this))
+ this.extra_channels = [];
+ this.extra_channels[i] = new SpiceConn(conn);
+ }
+
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+SpiceMainConn.prototype.stop = function(msg)
+{
+ this.state = "closing";
+
+ if (this.inputs)
+ {
+ this.inputs.cleanup();
+ this.inputs = undefined;
+ }
+
+ if (this.cursor)
+ {
+ this.cursor.cleanup();
+ this.cursor = undefined;
+ }
+
+ if (this.display)
+ {
+ this.display.cleanup();
+ this.display.destroy_surfaces();
+ this.display = undefined;
+ }
+
+ this.cleanup();
+
+ if ("extra_channels" in this)
+ for (var e in this.extra_channels)
+ this.extra_channels[e].cleanup();
+ this.extra_channels = undefined;
+}
diff --git a/ui/js/spice/png.js b/ui/js/spice/png.js
new file mode 100644
index 0000000..6a26151
--- /dev/null
+++ b/ui/js/spice/png.js
@@ -0,0 +1,256 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*----------------------------------------------------------------------------
+** crc logic from rfc2083 ported to Javascript
+**--------------------------------------------------------------------------*/
+
+var rfc2083_crc_table = Array(256);
+var rfc2083_crc_table_computed = 0;
+/* Make the table for a fast CRC. */
+function rfc2083_make_crc_table()
+{
+ var c;
+ var n, k;
+ for (n = 0; n < 256; n++)
+ {
+ c = n;
+ for (k = 0; k < 8; k++)
+ {
+ if (c & 1)
+ c = ((0xedb88320 ^ (c >>> 1)) >>> 0) & 0xffffffff;
+ else
+ c = c >>> 1;
+ }
+ rfc2083_crc_table[n] = c;
+ }
+
+ rfc2083_crc_table_computed = 1;
+}
+
+/* Update a running CRC with the bytes buf[0..len-1]--the CRC
+ should be initialized to all 1's, and the transmitted value
+ is the 1's complement of the final running CRC (see the
+ crc() routine below)). */
+
+function rfc2083_update_crc(crc, u8buf, at, len)
+{
+ var c = crc;
+ var n;
+
+ if (!rfc2083_crc_table_computed)
+ rfc2083_make_crc_table();
+
+ for (n = 0; n < len; n++)
+ {
+ c = rfc2083_crc_table[(c ^ u8buf[at + n]) & 0xff] ^ (c >>> 8);
+ }
+
+ return c;
+}
+
+function rfc2083_crc(u8buf, at, len)
+{
+ return rfc2083_update_crc(0xffffffff, u8buf, at, len) ^ 0xffffffff;
+}
+
+function crc32(mb, at, len)
+{
+ var u8 = new Uint8Array(mb);
+ return rfc2083_crc(u8, at, len);
+}
+
+function PngIHDR(width, height)
+{
+ this.width = width;
+ this.height = height;
+ this.depth = 8;
+ this.type = 6;
+ this.compression = 0;
+ this.filter = 0;
+ this.interlace = 0;
+}
+
+PngIHDR.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var orig = at;
+ var dv = new SpiceDataView(a);
+ dv.setUint32(at, this.buffer_size() - 12); at += 4;
+ dv.setUint8(at, 'I'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'H'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'D'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'R'.charCodeAt(0)); at++;
+ dv.setUint32(at, this.width); at += 4;
+ dv.setUint32(at, this.height); at += 4;
+ dv.setUint8(at, this.depth); at++;
+ dv.setUint8(at, this.type); at++;
+ dv.setUint8(at, this.compression); at++;
+ dv.setUint8(at, this.filter); at++;
+ dv.setUint8(at, this.interlace); at++;
+ dv.setUint32(at, crc32(a, orig + 4, this.buffer_size() - 8)); at += 4;
+ return at;
+ },
+ buffer_size: function()
+ {
+ return 12 + 13;
+ }
+}
+
+
+function adler()
+{
+ this.s1 = 1;
+ this.s2 = 0;
+}
+
+adler.prototype.update = function(b)
+{
+ this.s1 += b;
+ this.s1 %= 65521;
+ this.s2 += this.s1;
+ this.s2 %= 65521;
+}
+
+function PngIDAT(width, height, bytes)
+{
+ if (bytes.byteLength > 65535)
+ {
+ throw new Error("Cannot handle more than 64K");
+ }
+ this.data = bytes;
+ this.width = width;
+ this.height = height;
+}
+
+PngIDAT.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var orig = at;
+ var x, y, i, j;
+ var dv = new SpiceDataView(a);
+ var zsum = new adler();
+ dv.setUint32(at, this.buffer_size() - 12); at += 4;
+ dv.setUint8(at, 'I'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'D'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'A'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'T'.charCodeAt(0)); at++;
+
+ /* zlib header. */
+ dv.setUint8(at, 0x78); at++;
+ dv.setUint8(at, 0x01); at++;
+
+ /* Deflate header. Specifies uncompressed, final bit */
+ dv.setUint8(at, 0x80); at++;
+ dv.setUint16(at, this.data.byteLength + this.height); at += 2;
+ dv.setUint16(at, ~(this.data.byteLength + this.height)); at += 2;
+ var u8 = new Uint8Array(this.data);
+ for (i = 0, y = 0; y < this.height; y++)
+ {
+ /* Filter type 0 - uncompressed */
+ dv.setUint8(at, 0); at++;
+ zsum.update(0);
+ for (x = 0; x < this.width && i < this.data.byteLength; x++)
+ {
+ zsum.update(u8[i]);
+ dv.setUint8(at, u8[i++]); at++;
+ zsum.update(u8[i]);
+ dv.setUint8(at, u8[i++]); at++;
+ zsum.update(u8[i]);
+ dv.setUint8(at, u8[i++]); at++;
+ zsum.update(u8[i]);
+ dv.setUint8(at, u8[i++]); at++;
+ }
+ }
+
+ /* zlib checksum. */
+ dv.setUint16(at, zsum.s2); at+=2;
+ dv.setUint16(at, zsum.s1); at+=2;
+
+ /* FIXME - something is not quite right with the zlib code;
+ you get an error from libpng if you open the image in
+ gimp. But it works, so it's good enough for now... */
+
+ dv.setUint32(at, crc32(a, orig + 4, this.buffer_size() - 8)); at += 4;
+ return at;
+ },
+ buffer_size: function()
+ {
+ return 12 + this.data.byteLength + this.height + 4 + 2 + 1 + 2 + 2;
+ }
+}
+
+
+function PngIEND()
+{
+}
+
+PngIEND.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var orig = at;
+ var i;
+ var dv = new SpiceDataView(a);
+ dv.setUint32(at, this.buffer_size() - 12); at += 4;
+ dv.setUint8(at, 'I'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'E'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'N'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'D'.charCodeAt(0)); at++;
+ dv.setUint32(at, crc32(a, orig + 4, this.buffer_size() - 8)); at += 4;
+ return at;
+ },
+ buffer_size: function()
+ {
+ return 12;
+ }
+}
+
+
+function create_rgba_png(width, height, bytes)
+{
+ var i;
+ var ihdr = new PngIHDR(width, height);
+ var idat = new PngIDAT(width, height, bytes);
+ var iend = new PngIEND;
+
+ var mb = new ArrayBuffer(ihdr.buffer_size() + idat.buffer_size() + iend.buffer_size());
+ var at = ihdr.to_buffer(mb);
+ at = idat.to_buffer(mb, at);
+ at = iend.to_buffer(mb, at);
+
+ var u8 = new Uint8Array(mb);
+ var str = "";
+ for (i = 0; i < at; i++)
+ {
+ str += "%";
+ if (u8[i] < 16)
+ str += "0";
+ str += u8[i].toString(16);
+ }
+
+
+ return "%89PNG%0D%0A%1A%0A" + str;
+}
diff --git a/ui/js/spice/prng4.js b/ui/js/spice/prng4.js
new file mode 100644
index 0000000..ef3efd6
--- /dev/null
+++ b/ui/js/spice/prng4.js
@@ -0,0 +1,79 @@
+// Downloaded from http://www-cs-students.stanford.edu/~tjw/jsbn/ by Jeremy White on 6/1/2012
+
+/*
+ * Copyright (c) 2003-2005 Tom Wu
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
+ * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * In addition, the following condition applies:
+ *
+ * All redistributions must retain an intact copy of this copyright notice
+ * and disclaimer.
+ */
+
+
+// prng4.js - uses Arcfour as a PRNG
+
+function Arcfour() {
+ this.i = 0;
+ this.j = 0;
+ this.S = new Array();
+}
+
+// Initialize arcfour context from key, an array of ints, each from [0..255]
+function ARC4init(key) {
+ var i, j, t;
+ for(i = 0; i < 256; ++i)
+ this.S[i] = i;
+ j = 0;
+ for(i = 0; i < 256; ++i) {
+ j = (j + this.S[i] + key[i % key.length]) & 255;
+ t = this.S[i];
+ this.S[i] = this.S[j];
+ this.S[j] = t;
+ }
+ this.i = 0;
+ this.j = 0;
+}
+
+function ARC4next() {
+ var t;
+ this.i = (this.i + 1) & 255;
+ this.j = (this.j + this.S[this.i]) & 255;
+ t = this.S[this.i];
+ this.S[this.i] = this.S[this.j];
+ this.S[this.j] = t;
+ return this.S[(t + this.S[this.i]) & 255];
+}
+
+Arcfour.prototype.init = ARC4init;
+Arcfour.prototype.next = ARC4next;
+
+// Plug in your RNG constructor here
+function prng_newstate() {
+ return new Arcfour();
+}
+
+// Pool size must be a multiple of 4 and greater than 32.
+// An array of bytes the size of the pool will be passed to init()
+var rng_psize = 256;
diff --git a/ui/js/spice/quic.js b/ui/js/spice/quic.js
new file mode 100644
index 0000000..9bb9f47
--- /dev/null
+++ b/ui/js/spice/quic.js
@@ -0,0 +1,1335 @@
+/*"use strict";*/
+/* use strict is commented out because it results in a 5x slowdone in chrome */
+/*
+ * Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+ * Copyright (C) 2012 by Aric Stewart <aric(a)codeweavers.com>
+ *
+ * This file is part of spice-html5.
+ *
+ * spice-html5 is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * spice-html5 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+var encoder;
+
+var QUIC_IMAGE_TYPE_INVALID = 0;
+var QUIC_IMAGE_TYPE_GRAY = 1;
+var QUIC_IMAGE_TYPE_RGB16 = 2;
+var QUIC_IMAGE_TYPE_RGB24 = 3;
+var QUIC_IMAGE_TYPE_RGB32 = 4;
+var QUIC_IMAGE_TYPE_RGBA = 5;
+var DEFevol = 3;
+var DEFwmimax = 6;
+var DEFwminext = 2048;
+var need_init = true;
+var DEFmaxclen = 26;
+var evol = DEFevol;
+var wmimax = DEFwmimax;
+var wminext = DEFwminext;
+var family_5bpc = { nGRcodewords:[0,0,0,0,0,0,0,0],
+ notGRcwlen:[0,0,0,0,0,0,0,0],
+ notGRprefixmask:[0,0,0,0,0,0,0,0],
+ notGRsuffixlen:[0,0,0,0,0,0,0,0],
+ xlatU2L:[0,0,0,0,0,0,0,0],
+ xlatL2U:[0,0,0,0,0,0,0,0]
+ };
+var family_8bpc = { nGRcodewords:[0,0,0,0,0,0,0,0],
+ notGRcwlen:[0,0,0,0,0,0,0,0],
+ notGRprefixmask:[0,0,0,0,0,0,0,0],
+ notGRsuffixlen:[0,0,0,0,0,0,0,0],
+ xlatU2L:[0,0,0,0,0,0,0,0],
+ xlatL2U:[0,0,0,0,0,0,0,0]
+ };
+var bppmask = [ 0x00000000,
+ 0x00000001, 0x00000003, 0x00000007, 0x0000000f,
+ 0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff,
+ 0x000001ff, 0x000003ff, 0x000007ff, 0x00000fff,
+ 0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff,
+ 0x0001ffff, 0x0003ffff, 0x0007ffff, 0x000fffff,
+ 0x001fffff, 0x003fffff, 0x007fffff, 0x00ffffff,
+ 0x01ffffff, 0x03ffffff, 0x07ffffff, 0x0fffffff,
+ 0x1fffffff, 0x3fffffff, 0x7fffffff, 0xffffffff];
+
+var zeroLUT = [];
+
+var besttrigtab = [
+ [ 550, 900, 800, 700, 500, 350, 300, 200, 180, 180, 160],
+ [ 110, 550, 900, 800, 550, 400, 350, 250, 140, 160, 140],
+ [ 100, 120, 550, 900, 700, 500, 400, 300, 220, 250, 160]];
+
+var J = [ 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 5, 5, 6, 6,
+ 7, 7, 8, 9, 10, 11, 12, 13, 14, 15];
+
+var lzeroes = [
+ 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0];
+
+var tabrand_chaos = [
+ 0x02c57542, 0x35427717, 0x2f5a2153, 0x9244f155, 0x7bd26d07, 0x354c6052,
+ 0x57329b28, 0x2993868e, 0x6cd8808c, 0x147b46e0, 0x99db66af, 0xe32b4cac,
+ 0x1b671264, 0x9d433486, 0x62a4c192, 0x06089a4b, 0x9e3dce44, 0xdaabee13,
+ 0x222425ea, 0xa46f331d, 0xcd589250, 0x8bb81d7f, 0xc8b736b9, 0x35948d33,
+ 0xd7ac7fd0, 0x5fbe2803, 0x2cfbc105, 0x013dbc4e, 0x7a37820f, 0x39f88e9e,
+ 0xedd58794, 0xc5076689, 0xfcada5a4, 0x64c2f46d, 0xb3ba3243, 0x8974b4f9,
+ 0x5a05aebd, 0x20afcd00, 0x39e2b008, 0x88a18a45, 0x600bde29, 0xf3971ace,
+ 0xf37b0a6b, 0x7041495b, 0x70b707ab, 0x06beffbb, 0x4206051f, 0xe13c4ee3,
+ 0xc1a78327, 0x91aa067c, 0x8295f72a, 0x732917a6, 0x1d871b4d, 0x4048f136,
+ 0xf1840e7e, 0x6a6048c1, 0x696cb71a, 0x7ff501c3, 0x0fc6310b, 0x57e0f83d,
+ 0x8cc26e74, 0x11a525a2, 0x946934c7, 0x7cd888f0, 0x8f9d8604, 0x4f86e73b,
+ 0x04520316, 0xdeeea20c, 0xf1def496, 0x67687288, 0xf540c5b2, 0x22401484,
+ 0x3478658a, 0xc2385746, 0x01979c2c, 0x5dad73c8, 0x0321f58b, 0xf0fedbee,
+ 0x92826ddf, 0x284bec73, 0x5b1a1975, 0x03df1e11, 0x20963e01, 0xa17cf12b,
+ 0x740d776e, 0xa7a6bf3c, 0x01b5cce4, 0x1118aa76, 0xfc6fac0a, 0xce927e9b,
+ 0x00bf2567, 0x806f216c, 0xbca69056, 0x795bd3e9, 0xc9dc4557, 0x8929b6c2,
+ 0x789d52ec, 0x3f3fbf40, 0xb9197368, 0xa38c15b5, 0xc3b44fa8, 0xca8333b0,
+ 0xb7e8d590, 0xbe807feb, 0xbf5f8360, 0xd99e2f5c, 0x372928e1, 0x7c757c4c,
+ 0x0db5b154, 0xc01ede02, 0x1fc86e78, 0x1f3985be, 0xb4805c77, 0x00c880fa,
+ 0x974c1b12, 0x35ab0214, 0xb2dc840d, 0x5b00ae37, 0xd313b026, 0xb260969d,
+ 0x7f4c8879, 0x1734c4d3, 0x49068631, 0xb9f6a021, 0x6b863e6f, 0xcee5debf,
+ 0x29f8c9fb, 0x53dd6880, 0x72b61223, 0x1f67a9fd, 0x0a0f6993, 0x13e59119,
+ 0x11cca12e, 0xfe6b6766, 0x16b6effc, 0x97918fc4, 0xc2b8a563, 0x94f2f741,
+ 0x0bfa8c9a, 0xd1537ae8, 0xc1da349c, 0x873c60ca, 0x95005b85, 0x9b5c080e,
+ 0xbc8abbd9, 0xe1eab1d2, 0x6dac9070, 0x4ea9ebf1, 0xe0cf30d4, 0x1ef5bd7b,
+ 0xd161043e, 0x5d2fa2e2, 0xff5d3cae, 0x86ed9f87, 0x2aa1daa1, 0xbd731a34,
+ 0x9e8f4b22, 0xb1c2c67a, 0xc21758c9, 0xa182215d, 0xccb01948, 0x8d168df7,
+ 0x04238cfe, 0x368c3dbc, 0x0aeadca5, 0xbad21c24, 0x0a71fee5, 0x9fc5d872,
+ 0x54c152c6, 0xfc329483, 0x6783384a, 0xeddb3e1c, 0x65f90e30, 0x884ad098,
+ 0xce81675a, 0x4b372f7d, 0x68bf9a39, 0x43445f1e, 0x40f8d8cb, 0x90d5acb6,
+ 0x4cd07282, 0x349eeb06, 0x0c9d5332, 0x520b24ef, 0x80020447, 0x67976491,
+ 0x2f931ca3, 0xfe9b0535, 0xfcd30220, 0x61a9e6cc, 0xa487d8d7, 0x3f7c5dd1,
+ 0x7d0127c5, 0x48f51d15, 0x60dea871, 0xc9a91cb7, 0x58b53bb3, 0x9d5e0b2d,
+ 0x624a78b4, 0x30dbee1b, 0x9bdf22e7, 0x1df5c299, 0x2d5643a7, 0xf4dd35ff,
+ 0x03ca8fd6, 0x53b47ed8, 0x6f2c19aa, 0xfeb0c1f4, 0x49e54438, 0x2f2577e6,
+ 0xbf876969, 0x72440ea9, 0xfa0bafb8, 0x74f5b3a0, 0x7dd357cd, 0x89ce1358,
+ 0x6ef2cdda, 0x1e7767f3, 0xa6be9fdb, 0x4f5f88f8, 0xba994a3a, 0x08ca6b65,
+ 0xe0893818, 0x9e00a16a, 0xf42bfc8f, 0x9972eedc, 0x749c8b51, 0x32c05f5e,
+ 0xd706805f, 0x6bfbb7cf, 0xd9210a10, 0x31a1db97, 0x923a9559, 0x37a7a1f6,
+ 0x059f8861, 0xca493e62, 0x65157e81, 0x8f6467dd, 0xab85ff9f, 0x9331aff2,
+ 0x8616b9f5, 0xedbd5695, 0xee7e29b1, 0x313ac44f, 0xb903112f, 0x432ef649,
+ 0xdc0a36c0, 0x61cf2bba, 0x81474925, 0xa8b6c7ad, 0xee5931de, 0xb2f8158d,
+ 0x59fb7409, 0x2e3dfaed, 0x9af25a3f, 0xe1fed4d5 ];
+
+var rgb32_pixel_pad = 3;
+var rgb32_pixel_r = 2;
+var rgb32_pixel_g = 1;
+var rgb32_pixel_b = 0;
+var rgb32_pixel_size = 4;
+
+/* Helper Functions */
+
+function ceil_log_2(val)
+{
+ if (val === 1)
+ return 0;
+
+ var result = 1;
+ val -= 1;
+ while (val = val >>> 1)
+ result++;
+
+ return result;
+}
+
+function family_init(family, bpc, limit)
+{
+ var l;
+ for (l = 0; l < bpc; l++)
+ {
+ var altprefixlen, altcodewords;
+ altprefixlen = limit - bpc;
+ if (altprefixlen > bppmask[bpc - l])
+ altprefixlen = bppmask[bpc - l];
+
+ altcodewords = bppmask[bpc] + 1 - (altprefixlen << l);
+ family.nGRcodewords[l] = (altprefixlen << l);
+ family.notGRcwlen[l] = altprefixlen + ceil_log_2(altcodewords);
+ family.notGRprefixmask[l] = bppmask[32 - altprefixlen]>>>0;
+ family.notGRsuffixlen[l] = ceil_log_2(altcodewords);
+ }
+
+ /* decorelate_init */
+ var pixelbitmask = bppmask[bpc];
+ var pixelbitmaskshr = pixelbitmask >>> 1;
+ var s;
+ for (s = 0; s <= pixelbitmask; s++) {
+ if (s <= pixelbitmaskshr) {
+ family.xlatU2L[s] = s << 1;
+ } else {
+ family.xlatU2L[s] = ((pixelbitmask - s) << 1) + 1;
+ }
+ }
+
+ /* corelate_init */
+ for (s = 0; s <= pixelbitmask; s++) {
+ if (s & 0x01) {
+ family.xlatL2U[s] = pixelbitmask - (s >>> 1);
+ } else {
+ family.xlatL2U[s] = (s >>> 1);
+ }
+ }
+}
+
+function quic_image_bpc(type)
+{
+ switch (type) {
+ case QUIC_IMAGE_TYPE_GRAY:
+ return 8;
+ case QUIC_IMAGE_TYPE_RGB16:
+ return 5;
+ case QUIC_IMAGE_TYPE_RGB24:
+ return 8;
+ case QUIC_IMAGE_TYPE_RGB32:
+ return 8;
+ case QUIC_IMAGE_TYPE_RGBA:
+ return 8;
+ case QUIC_IMAGE_TYPE_INVALID:
+ default:
+ console.log("quic: bad image type\n");
+ return 0;
+ }
+}
+
+function cnt_l_zeroes(bits)
+{
+ if (bits & 0xff800000) {
+ return lzeroes[bits >>> 24];
+ } else if (bits & 0xffff8000) {
+ return 8 + lzeroes[(bits >>> 16) & 0x000000ff];
+ } else if (bits & 0xffffff80) {
+ return 16 + lzeroes[(bits >>> 8) & 0x000000ff];
+ } else {
+ return 24 + lzeroes[bits & 0x000000ff];
+ }
+}
+
+function golomb_decoding_8bpc(l, bits)
+{
+ var rc;
+ var cwlen;
+
+ if (bits < 0 || bits > family_8bpc.notGRprefixmask[l])
+ {
+ var zeroprefix = cnt_l_zeroes(bits);
+ cwlen = zeroprefix + 1 + l;
+ rc = (zeroprefix << l) | (bits >> (32-cwlen)) & bppmask[l];
+ }
+ else
+ {
+ cwlen = family_8bpc.notGRcwlen[l];
+ rc = family_8bpc.nGRcodewords[l] + ((bits >> (32-cwlen)) & bppmask[family_8bpc.notGRsuffixlen[l]]);
+ }
+ return {'codewordlen':cwlen, 'rc':rc};
+}
+
+function golomb_code_len_8bpc(n, l)
+{
+ if (n < family_8bpc.nGRcodewords[l]) {
+ return (n >>> l) + 1 + l;
+ } else {
+ return family_8bpc.notGRcwlen[l];
+ }
+}
+
+function QuicModel(bpc)
+{
+ var bstart;
+ var bend = 0;
+
+ this.levels = 0x1 << bpc;
+ this.n_buckets_ptrs = 0;
+
+ switch (evol) {
+ case 1:
+ this.repfirst = 3;
+ this.firstsize = 1;
+ this.repnext = 2;
+ this.mulsize = 2;
+ break;
+ case 3:
+ this.repfirst = 1;
+ this.firstsize = 1;
+ this.repnext = 1;
+ this.mulsize = 2;
+ break;
+ case 5:
+ this.repfirst = 1;
+ this.firstsize = 1;
+ this.repnext = 1;
+ this.mulsize = 4;
+ break;
+ case 0:
+ case 2:
+ case 4:
+ console.log("quic: findmodelparams(): evol value obsolete!!!\n");
+ default:
+ console.log("quic: findmodelparams(): evol out of range!!!\n");
+ }
+
+ this.n_buckets = 0;
+ var repcntr = this.repfirst + 1;
+ var bsize = this.firstsize;
+
+ do {
+ if (this.n_buckets) {
+ bstart = bend + 1;
+ } else {
+ bstart = 0;
+ }
+
+ if (!--repcntr) {
+ repcntr = this.repnext;
+ bsize *= this.mulsize;
+ }
+
+ bend = bstart + bsize - 1;
+ if (bend + bsize >= this.levels) {
+ bend = this.levels - 1;
+ }
+
+ if (!this.n_buckets_ptrs) {
+ this.n_buckets_ptrs = this.levels;
+ }
+
+ (this.n_buckets)++;
+ } while (bend < this.levels - 1);
+}
+
+QuicModel.prototype = {
+ n_buckets : 0,
+ n_buckets_ptrs : 0,
+ repfirst : 0,
+ firstsize : 0,
+ repnext : 0,
+ mulsize : 0,
+ levels :0
+}
+
+function QuicBucket()
+{
+ this.counters = [0,0,0,0,0,0,0,0];
+}
+
+QuicBucket.prototype = {
+ bestcode: 0,
+
+ reste : function (bpp)
+ {
+ this.bestcode = bpp;
+ this.counters = [0,0,0,0,0,0,0,0];
+ },
+
+ update_model_8bpc : function (state, curval, bpp)
+ {
+ var i;
+
+ var bestcode = bpp - 1;
+ var bestcodelen = (this.counters[bestcode] += golomb_code_len_8bpc(curval, bestcode));
+
+ for (i = bpp - 2; i >= 0; i--) {
+ var ithcodelen = (this.counters[i] += golomb_code_len_8bpc(curval, i));
+
+ if (ithcodelen < bestcodelen) {
+ bestcode = i;
+ bestcodelen = ithcodelen;
+ }
+ }
+
+ this.bestcode = bestcode;
+
+ if (bestcodelen > state.wm_trigger) {
+ for (i = 0; i < bpp; i++) {
+ this.counters[i] = this.counters[i] >>> 1;
+ }
+ }
+ }
+}
+
+function QuicFamilyStat()
+{
+ this.buckets_ptrs = [];
+ this.buckets_buf = [];
+}
+
+QuicFamilyStat.prototype = {
+
+ fill_model_structures : function(model)
+ {
+ var bstart;
+ var bend = 0;
+ var bnumber = 0;
+
+ var repcntr = model.repfirst + 1;
+ var bsize = model.firstsize;
+
+ do {
+ if (bnumber) {
+ bstart = bend + 1;
+ } else {
+ bstart = 0;
+ }
+
+ if (!--repcntr) {
+ repcntr = model.repnext;
+ bsize *= model.mulsize;
+ }
+
+ bend = bstart + bsize - 1;
+ if (bend + bsize >= model.levels) {
+ bend = model.levels - 1;
+ }
+
+ this.buckets_buf[bnumber] = new QuicBucket;
+
+ var i;
+ for (i = bstart; i <= bend; i++) {
+ this.buckets_ptrs[i] = this.buckets_buf[bnumber];
+ }
+
+ bnumber++;
+ } while (bend < model.levels - 1);
+ return true;
+ }
+}
+
+function QuicChannel(model_8bpc, model_5bpc)
+{
+ this.state = new CommonState;
+ this.family_stat_8bpc = new QuicFamilyStat;
+ this.family_stat_5bpc = new QuicFamilyStat;
+ this.correlate_row = { zero: 0 , row:[] };
+ this.model_8bpc = model_8bpc;
+ this.model_5bpc = model_5bpc;
+ this.buckets_ptrs = [];
+
+ if (!this.family_stat_8bpc.fill_model_structures(this.model_8bpc))
+ return undefined;
+
+ if (!this.family_stat_5bpc.fill_model_structures(this.model_5bpc))
+ return undefined;
+}
+
+QuicChannel.prototype = {
+
+ reste : function (bpc)
+ {
+ var j;
+ this.correlate_row = { zero: 0 , row: []};
+
+ if (bpc == 8) {
+ for (j = 0; j < this.model_8bpc.n_buckets; j++)
+ this.family_stat_8bpc.buckets_buf[j].reste(7);
+ this.buckets_ptrs = this.family_stat_8bpc.buckets_ptrs;
+ } else if (bpc == 5) {
+ for (j = 0; j < this.model_5bpc.n_buckets; j++)
+ this.family_stat_8bpc.buckets_buf[j].reste(4);
+ this.buckets_ptrs = this.family_stat_5bpc.buckets_ptrs;
+ } else {
+ console.log("quic: %s: bad bpc %d\n", __FUNCTION__, bpc);
+ return false;
+ }
+
+ this.state.reste();
+ return true;
+ }
+}
+
+function CommonState()
+{
+}
+
+CommonState.prototype = {
+ waitcnt: 0,
+ tabrand_seed: 0xff,
+ wm_trigger: 0,
+ wmidx: 0,
+ wmileft: wminext,
+ melcstate: 0,
+ melclen: 0,
+ melcorder: 0,
+
+ set_wm_trigger : function()
+ {
+ var wm = this.wmidx;
+ if (wm > 10) {
+ wm = 10;
+ }
+
+ this.wm_trigger = besttrigtab[Math.floor(evol / 2)][wm];
+ },
+
+ reste : function()
+ {
+ this.waitcnt = 0;
+ this.tabrand_seed = 0x0ff;
+ this.wmidx = 0;
+ this.wmileft = wminext;
+
+ this.set_wm_trigger();
+
+ this.melcstate = 0;
+ this.melclen = J[0];
+ this.melcorder = 1 << this.melclen;
+ },
+
+ tabrand : function()
+ {
+ this.tabrand_seed++;
+ return tabrand_chaos[this.tabrand_seed & 0x0ff];
+ }
+}
+
+
+function QuicEncoder()
+{
+ this.rgb_state = new CommonState;
+ this.model_8bpc = new QuicModel(8);
+ this.model_5bpc = new QuicModel(5);
+ this.channels = [];
+
+ var i;
+ for (i = 0; i < 4; i++) {
+ this.channels[i] = new QuicChannel(this.model_8bpc, this.model_5bpc);
+ if (!this.channels[i])
+ {
+ console.log("quic: failed to create channel");
+ return undefined;
+ }
+ }
+}
+
+QuicEncoder.prototype = {
+ type: 0,
+ width: 0,
+ height: 0,
+ io_idx: 0,
+ io_available_bits: 0,
+ io_word: 0,
+ io_next_word: 0,
+ io_now: 0,
+ io_end: 0,
+ rows_completed: 0,
+ };
+
+QuicEncoder.prototype.reste = function(io_ptr)
+{
+ this.rgb_state.reste();
+
+ this.io_now = io_ptr;
+ this.io_end = this.io_now.length;
+ this.io_idx = 0;
+ this.rows_completed = 0;
+ return true;
+}
+
+QuicEncoder.prototype.read_io_word = function()
+{
+ if (this.io_idx >= this.io_end)
+ throw("quic: out of data");
+ this.io_next_word = this.io_now[this.io_idx++] | this.io_now[this.io_idx++]<<8 | this.io_now[this.io_idx++]<<16 | this.io_now[this.io_idx++]<<24;
+}
+
+QuicEncoder.prototype.decode_eatbits = function (len)
+{
+ this.io_word = this.io_word << len;
+
+ var delta = (this.io_available_bits - len);
+ if (delta >= 0)
+ {
+ this.io_available_bits = delta;
+ this.io_word |= this.io_next_word >>> this.io_available_bits;
+ }
+ else
+ {
+ delta = -1 * delta;
+ this.io_word |= this.io_next_word << delta;
+ this.read_io_word();
+ this.io_available_bits = 32 - delta;
+ this.io_word |= this.io_next_word >>> this.io_available_bits;
+ }
+}
+
+QuicEncoder.prototype.decode_eat32bits = function()
+{
+ this.decode_eatbits(16);
+ this.decode_eatbits(16);
+}
+
+QuicEncoder.prototype.reste_channels = function(bpc)
+{
+ var i;
+
+ for (i = 0; i < 4; i++)
+ if (!this.channels[i].reste(bpc))
+ return false;
+ return true;
+}
+
+QuicEncoder.prototype.quic_decode_begin = function(io_ptr)
+{
+ if (!this.reste(io_ptr)) {
+ return false;
+ }
+
+ this.io_idx = 0;
+ this.io_next_word = this.io_now[this.io_idx++] | this.io_now[this.io_idx++]<<8 | this.io_now[this.io_idx++]<<16 | this.io_now[this.io_idx++]<<24;
+ this.io_word = this.io_next_word;
+ this.io_available_bits = 0;
+
+ var magic = this.io_word;
+ this.decode_eat32bits();
+ if (magic != 0x43495551) /*QUIC*/ {
+ console.log("quic: bad magic "+magic.toString(16));
+ return false;
+ }
+
+ var version = this.io_word;
+ this.decode_eat32bits();
+ if (version != ((0 << 16) | (0 & 0xffff))) {
+ console.log("quic: bad version "+version.toString(16));
+ return false;
+ }
+
+ this.type = this.io_word;
+ this.decode_eat32bits();
+
+ this.width = this.io_word;
+ this.decode_eat32bits();
+
+ this.height = this.io_word;
+ this.decode_eat32bits();
+
+ var bpc = quic_image_bpc(this.type);
+
+ if (!this.reste_channels(bpc))
+ return false;
+
+ return true;
+}
+
+QuicEncoder.prototype.quic_rgb32_uncompress_row0_seg = function (i, cur_row, end,
+ waitmask, bpc, bpc_mask)
+{
+ var stopidx;
+ var n_channels = 3;
+ var c;
+ var a;
+
+ if (!i) {
+ cur_row[rgb32_pixel_pad] = 0;
+ c = 0;
+ do
+ {
+ a = golomb_decoding_8bpc(this.channels[c].buckets_ptrs[this.channels[c].correlate_row.zero].bestcode, this.io_word);
+ this.channels[c].correlate_row.row[0] = a.rc;
+ cur_row[2-c] = (family_8bpc.xlatL2U[a.rc]&0xFF);
+ this.decode_eatbits(a.codewordlen);
+ } while (++c < n_channels);
+
+ if (this.rgb_state.waitcnt) {
+ --this.rgb_state.waitcnt;
+ } else {
+ this.rgb_state.waitcnt = (this.rgb_state.tabrand() & waitmask);
+ c = 0;
+ do
+ {
+ this.channels[c].buckets_ptrs[this.channels[c].correlate_row.zero].update_model_8bpc(this.rgb_state, this.channels[c].correlate_row.row[0], bpc);
+ } while (++c < n_channels);
+ }
+ stopidx = ++i + this.rgb_state.waitcnt;
+ } else {
+ stopidx = i + this.rgb_state.waitcnt;
+ }
+
+ while (stopidx < end) {
+ for (; i <= stopidx; i++) {
+ cur_row[(i* rgb32_pixel_size)+rgb32_pixel_pad] = 0;
+ c = 0;
+ do
+ {
+ a = golomb_decoding_8bpc(this.channels[c].buckets_ptrs[this.channels[c].correlate_row.row[i - 1]].bestcode, this.io_word);
+ this.channels[c].correlate_row.row[i] = a.rc;
+ cur_row[(i* rgb32_pixel_size)+(2-c)] = (family_8bpc.xlatL2U[a.rc] + cur_row[((i-1) * rgb32_pixel_size) + (2-c)]) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ } while (++c < n_channels);
+ }
+ c = 0;
+ do
+ {
+ this.channels[c].buckets_ptrs[this.channels[c].correlate_row.row[stopidx - 1]].update_model_8bpc(this.rgb_state, this.channels[c].correlate_row.row[stopidx], bpc);
+ } while (++c < n_channels);
+ stopidx = i + (this.rgb_state.tabrand() & waitmask);
+ }
+
+ for (; i < end; i++) {
+ cur_row[(i* rgb32_pixel_size)+rgb32_pixel_pad] = 0;
+ c = 0;
+ do
+ {
+ a = golomb_decoding_8bpc(this.channels[c].buckets_ptrs[this.channels[c].correlate_row.row[i - 1]].bestcode, this.io_word);
+ this.channels[c].correlate_row.row[i] = a.rc;
+ cur_row[(i* rgb32_pixel_size)+(2-c)] = (family_8bpc.xlatL2U[a.rc] + cur_row[((i-1) * rgb32_pixel_size) + (2-c)]) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ } while (++c < n_channels);
+ }
+ this.rgb_state.waitcnt = stopidx - end;
+}
+
+QuicEncoder.prototype.quic_rgb32_uncompress_row0 = function (cur_row)
+{
+ var bpc = 8;
+ var bpc_mask = 0xff;
+ var pos = 0;
+ var width = this.width;
+
+ while ((wmimax > this.rgb_state.wmidx) && (this.rgb_state.wmileft <= width)) {
+ if (this.rgb_state.wmileft) {
+ this.quic_rgb32_uncompress_row0_seg(pos, cur_row,
+ pos + this.rgb_state.wmileft,
+ bppmask[this.rgb_state.wmidx],
+ bpc, bpc_mask);
+ pos += this.rgb_state.wmileft;
+ width -= this.rgb_state.wmileft;
+ }
+
+ this.rgb_state.wmidx++;
+ this.rgb_state.set_wm_trigger();
+ this.rgb_state.wmileft = wminext;
+ }
+
+ if (width) {
+ this.quic_rgb32_uncompress_row0_seg(pos, cur_row, pos + width,
+ bppmask[this.rgb_state.wmidx], bpc, bpc_mask);
+ if (wmimax > this.rgb_state.wmidx) {
+ this.rgb_state.wmileft -= width;
+ }
+ }
+}
+
+QuicEncoder.prototype.quic_rgb32_uncompress_row_seg = function( prev_row, cur_row, i, end, bpc, bpc_mask)
+{
+ var n_channels = 3;
+ var waitmask = bppmask[this.rgb_state.wmidx];
+
+ var a;
+ var run_index = 0;
+ var stopidx = 0;
+ var run_end = 0;
+ var c;
+
+ if (!i)
+ {
+ cur_row[rgb32_pixel_pad] = 0;
+
+ c = 0;
+ do {
+ a = golomb_decoding_8bpc(this.channels[c].buckets_ptrs[this.channels[c].correlate_row.zero].bestcode, this.io_word);
+ this.channels[c].correlate_row.row[0] = a.rc;
+ cur_row[2-c] = (family_8bpc.xlatL2U[this.channels[c].correlate_row.row[0]] + prev_row[2-c]) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ } while (++c < n_channels);
+
+ if (this.rgb_state.waitcnt) {
+ --this.rgb_state.waitcnt;
+ } else {
+ this.rgb_state.waitcnt = (this.rgb_state.tabrand() & waitmask);
+ c = 0;
+ do {
+ this.channels[c].buckets_ptrs[this.channels[c].correlate_row.zero].update_model_8bpc(this.rgb_state, this.channels[c].correlate_row.row[0], bpc);
+ } while (++c < n_channels);
+ }
+ stopidx = ++i + this.rgb_state.waitcnt;
+ } else {
+ stopidx = i + this.rgb_state.waitcnt;
+ }
+ for (;;) {
+ var rc = 0;
+ while (stopidx < end && !rc) {
+ for (; i <= stopidx && !rc; i++) {
+ var pixel = i * rgb32_pixel_size;
+ var pixelm1 = (i-1) * rgb32_pixel_size;
+ var pixelm2 = (i-2) * rgb32_pixel_size;
+
+ if ( prev_row[pixelm1+rgb32_pixel_r] == prev_row[pixel+rgb32_pixel_r] && prev_row[pixelm1+rgb32_pixel_g] == prev_row[pixel+rgb32_pixel_g] && prev_row[pixelm1 + rgb32_pixel_b] == prev_row[pixel+rgb32_pixel_b])
+ {
+ if (run_index != i && i > 2 && (cur_row[pixelm1+rgb32_pixel_r] == cur_row[pixelm2+rgb32_pixel_r] && cur_row[pixelm1+rgb32_pixel_g] == cur_row[pixelm2+rgb32_pixel_g] && cur_row[pixelm1+rgb32_pixel_b] == cur_row[pixelm2+rgb32_pixel_b]))
+ {
+ /* do run */
+ this.rgb_state.waitcnt = stopidx - i;
+ run_index = i;
+ run_end = i + this.decode_run(this.rgb_state);
+
+ for (; i < run_end; i++) {
+ var pixel = i * rgb32_pixel_size;
+ var pixelm1 = (i-1) * rgb32_pixel_size;
+ cur_row[pixel+rgb32_pixel_pad] = 0;
+ cur_row[pixel+rgb32_pixel_r] = cur_row[pixelm1+rgb32_pixel_r];
+ cur_row[pixel+rgb32_pixel_g] = cur_row[pixelm1+rgb32_pixel_g];
+ cur_row[pixel+rgb32_pixel_b] = cur_row[pixelm1+rgb32_pixel_b];
+ }
+
+ if (i == end) {
+ return;
+ }
+ else
+ {
+ stopidx = i + this.rgb_state.waitcnt;
+ rc = 1;
+ break;
+ }
+ }
+ }
+
+ c = 0;
+ cur_row[pixel+rgb32_pixel_pad] = 0;
+ do {
+ var cc = this.channels[c];
+ var cr = cc.correlate_row;
+
+ a = golomb_decoding_8bpc(cc.buckets_ptrs[cr.row[i-1]].bestcode, this.io_word);
+ cr.row[i] = a.rc;
+ cur_row[pixel+(2-c)] = (family_8bpc.xlatL2U[a.rc] + ((cur_row[pixelm1+(2-c)] + prev_row[pixel+(2-c)]) >> 1)) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ } while (++c < n_channels);
+ }
+ if (rc)
+ break;
+
+ c = 0;
+ do {
+ this.channels[c].buckets_ptrs[this.channels[c].correlate_row.row[stopidx - 1]].update_model_8bpc(this.rgb_state, this.channels[c].correlate_row.row[stopidx], bpc);
+ } while (++c < n_channels);
+
+ stopidx = i + (this.rgb_state.tabrand() & waitmask);
+ }
+
+ for (; i < end && !rc; i++) {
+ var pixel = i * rgb32_pixel_size;
+ var pixelm1 = (i-1) * rgb32_pixel_size;
+ var pixelm2 = (i-2) * rgb32_pixel_size;
+
+ if (prev_row[pixelm1+rgb32_pixel_r] == prev_row[pixel+rgb32_pixel_r] && prev_row[pixelm1+rgb32_pixel_g] == prev_row[pixel+rgb32_pixel_g] && prev_row[pixelm1+rgb32_pixel_b] == prev_row[pixel+rgb32_pixel_b])
+ {
+ if (run_index != i && i > 2 && (cur_row[pixelm1+rgb32_pixel_r] == cur_row[pixelm2+rgb32_pixel_r] && cur_row[pixelm1+rgb32_pixel_g] == cur_row[pixelm2+rgb32_pixel_g] && cur_row[pixelm1+rgb32_pixel_b] == cur_row[pixelm2+rgb32_pixel_b]))
+ {
+ /* do run */
+ this.rgb_state.waitcnt = stopidx - i;
+ run_index = i;
+ run_end = i + this.decode_run(this.rgb_state);
+
+ for (; i < run_end; i++) {
+ var pixel = i * rgb32_pixel_size;
+ var pixelm1 = (i-1) * rgb32_pixel_size;
+ cur_row[pixel+rgb32_pixel_pad] = 0;
+ cur_row[pixel+rgb32_pixel_r] = cur_row[pixelm1+rgb32_pixel_r];
+ cur_row[pixel+rgb32_pixel_g] = cur_row[pixelm1+rgb32_pixel_g];
+ cur_row[pixel+rgb32_pixel_b] = cur_row[pixelm1+rgb32_pixel_b];
+ }
+
+ if (i == end) {
+ return;
+ }
+ else
+ {
+ stopidx = i + this.rgb_state.waitcnt;
+ rc = 1;
+ break;
+ }
+ }
+ }
+
+ cur_row[pixel+rgb32_pixel_pad] = 0;
+ c = 0;
+ do
+ {
+ a = golomb_decoding_8bpc(this.channels[c].buckets_ptrs[this.channels[c].correlate_row.row[i-1]].bestcode, this.io_word);
+ this.channels[c].correlate_row.row[i] = a.rc;
+ cur_row[pixel+(2-c)] = (family_8bpc.xlatL2U[a.rc] + ((cur_row[pixelm1+(2-c)] + prev_row[pixel+(2-c)]) >> 1)) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ } while (++c < n_channels);
+ }
+
+ if (!rc)
+ {
+ this.rgb_state.waitcnt = stopidx - end;
+ return;
+ }
+ }
+}
+
+QuicEncoder.prototype.decode_run = function(state)
+{
+ var runlen = 0;
+
+ do {
+ var hits;
+ var x = (~(this.io_word >>> 24)>>>0)&0xff;
+ var temp = zeroLUT[x];
+
+ for (hits = 1; hits <= temp; hits++) {
+ runlen += state.melcorder;
+
+ if (state.melcstate < 32) {
+ state.melclen = J[++state.melcstate];
+ state.melcorder = (1 << state.melclen);
+ }
+ }
+ if (temp != 8) {
+ this.decode_eatbits(temp + 1);
+
+ break;
+ }
+ this.decode_eatbits(8);
+ } while (true);
+
+ if (state.melclen) {
+ runlen += this.io_word >>> (32 - state.melclen);
+ this.decode_eatbits(state.melclen);
+ }
+
+ if (state.melcstate) {
+ state.melclen = J[--state.melcstate];
+ state.melcorder = (1 << state.melclen);
+ }
+
+ return runlen;
+}
+
+QuicEncoder.prototype.quic_rgb32_uncompress_row = function (prev_row, cur_row)
+{
+ var bpc = 8;
+ var bpc_mask = 0xff;
+ var pos = 0;
+ var width = this.width;
+
+ while ((wmimax > this.rgb_state.wmidx) && (this.rgb_state.wmileft <= width)) {
+ if (this.rgb_state.wmileft) {
+ this.quic_rgb32_uncompress_row_seg(prev_row, cur_row, pos,
+ pos + this.rgb_state.wmileft, bpc, bpc_mask);
+ pos += this.rgb_state.wmileft;
+ width -= this.rgb_state.wmileft;
+ }
+
+ this.rgb_state.wmidx++;
+ this.rgb_state.set_wm_trigger();
+ this.rgb_state.wmileft = wminext;
+ }
+
+ if (width) {
+ this.quic_rgb32_uncompress_row_seg(prev_row, cur_row, pos,
+ pos + width, bpc, bpc_mask);
+ if (wmimax > this.rgb_state.wmidx) {
+ this.rgb_state.wmileft -= width;
+ }
+ }
+}
+
+QuicEncoder.prototype.quic_four_uncompress_row0_seg = function (channel, i,
+ correlate_row, cur_row, end, waitmask,
+ bpc, bpc_mask)
+{
+ var stopidx;
+ var a;
+
+ if (i == 0) {
+ a = golomb_decoding_8bpc(channel.buckets_ptrs[correlate_row.zero].bestcode, this.io_word);
+ correlate_row.row[0] = a.rc;
+ cur_row[rgb32_pixel_pad] = family_8bpc.xlatL2U[a.rc];
+ this.decode_eatbits(a.codewordlen);
+
+ if (channel.state.waitcnt) {
+ --channel.state.waitcnt;
+ } else {
+ channel.state.waitcnt = (channel.state.tabrand() & waitmask);
+ channel.buckets_ptrs[correlate_row.zero].update_model_8bpc(channel.state, correlate_row.row[0], bpc);
+ }
+ stopidx = ++i + channel.state.waitcnt;
+ } else {
+ stopidx = i + channel.state.waitcnt;
+ }
+
+ while (stopidx < end) {
+ var pbucket;
+
+ for (; i <= stopidx; i++) {
+ pbucket = channel.buckets_ptrs[correlate_row.row[i - 1]];
+
+ a = golomb_decoding_8bpc(pbucket.bestcode, this.io_word);
+ correlate_row.row[i] = a.rc;
+ cur_row[(i*rgb32_pixel_size)+rgb32_pixel_pad] = (family_8bpc.xlatL2U[a.rc] + cur_row[((i-1)*rgb32_pixel_size)+rgb32_pixel_pad]) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ }
+
+ pbucket.update_model_8bpc(channel.state, correlate_row.row[stopidx], bpc);
+
+ stopidx = i + (channel.state.tabrand() & waitmask);
+ }
+
+ for (; i < end; i++) {
+ a = golomb_decoding_8bpc(channel.buckets_ptrs[correlate_row.row[i-1]].bestcode, this.io_word);
+
+ correlate_row.row[i] = a.rc;
+ cur_row[(i*rgb32_pixel_size)+rgb32_pixel_pad] = (family_8bpc.xlatL2U[a.rc] + cur_row[((i-1)*rgb32_pixel_size)+rgb32_pixel_pad]) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ }
+ channel.state.waitcnt = stopidx - end;
+}
+
+QuicEncoder.prototype.quic_four_uncompress_row0 = function(channel, cur_row)
+{
+ var bpc = 8;
+ var bpc_mask = 0xff;
+ var correlate_row = channel.correlate_row;
+ var pos = 0;
+ var width = this.width;
+
+ while ((wmimax > channel.state.wmidx) && (channel.state.wmileft <= width)) {
+ if (channel.state.wmileft) {
+ this.quic_four_uncompress_row0_seg(channel, pos, correlate_row, cur_row,
+ pos + channel.state.wmileft, bppmask[channel.state.wmidx],
+ bpc, bpc_mask);
+ pos += channel.state.wmileft;
+ width -= channel.state.wmileft;
+ }
+
+ channel.state.wmidx++;
+ channel.state.set_wm_trigger();
+ channel.state.wmileft = wminext;
+ }
+
+ if (width) {
+ this.quic_four_uncompress_row0_seg(channel, pos, correlate_row, cur_row, pos + width,
+ bppmask[channel.state.wmidx], bpc, bpc_mask);
+ if (wmimax > channel.state.wmidx) {
+ channel.state.wmileft -= width;
+ }
+ }
+}
+
+QuicEncoder.prototype.quic_four_uncompress_row_seg = function (channel,
+ correlate_row, prev_row, cur_row, i,
+ end, bpc, bpc_mask)
+{
+ var waitmask = bppmask[channel.state.wmidx];
+ var stopidx;
+
+ var run_index = 0;
+ var run_end;
+
+ var a;
+
+ if (i == 0) {
+ a = golomb_decoding_8bpc(channel.buckets_ptrs[correlate_row.zero].bestcode, this.io_word);
+
+ correlate_row.row[0] = a.rc
+ cur_row[rgb32_pixel_pad] = (family_8bpc.xlatL2U[a.rc] + prev_row[rgb32_pixel_pad]) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+
+ if (channel.state.waitcnt) {
+ --channel.state.waitcnt;
+ } else {
+ channel.state.waitcnt = (channel.state.tabrand() & waitmask);
+ channel.buckets_ptrs[correlate_row.zero].update_model_8bpc(channel.state, correlate_row.row[0], bpc);
+ }
+ stopidx = ++i + channel.state.waitcnt;
+ } else {
+ stopidx = i + channel.state.waitcnt;
+ }
+ for (;;) {
+ var rc = 0;
+ while (stopidx < end && !rc) {
+ var pbucket;
+ for (; i <= stopidx && !rc; i++) {
+ var pixel = i * rgb32_pixel_size;
+ var pixelm1 = (i-1) * rgb32_pixel_size;
+ var pixelm2 = (i-2) * rgb32_pixel_size;
+
+ if (prev_row[pixelm1+rgb32_pixel_pad] == prev_row[pixel+rgb32_pixel_pad])
+ {
+ if (run_index != i && i > 2 && cur_row[pixelm1+rgb32_pixel_pad] == cur_row[pixelm2+rgb32_pixel_pad])
+ {
+ /* do run */
+ channel.state.waitcnt = stopidx - i;
+ run_index = i;
+
+ run_end = i + this.decode_run(channel.state);
+
+ for (; i < run_end; i++) {
+ var pixel = i * rgb32_pixel_size;
+ var pixelm1 = (i-1) * rgb32_pixel_size;
+ cur_row[pixel+rgb32_pixel_pad] = cur_row[pixelm1+rgb32_pixel_pad];
+ }
+
+ if (i == end) {
+ return;
+ }
+ else
+ {
+ stopidx = i + channel.state.waitcnt;
+ rc = 1;
+ break;
+ }
+ }
+ }
+
+ pbucket = channel.buckets_ptrs[correlate_row.row[i - 1]];
+ a = golomb_decoding_8bpc(pbucket.bestcode, this.io_word);
+ correlate_row.row[i] = a.rc
+ cur_row[pixel+rgb32_pixel_pad] = (family_8bpc.xlatL2U[a.rc] + ((cur_row[pixelm1+rgb32_pixel_pad] + prev_row[pixel+rgb32_pixel_pad]) >> 1)) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ }
+ if (rc)
+ break;
+
+ pbucket.update_model_8bpc(channel.state, correlate_row.row[stopidx], bpc);
+
+ stopidx = i + (channel.state.tabrand() & waitmask);
+ }
+
+ for (; i < end && !rc; i++) {
+ var pixel = i * rgb32_pixel_size;
+ var pixelm1 = (i-1) * rgb32_pixel_size;
+ var pixelm2 = (i-2) * rgb32_pixel_size;
+ if (prev_row[pixelm1+rgb32_pixel_pad] == prev_row[pixel+rgb32_pixel_pad])
+ {
+ if (run_index != i && i > 2 && cur_row[pixelm1+rgb32_pixel_pad] == cur_row[pixelm2+rgb32_pixel_pad])
+ {
+ /* do run */
+ channel.state.waitcnt = stopidx - i;
+ run_index = i;
+
+ run_end = i + this.decode_run(channel.state);
+
+ for (; i < run_end; i++) {
+ var pixel = i * rgb32_pixel_size;
+ var pixelm1 = (i-1) * rgb32_pixel_size;
+ cur_row[pixel+rgb32_pixel_pad] = cur_row[pixelm1+rgb32_pixel_pad];
+ }
+
+ if (i == end) {
+ return;
+ }
+ else
+ {
+ stopidx = i + channel.state.waitcnt;
+ rc = 1;
+ break;
+ }
+ }
+ }
+
+ a = golomb_decoding_8bpc(channel.buckets_ptrs[correlate_row.row[i-1]].bestcode, this.io_word);
+ correlate_row.row[i] = a.rc;
+ cur_row[pixel+rgb32_pixel_pad] = (family_8bpc.xlatL2U[a.rc] + ((cur_row[pixelm1+rgb32_pixel_pad] + prev_row[pixel+rgb32_pixel_pad]) >> 1)) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ }
+
+ if (!rc)
+ {
+ channel.state.waitcnt = stopidx - end;
+ return;
+ }
+ }
+}
+
+QuicEncoder.prototype.quic_four_uncompress_row = function(channel, prev_row,
+ cur_row)
+{
+ var bpc = 8;
+ var bpc_mask = 0xff;
+ var correlate_row = channel.correlate_row;
+ var pos = 0;
+ var width = this.width;
+
+ while ((wmimax > channel.state.wmidx) && (channel.state.wmileft <= width)) {
+ if (channel.state.wmileft) {
+ this.quic_four_uncompress_row_seg(channel, correlate_row, prev_row, cur_row, pos,
+ pos + channel.state.wmileft, bpc, bpc_mask);
+ pos += channel.state.wmileft;
+ width -= channel.state.wmileft;
+ }
+
+ channel.state.wmidx++;
+ channel.state.set_wm_trigger();
+ channel.state.wmileft = wminext;
+ }
+
+ if (width) {
+ this.quic_four_uncompress_row_seg(channel, correlate_row, prev_row, cur_row, pos,
+ pos + width, bpc, bpc_mask);
+ if (wmimax > channel.state.wmidx) {
+ channel.state.wmileft -= width;
+ }
+ }
+}
+
+/* We need to be generating rgb32 or rgba */
+QuicEncoder.prototype.quic_decode = function(buf, stride)
+{
+ var row;
+
+ switch (this.type)
+ {
+ case QUIC_IMAGE_TYPE_RGB32:
+ case QUIC_IMAGE_TYPE_RGB24:
+ this.channels[0].correlate_row.zero = 0;
+ this.channels[1].correlate_row.zero = 0;
+ this.channels[2].correlate_row.zero = 0;
+ this.quic_rgb32_uncompress_row0(buf);
+
+ this.rows_completed++;
+ for (row = 1; row < this.height; row++)
+ {
+ var prev = buf;
+ buf = prev.subarray(stride);
+ this.channels[0].correlate_row.zero = this.channels[0].correlate_row.row[0];
+ this.channels[1].correlate_row.zero = this.channels[1].correlate_row.row[0];
+ this.channels[2].correlate_row.zero = this.channels[2].correlate_row.row[0];
+ this.quic_rgb32_uncompress_row(prev, buf);
+ this.rows_completed++;
+ };
+ break;
+ case QUIC_IMAGE_TYPE_RGB16:
+ console.log("quic: unsupported output format\n");
+ return false;
+ break;
+ case QUIC_IMAGE_TYPE_RGBA:
+ this.channels[0].correlate_row.zero = 0;
+ this.channels[1].correlate_row.zero = 0;
+ this.channels[2].correlate_row.zero = 0;
+ this.quic_rgb32_uncompress_row0(buf);
+
+ this.channels[3].correlate_row.zero = 0;
+ this.quic_four_uncompress_row0(this.channels[3], buf);
+
+ this.rows_completed++;
+ for (row = 1; row < this.height; row++) {
+ var prev = buf;
+ buf = prev.subarray(stride);
+
+ this.channels[0].correlate_row.zero = this.channels[0].correlate_row.row[0];
+ this.channels[1].correlate_row.zero = this.channels[1].correlate_row.row[0];
+ this.channels[2].correlate_row.zero = this.channels[2].correlate_row.row[0];
+ this.quic_rgb32_uncompress_row(prev, buf);
+
+ this.channels[3].correlate_row.zero = this.channels[3].correlate_row.row[0];
+ this.quic_four_uncompress_row(encoder.channels[3], prev, buf);
+ this.rows_completed++;
+ }
+ break;
+
+ case QUIC_IMAGE_TYPE_GRAY:
+ console.log("quic: unsupported output format\n");
+ return false;
+ break;
+
+ case QUIC_IMAGE_TYPE_INVALID:
+ default:
+ console.log("quic: bad image type\n");
+ return false;
+ }
+ return true;
+}
+
+QuicEncoder.prototype.simple_quic_decode = function(buf)
+{
+ var stride = 4; /* FIXME - proper stride calc please */
+ if (!this.quic_decode_begin(buf))
+ return undefined;
+ if (this.type != QUIC_IMAGE_TYPE_RGB32 && this.type != QUIC_IMAGE_TYPE_RGB24
+ && this.type != QUIC_IMAGE_TYPE_RGBA)
+ return undefined;
+ var out = new Uint8Array(this.width*this.height*4);
+ out[0] = 69;
+ if (this.quic_decode( out, (this.width * stride)))
+ return out;
+ return undefined;
+}
+
+function SpiceQuic()
+{
+}
+
+SpiceQuic.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ if (!encoder)
+ throw("quic: no quic encoder");
+ this.data_size = dv.getUint32(at, true);
+ at += 4;
+ var buf = new Uint8Array(mb.slice(at));
+ this.outptr = encoder.simple_quic_decode(buf);
+ if (this.outptr)
+ {
+ this.type = encoder.type;
+ this.width = encoder.width;
+ this.height = encoder.height;
+ }
+ at += buf.length;
+ return at;
+ },
+}
+
+function convert_spice_quic_to_web(context, spice_quic)
+{
+ var ret = context.createImageData(spice_quic.width, spice_quic.height);
+ var i;
+ for (i = 0; i < (ret.width * ret.height * 4); i+=4)
+ {
+ ret.data[i + 0] = spice_quic.outptr[i + 2];
+ ret.data[i + 1] = spice_quic.outptr[i + 1];
+ ret.data[i + 2] = spice_quic.outptr[i + 0];
+ if (spice_quic.type !== QUIC_IMAGE_TYPE_RGBA)
+ ret.data[i + 3] = 255;
+ else
+ ret.data[i + 3] = 255 - spice_quic.outptr[i + 3];
+ }
+ return ret;
+}
+
+/* Module initialization */
+if (need_init)
+{
+ need_init = false;
+
+ family_init(family_8bpc, 8, DEFmaxclen);
+ family_init(family_5bpc, 5, DEFmaxclen);
+ /* init_zeroLUT */
+ var i, j, k, l;
+
+ j = k = 1;
+ l = 8;
+ for (i = 0; i < 256; ++i) {
+ zeroLUT[i] = l;
+ --k;
+ if (k == 0) {
+ k = j;
+ --l;
+ j *= 2;
+ }
+ }
+
+ encoder = new QuicEncoder;
+
+ if (!encoder)
+ throw("quic: failed to create encoder");
+}
diff --git a/ui/js/spice/rng.js b/ui/js/spice/rng.js
new file mode 100644
index 0000000..efbf382
--- /dev/null
+++ b/ui/js/spice/rng.js
@@ -0,0 +1,102 @@
+// Downloaded from http://www-cs-students.stanford.edu/~tjw/jsbn/ by Jeremy White on 6/1/2012
+
+/*
+ * Copyright (c) 2003-2005 Tom Wu
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
+ * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * In addition, the following condition applies:
+ *
+ * All redistributions must retain an intact copy of this copyright notice
+ * and disclaimer.
+ */
+
+
+// Random number generator - requires a PRNG backend, e.g. prng4.js
+
+// For best results, put code like
+// <body onClick='rng_seed_time();' onKeyPress='rng_seed_time();'>
+// in your main HTML document.
+
+var rng_state;
+var rng_pool;
+var rng_pptr;
+
+// Mix in a 32-bit integer into the pool
+function rng_seed_int(x) {
+ rng_pool[rng_pptr++] ^= x & 255;
+ rng_pool[rng_pptr++] ^= (x >> 8) & 255;
+ rng_pool[rng_pptr++] ^= (x >> 16) & 255;
+ rng_pool[rng_pptr++] ^= (x >> 24) & 255;
+ if(rng_pptr >= rng_psize) rng_pptr -= rng_psize;
+}
+
+// Mix in the current time (w/milliseconds) into the pool
+function rng_seed_time() {
+ rng_seed_int(new Date().getTime());
+}
+
+// Initialize the pool with junk if needed.
+if(rng_pool == null) {
+ rng_pool = new Array();
+ rng_pptr = 0;
+ var t;
+ if(navigator.appName == "Netscape" && navigator.appVersion < "5" && window.crypto) {
+ // Extract entropy (256 bits) from NS4 RNG if available
+ var z = window.crypto.random(32);
+ for(t = 0; t < z.length; ++t)
+ rng_pool[rng_pptr++] = z.charCodeAt(t) & 255;
+ }
+ while(rng_pptr < rng_psize) { // extract some randomness from Math.random()
+ t = Math.floor(65536 * Math.random());
+ rng_pool[rng_pptr++] = t >>> 8;
+ rng_pool[rng_pptr++] = t & 255;
+ }
+ rng_pptr = 0;
+ rng_seed_time();
+ //rng_seed_int(window.screenX);
+ //rng_seed_int(window.screenY);
+}
+
+function rng_get_byte() {
+ if(rng_state == null) {
+ rng_seed_time();
+ rng_state = prng_newstate();
+ rng_state.init(rng_pool);
+ for(rng_pptr = 0; rng_pptr < rng_pool.length; ++rng_pptr)
+ rng_pool[rng_pptr] = 0;
+ rng_pptr = 0;
+ //rng_pool = null;
+ }
+ // TODO: allow reseeding after first request
+ return rng_state.next();
+}
+
+function rng_get_bytes(ba) {
+ var i;
+ for(i = 0; i < ba.length; ++i) ba[i] = rng_get_byte();
+}
+
+function SecureRandom() {}
+
+SecureRandom.prototype.nextBytes = rng_get_bytes;
diff --git a/ui/js/spice/rsa.js b/ui/js/spice/rsa.js
new file mode 100644
index 0000000..ea0e45b
--- /dev/null
+++ b/ui/js/spice/rsa.js
@@ -0,0 +1,146 @@
+// Downloaded from http://www-cs-students.stanford.edu/~tjw/jsbn/ by Jeremy White on 6/1/2012
+
+/*
+ * Copyright (c) 2003-2005 Tom Wu
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
+ * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * In addition, the following condition applies:
+ *
+ * All redistributions must retain an intact copy of this copyright notice
+ * and disclaimer.
+ */
+
+
+// Depends on jsbn.js and rng.js
+
+// Version 1.1: support utf-8 encoding in pkcs1pad2
+
+// convert a (hex) string to a bignum object
+function parseBigInt(str,r) {
+ return new BigInteger(str,r);
+}
+
+function linebrk(s,n) {
+ var ret = "";
+ var i = 0;
+ while(i + n < s.length) {
+ ret += s.substring(i,i+n) + "\n";
+ i += n;
+ }
+ return ret + s.substring(i,s.length);
+}
+
+function byte2Hex(b) {
+ if(b < 0x10)
+ return "0" + b.toString(16);
+ else
+ return b.toString(16);
+}
+
+// PKCS#1 (type 2, random) pad input string s to n bytes, and return a bigint
+function pkcs1pad2(s,n) {
+ if(n < s.length + 11) { // TODO: fix for utf-8
+ alert("Message too long for RSA");
+ return null;
+ }
+ var ba = new Array();
+ var i = s.length - 1;
+ while(i >= 0 && n > 0) {
+ var c = s.charCodeAt(i--);
+ if(c < 128) { // encode using utf-8
+ ba[--n] = c;
+ }
+ else if((c > 127) && (c < 2048)) {
+ ba[--n] = (c & 63) | 128;
+ ba[--n] = (c >> 6) | 192;
+ }
+ else {
+ ba[--n] = (c & 63) | 128;
+ ba[--n] = ((c >> 6) & 63) | 128;
+ ba[--n] = (c >> 12) | 224;
+ }
+ }
+ ba[--n] = 0;
+ var rng = new SecureRandom();
+ var x = new Array();
+ while(n > 2) { // random non-zero pad
+ x[0] = 0;
+ while(x[0] == 0) rng.nextBytes(x);
+ ba[--n] = x[0];
+ }
+ ba[--n] = 2;
+ ba[--n] = 0;
+ return new BigInteger(ba);
+}
+
+// "empty" RSA key constructor
+function RSAKey() {
+ this.n = null;
+ this.e = 0;
+ this.d = null;
+ this.p = null;
+ this.q = null;
+ this.dmp1 = null;
+ this.dmq1 = null;
+ this.coeff = null;
+}
+
+// Set the public key fields N and e from hex strings
+function RSASetPublic(N,E) {
+ if(N != null && E != null && N.length > 0 && E.length > 0) {
+ this.n = parseBigInt(N,16);
+ this.e = parseInt(E,16);
+ }
+ else
+ alert("Invalid RSA public key");
+}
+
+// Perform raw public operation on "x": return x^e (mod n)
+function RSADoPublic(x) {
+ return x.modPowInt(this.e, this.n);
+}
+
+// Return the PKCS#1 RSA encryption of "text" as an even-length hex string
+function RSAEncrypt(text) {
+ var m = pkcs1pad2(text,(this.n.bitLength()+7)>>3);
+ if(m == null) return null;
+ var c = this.doPublic(m);
+ if(c == null) return null;
+ var h = c.toString(16);
+ if((h.length & 1) == 0) return h; else return "0" + h;
+}
+
+// Return the PKCS#1 RSA encryption of "text" as a Base64-encoded string
+//function RSAEncryptB64(text) {
+// var h = this.encrypt(text);
+// if(h) return hex2b64(h); else return null;
+//}
+
+// protected
+RSAKey.prototype.doPublic = RSADoPublic;
+
+// public
+RSAKey.prototype.setPublic = RSASetPublic;
+RSAKey.prototype.encrypt = RSAEncrypt;
+//RSAKey.prototype.encrypt_b64 = RSAEncryptB64;
diff --git a/ui/js/spice/sha1.js b/ui/js/spice/sha1.js
new file mode 100644
index 0000000..363c83d
--- /dev/null
+++ b/ui/js/spice/sha1.js
@@ -0,0 +1,346 @@
+/*
+ * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
+ * in FIPS 180-1
+ * Version 2.2 Copyright Paul Johnston 2000 - 2009.
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for details.
+ */
+
+ /* Downloaded 6/1/2012 from the above address by Jeremy White.
+ License reproduce here for completeness:
+
+Copyright (c) 1998 - 2009, Paul Johnston & Contributors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ */
+
+/*
+ * Configurable variables. You may need to tweak these to be compatible with
+ * the server-side, but the defaults work in most cases.
+ */
+var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
+var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
+
+/*
+ * These are the functions you'll usually want to call
+ * They take string arguments and return either hex or base-64 encoded strings
+ */
+function hex_sha1(s) { return rstr2hex(rstr_sha1(str2rstr_utf8(s))); }
+function b64_sha1(s) { return rstr2b64(rstr_sha1(str2rstr_utf8(s))); }
+function any_sha1(s, e) { return rstr2any(rstr_sha1(str2rstr_utf8(s)), e); }
+function hex_hmac_sha1(k, d)
+ { return rstr2hex(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d))); }
+function b64_hmac_sha1(k, d)
+ { return rstr2b64(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d))); }
+function any_hmac_sha1(k, d, e)
+ { return rstr2any(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d)), e); }
+
+/*
+ * Perform a simple self-test to see if the VM is working
+ */
+function sha1_vm_test()
+{
+ return hex_sha1("abc").toLowerCase() == "a9993e364706816aba3e25717850c26c9cd0d89d";
+}
+
+/*
+ * Calculate the SHA1 of a raw string
+ */
+function rstr_sha1(s)
+{
+ return binb2rstr(binb_sha1(rstr2binb(s), s.length * 8));
+}
+
+/*
+ * Calculate the HMAC-SHA1 of a key and some data (raw strings)
+ */
+function rstr_hmac_sha1(key, data)
+{
+ var bkey = rstr2binb(key);
+ if(bkey.length > 16) bkey = binb_sha1(bkey, key.length * 8);
+
+ var ipad = Array(16), opad = Array(16);
+ for(var i = 0; i < 16; i++)
+ {
+ ipad[i] = bkey[i] ^ 0x36363636;
+ opad[i] = bkey[i] ^ 0x5C5C5C5C;
+ }
+
+ var hash = binb_sha1(ipad.concat(rstr2binb(data)), 512 + data.length * 8);
+ return binb2rstr(binb_sha1(opad.concat(hash), 512 + 160));
+}
+
+/*
+ * Convert a raw string to a hex string
+ */
+function rstr2hex(input)
+{
+ try { hexcase } catch(e) { hexcase=0; }
+ var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+ var output = "";
+ var x;
+ for(var i = 0; i < input.length; i++)
+ {
+ x = input.charCodeAt(i);
+ output += hex_tab.charAt((x >>> 4) & 0x0F)
+ + hex_tab.charAt( x & 0x0F);
+ }
+ return output;
+}
+
+/*
+ * Convert a raw string to a base-64 string
+ */
+function rstr2b64(input)
+{
+ try { b64pad } catch(e) { b64pad=''; }
+ var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ var output = "";
+ var len = input.length;
+ for(var i = 0; i < len; i += 3)
+ {
+ var triplet = (input.charCodeAt(i) << 16)
+ | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)
+ | (i + 2 < len ? input.charCodeAt(i+2) : 0);
+ for(var j = 0; j < 4; j++)
+ {
+ if(i * 8 + j * 6 > input.length * 8) output += b64pad;
+ else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);
+ }
+ }
+ return output;
+}
+
+/*
+ * Convert a raw string to an arbitrary string encoding
+ */
+function rstr2any(input, encoding)
+{
+ var divisor = encoding.length;
+ var remainders = Array();
+ var i, q, x, quotient;
+
+ /* Convert to an array of 16-bit big-endian values, forming the dividend */
+ var dividend = Array(Math.ceil(input.length / 2));
+ for(i = 0; i < dividend.length; i++)
+ {
+ dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
+ }
+
+ /*
+ * Repeatedly perform a long division. The binary array forms the dividend,
+ * the length of the encoding is the divisor. Once computed, the quotient
+ * forms the dividend for the next step. We stop when the dividend is zero.
+ * All remainders are stored for later use.
+ */
+ while(dividend.length > 0)
+ {
+ quotient = Array();
+ x = 0;
+ for(i = 0; i < dividend.length; i++)
+ {
+ x = (x << 16) + dividend[i];
+ q = Math.floor(x / divisor);
+ x -= q * divisor;
+ if(quotient.length > 0 || q > 0)
+ quotient[quotient.length] = q;
+ }
+ remainders[remainders.length] = x;
+ dividend = quotient;
+ }
+
+ /* Convert the remainders to the output string */
+ var output = "";
+ for(i = remainders.length - 1; i >= 0; i--)
+ output += encoding.charAt(remainders[i]);
+
+ /* Append leading zero equivalents */
+ var full_length = Math.ceil(input.length * 8 /
+ (Math.log(encoding.length) / Math.log(2)))
+ for(i = output.length; i < full_length; i++)
+ output = encoding[0] + output;
+
+ return output;
+}
+
+/*
+ * Encode a string as utf-8.
+ * For efficiency, this assumes the input is valid utf-16.
+ */
+function str2rstr_utf8(input)
+{
+ var output = "";
+ var i = -1;
+ var x, y;
+
+ while(++i < input.length)
+ {
+ /* Decode utf-16 surrogate pairs */
+ x = input.charCodeAt(i);
+ y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
+ if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)
+ {
+ x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
+ i++;
+ }
+
+ /* Encode output as utf-8 */
+ if(x <= 0x7F)
+ output += String.fromCharCode(x);
+ else if(x <= 0x7FF)
+ output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),
+ 0x80 | ( x & 0x3F));
+ else if(x <= 0xFFFF)
+ output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),
+ 0x80 | ((x >>> 6 ) & 0x3F),
+ 0x80 | ( x & 0x3F));
+ else if(x <= 0x1FFFFF)
+ output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),
+ 0x80 | ((x >>> 12) & 0x3F),
+ 0x80 | ((x >>> 6 ) & 0x3F),
+ 0x80 | ( x & 0x3F));
+ }
+ return output;
+}
+
+/*
+ * Encode a string as utf-16
+ */
+function str2rstr_utf16le(input)
+{
+ var output = "";
+ for(var i = 0; i < input.length; i++)
+ output += String.fromCharCode( input.charCodeAt(i) & 0xFF,
+ (input.charCodeAt(i) >>> 8) & 0xFF);
+ return output;
+}
+
+function str2rstr_utf16be(input)
+{
+ var output = "";
+ for(var i = 0; i < input.length; i++)
+ output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF,
+ input.charCodeAt(i) & 0xFF);
+ return output;
+}
+
+/*
+ * Convert a raw string to an array of big-endian words
+ * Characters >255 have their high-byte silently ignored.
+ */
+function rstr2binb(input)
+{
+ var output = Array(input.length >> 2);
+ for(var i = 0; i < output.length; i++)
+ output[i] = 0;
+ for(var i = 0; i < input.length * 8; i += 8)
+ output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (24 - i % 32);
+ return output;
+}
+
+/*
+ * Convert an array of big-endian words to a string
+ */
+function binb2rstr(input)
+{
+ var output = "";
+ for(var i = 0; i < input.length * 32; i += 8)
+ output += String.fromCharCode((input[i>>5] >>> (24 - i % 32)) & 0xFF);
+ return output;
+}
+
+/*
+ * Calculate the SHA-1 of an array of big-endian words, and a bit length
+ */
+function binb_sha1(x, len)
+{
+ /* append padding */
+ x[len >> 5] |= 0x80 << (24 - len % 32);
+ x[((len + 64 >> 9) << 4) + 15] = len;
+
+ var w = Array(80);
+ var a = 1732584193;
+ var b = -271733879;
+ var c = -1732584194;
+ var d = 271733878;
+ var e = -1009589776;
+
+ for(var i = 0; i < x.length; i += 16)
+ {
+ var olda = a;
+ var oldb = b;
+ var oldc = c;
+ var oldd = d;
+ var olde = e;
+
+ for(var j = 0; j < 80; j++)
+ {
+ if(j < 16) w[j] = x[i + j];
+ else w[j] = bit_rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
+ var t = safe_add(safe_add(bit_rol(a, 5), sha1_ft(j, b, c, d)),
+ safe_add(safe_add(e, w[j]), sha1_kt(j)));
+ e = d;
+ d = c;
+ c = bit_rol(b, 30);
+ b = a;
+ a = t;
+ }
+
+ a = safe_add(a, olda);
+ b = safe_add(b, oldb);
+ c = safe_add(c, oldc);
+ d = safe_add(d, oldd);
+ e = safe_add(e, olde);
+ }
+ return Array(a, b, c, d, e);
+
+}
+
+/*
+ * Perform the appropriate triplet combination function for the current
+ * iteration
+ */
+function sha1_ft(t, b, c, d)
+{
+ if(t < 20) return (b & c) | ((~b) & d);
+ if(t < 40) return b ^ c ^ d;
+ if(t < 60) return (b & c) | (b & d) | (c & d);
+ return b ^ c ^ d;
+}
+
+/*
+ * Determine the appropriate additive constant for the current iteration
+ */
+function sha1_kt(t)
+{
+ return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 :
+ (t < 60) ? -1894007588 : -899497514;
+}
+
+/*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+function safe_add(x, y)
+{
+ var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+ var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+ return (msw << 16) | (lsw & 0xFFFF);
+}
+
+/*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+function bit_rol(num, cnt)
+{
+ return (num << cnt) | (num >>> (32 - cnt));
+}
diff --git a/ui/js/spice/spiceconn.js b/ui/js/spice/spiceconn.js
new file mode 100644
index 0000000..7d26913
--- /dev/null
+++ b/ui/js/spice/spiceconn.js
@@ -0,0 +1,447 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*----------------------------------------------------------------------------
+** SpiceConn
+** This is the base Javascript class for establishing and
+** managing a connection to a Spice Server.
+** It is used to provide core functionality to the Spice main,
+** display, inputs, and cursor channels. See main.js for
+** usage.
+**--------------------------------------------------------------------------*/
+function SpiceConn(o)
+{
+ if (o === undefined || o.uri === undefined || ! o.uri)
+ throw new Error("You must specify a uri");
+
+ this.ws = new WebSocket(o.uri, 'binary');
+
+ if (! this.ws.binaryType)
+ throw new Error("WebSocket doesn't support binaryType. Try a different browser.");
+
+ this.connection_id = o.connection_id !== undefined ? o.connection_id : 0;
+ this.type = o.type !== undefined ? o.type : SPICE_CHANNEL_MAIN;
+ this.chan_id = o.chan_id !== undefined ? o.chan_id : 0;
+ if (o.parent !== undefined)
+ {
+ this.parent = o.parent;
+ this.message_id = o.parent.message_id;
+ this.password = o.parent.password;
+ }
+ if (o.screen_id !== undefined)
+ this.screen_id = o.screen_id;
+ if (o.dump_id !== undefined)
+ this.dump_id = o.dump_id;
+ if (o.message_id !== undefined)
+ this.message_id = o.message_id;
+ if (o.password !== undefined)
+ this.password = o.password;
+ if (o.onerror !== undefined)
+ this.onerror = o.onerror;
+
+ this.state = "connecting";
+ this.ws.parent = this;
+ this.wire_reader = new SpiceWireReader(this, this.process_inbound);
+ this.messages_sent = 0;
+ this.warnings = [];
+
+ this.ws.addEventListener('open', function(e) {
+ DEBUG > 0 && console.log(">> WebSockets.onopen");
+ DEBUG > 0 && console.log("id " + this.parent.connection_id +"; type " + this.parent.type);
+
+ /***********************************************************************
+ ** WHERE IT ALL REALLY BEGINS
+ ***********************************************************************/
+ this.parent.send_hdr();
+ this.parent.wire_reader.request(SpiceLinkHeader.prototype.buffer_size());
+ this.parent.state = "start";
+ });
+ this.ws.addEventListener('error', function(e) {
+ this.parent.log_err(">> WebSockets.onerror" + e.toString());
+ this.parent.report_error(e);
+ });
+ this.ws.addEventListener('close', function(e) {
+ DEBUG > 0 && console.log(">> WebSockets.onclose");
+ DEBUG > 0 && console.log("id " + this.parent.connection_id +"; type " + this.parent.type);
+ DEBUG > 0 && console.log(e);
+ if (this.parent.state != "closing" && this.parent.state != "error" && this.parent.onerror !== undefined)
+ {
+ var e;
+ if (this.parent.state == "connecting")
+ e = new Error("Connection refused.");
+ else if (this.parent.state == "start" || this.parent.state == "link")
+ e = new Error("Unexpected protocol mismatch.");
+ else if (this.parent.state == "ticket")
+ e = new Error("Bad password.");
+ else
+ e = new Error("Unexpected close while " + this.parent.state);
+
+ this.parent.onerror(e);
+ this.parent.log_err(e.toString());
+ }
+ });
+
+ if (this.ws.readyState == 2 || this.ws.readyState == 3)
+ throw new Error("Unable to connect to " + o.uri);
+
+ this.timeout = window.setTimeout(spiceconn_timeout, SPICE_CONNECT_TIMEOUT, this);
+}
+
+SpiceConn.prototype =
+{
+ send_hdr : function ()
+ {
+ var hdr = new SpiceLinkHeader;
+ var msg = new SpiceLinkMess;
+
+ msg.connection_id = this.connection_id;
+ msg.channel_type = this.type;
+ // FIXME - we're not setting a channel_id...
+ msg.common_caps.push(
+ (1 << SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION) |
+ (1 << SPICE_COMMON_CAP_MINI_HEADER)
+ );
+
+ hdr.size = msg.buffer_size();
+
+ var mb = new ArrayBuffer(hdr.buffer_size() + msg.buffer_size());
+ hdr.to_buffer(mb);
+ msg.to_buffer(mb, hdr.buffer_size());
+
+ DEBUG > 1 && console.log("Sending header:");
+ DEBUG > 2 && hexdump_buffer(mb);
+ this.ws.send(mb);
+ },
+
+ send_ticket: function(ticket)
+ {
+ var hdr = new SpiceLinkAuthTicket();
+ hdr.auth_mechanism = SPICE_COMMON_CAP_AUTH_SPICE;
+ // FIXME - we need to implement RSA to make this work right
+ hdr.encrypted_data = ticket;
+ var mb = new ArrayBuffer(hdr.buffer_size());
+
+ hdr.to_buffer(mb);
+ DEBUG > 1 && console.log("Sending ticket:");
+ DEBUG > 2 && hexdump_buffer(mb);
+ this.ws.send(mb);
+ },
+
+ send_msg: function(msg)
+ {
+ var mb = new ArrayBuffer(msg.buffer_size());
+ msg.to_buffer(mb);
+ this.messages_sent++;
+ DEBUG > 0 && console.log(">> hdr " + this.channel_type() + " type " + msg.type + " size " + mb.byteLength);
+ DEBUG > 2 && hexdump_buffer(mb);
+ this.ws.send(mb);
+ },
+
+ process_inbound: function(mb, saved_header)
+ {
+ DEBUG > 2 && console.log(this.type + ": processing message of size " + mb.byteLength + "; state is " + this.state);
+ if (this.state == "ready")
+ {
+ if (saved_header == undefined)
+ {
+ var msg = new SpiceMiniData(mb);
+
+ if (msg.type > 500)
+ {
+ alert("Something has gone very wrong; we think we have message of type " + msg.type);
+ debugger;
+ }
+
+ if (msg.size == 0)
+ {
+ this.process_message(msg);
+ this.wire_reader.request(SpiceMiniData.prototype.buffer_size());
+ }
+ else
+ {
+ this.wire_reader.request(msg.size);
+ this.wire_reader.save_header(msg);
+ }
+ }
+ else
+ {
+ saved_header.data = mb;
+ this.process_message(saved_header);
+ this.wire_reader.request(SpiceMiniData.prototype.buffer_size());
+ this.wire_reader.save_header(undefined);
+ }
+ }
+
+ else if (this.state == "start")
+ {
+ this.reply_hdr = new SpiceLinkHeader(mb);
+ if (this.reply_hdr.magic != SPICE_MAGIC)
+ {
+ this.state = "error";
+ var e = new Error('Error: magic mismatch: ' + this.reply_hdr.magic);
+ this.report_error(e);
+ }
+ else
+ {
+ // FIXME - Determine major/minor version requirements
+ this.wire_reader.request(this.reply_hdr.size);
+ this.state = "link";
+ }
+ }
+
+ else if (this.state == "link")
+ {
+ this.reply_link = new SpiceLinkReply(mb);
+ // FIXME - Screen the caps - require minihdr at least, right?
+ if (this.reply_link.error)
+ {
+ this.state = "error";
+ var e = new Error('Error: reply link error ' + this.reply_link.error);
+ this.report_error(e);
+ }
+ else
+ {
+ this.send_ticket(rsa_encrypt(this.reply_link.pub_key, this.password + String.fromCharCode(0)));
+ this.state = "ticket";
+ this.wire_reader.request(SpiceLinkAuthReply.prototype.buffer_size());
+ }
+ }
+
+ else if (this.state == "ticket")
+ {
+ this.auth_reply = new SpiceLinkAuthReply(mb);
+ if (this.auth_reply.auth_code == SPICE_LINK_ERR_OK)
+ {
+ DEBUG > 0 && console.log(this.type + ': Connected');
+
+ if (this.type == SPICE_CHANNEL_DISPLAY)
+ {
+ // FIXME - pixmap and glz dictionary config info?
+ var dinit = new SpiceMsgcDisplayInit();
+ var reply = new SpiceMiniData();
+ reply.build_msg(SPICE_MSGC_DISPLAY_INIT, dinit);
+ DEBUG > 0 && console.log("Request display init");
+ this.send_msg(reply);
+ }
+ this.state = "ready";
+ this.wire_reader.request(SpiceMiniData.prototype.buffer_size());
+ if (this.timeout)
+ {
+ window.clearTimeout(this.timeout);
+ delete this.timeout;
+ }
+ }
+ else
+ {
+ this.state = "error";
+ if (this.auth_reply.auth_code == SPICE_LINK_ERR_PERMISSION_DENIED)
+ {
+ var e = new Error("Permission denied.");
+ }
+ else
+ {
+ var e = new Error("Unexpected link error " + this.auth_reply.auth_code);
+ }
+ this.report_error(e);
+ }
+ }
+ },
+
+ process_common_messages : function(msg)
+ {
+ if (msg.type == SPICE_MSG_SET_ACK)
+ {
+ var ack = new SpiceMsgSetAck(msg.data);
+ // FIXME - what to do with generation?
+ this.ack_window = ack.window;
+ DEBUG > 1 && console.log(this.type + ": set ack to " + ack.window);
+ this.msgs_until_ack = this.ack_window;
+ var ackack = new SpiceMsgcAckSync(ack);
+ var reply = new SpiceMiniData();
+ reply.build_msg(SPICE_MSGC_ACK_SYNC, ackack);
+ this.send_msg(reply);
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_PING)
+ {
+ DEBUG > 1 && console.log("ping!");
+ var pong = new SpiceMiniData;
+ pong.type = SPICE_MSGC_PONG;
+ if (msg.data)
+ {
+ pong.data = msg.data.slice(0, 12);
+ }
+ pong.size = pong.buffer_size();
+ this.send_msg(pong);
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_NOTIFY)
+ {
+ // FIXME - Visibility + what
+ var notify = new SpiceMsgNotify(msg.data);
+ if (notify.severity == SPICE_NOTIFY_SEVERITY_ERROR)
+ this.log_err(notify.message);
+ else if (notify.severity == SPICE_NOTIFY_SEVERITY_WARN )
+ this.log_warn(notify.message);
+ else
+ this.log_info(notify.message);
+ return true;
+ }
+
+ return false;
+
+ },
+
+ process_message: function(msg)
+ {
+ var rc;
+ DEBUG > 0 && console.log("<< hdr " + this.channel_type() + " type " + msg.type + " size " + (msg.data && msg.data.byteLength));
+ rc = this.process_common_messages(msg);
+ if (rc)
+ return rc;
+
+ if (this.process_channel_message)
+ rc = this.process_channel_message(msg);
+ else
+ {
+ this.log_err(this.type + ": No message handlers for this channel; message " + msg.type);
+ return false;
+ }
+
+ if (! rc)
+ this.log_warn(this.type + ": Unknown message type " + msg.type + "!");
+
+ if (this.msgs_until_ack !== undefined && this.ack_window)
+ {
+ this.msgs_until_ack--;
+ if (this.msgs_until_ack <= 0)
+ {
+ this.msgs_until_ack = this.ack_window;
+ var ack = new SpiceMiniData();
+ ack.type = SPICE_MSGC_ACK;
+ this.send_msg(ack);
+ DEBUG > 1 && console.log(this.type + ": sent ack");
+ }
+ }
+
+ return rc;
+ },
+
+ channel_type: function()
+ {
+ if (this.type == SPICE_CHANNEL_MAIN)
+ return "main";
+ else if (this.type == SPICE_CHANNEL_DISPLAY)
+ return "display";
+ else if (this.type == SPICE_CHANNEL_INPUTS)
+ return "inputs";
+ else if (this.type == SPICE_CHANNEL_CURSOR)
+ return "cursor";
+ return "unknown-" + this.type;
+
+ },
+
+ log_info: function()
+ {
+ var msg = Array.prototype.join.call(arguments, " ");
+ console.log(msg);
+ if (this.message_id)
+ {
+ var p = document.createElement("p");
+ p.appendChild(document.createTextNode(msg));
+ p.className += "spice-message-info";
+ document.getElementById(this.message_id).appendChild(p);
+ }
+ },
+
+ log_warn: function()
+ {
+ var msg = Array.prototype.join.call(arguments, " ");
+ console.log("WARNING: " + msg);
+ if (this.message_id)
+ {
+ var p = document.createElement("p");
+ p.appendChild(document.createTextNode(msg));
+ p.className += "spice-message-warning";
+ document.getElementById(this.message_id).appendChild(p);
+ }
+ },
+
+ log_err: function()
+ {
+ var msg = Array.prototype.join.call(arguments, " ");
+ console.log("ERROR: " + msg);
+ if (this.message_id)
+ {
+ var p = document.createElement("p");
+ p.appendChild(document.createTextNode(msg));
+ p.className += "spice-message-error";
+ document.getElementById(this.message_id).appendChild(p);
+ }
+ },
+
+ known_unimplemented: function(type, msg)
+ {
+ if ( (!this.warnings[type]) || DEBUG > 1)
+ {
+ var str = "";
+ if (DEBUG <= 1)
+ str = " [ further notices suppressed ]";
+ this.log_warn("Unimplemented function " + type + "(" + msg + ")" + str);
+ this.warnings[type] = true;
+ }
+ },
+
+ report_error: function(e)
+ {
+ this.log_err(e.toString());
+ if (this.onerror != undefined)
+ this.onerror(e);
+ else
+ throw(e);
+ },
+
+ cleanup: function()
+ {
+ if (this.timeout)
+ {
+ window.clearTimeout(this.timeout);
+ delete this.timeout;
+ }
+ if (this.ws)
+ {
+ this.ws.close();
+ this.ws = undefined;
+ }
+ },
+
+ handle_timeout: function()
+ {
+ var e = new Error("Connection timed out.");
+ this.report_error(e);
+ },
+}
+
+function spiceconn_timeout(sc)
+{
+ SpiceConn.prototype.handle_timeout.call(sc);
+}
diff --git a/ui/js/spice/spicedataview.js b/ui/js/spice/spicedataview.js
new file mode 100644
index 0000000..0699ed5
--- /dev/null
+++ b/ui/js/spice/spicedataview.js
@@ -0,0 +1,96 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*----------------------------------------------------------------------------
+** SpiceDataView
+** FIXME FIXME
+** This is used because Firefox does not have DataView yet.
+** We should use DataView if we have it, because it *has* to
+** be faster than this code
+**--------------------------------------------------------------------------*/
+function SpiceDataView(buffer, byteOffset, byteLength)
+{
+ if (byteOffset !== undefined)
+ {
+ if (byteLength !== undefined)
+ this.u8 = new Uint8Array(buffer, byteOffset, byteLength);
+ else
+ this.u8 = new Uint8Array(buffer, byteOffset);
+ }
+ else
+ this.u8 = new Uint8Array(buffer);
+};
+
+SpiceDataView.prototype = {
+ getUint8: function(byteOffset)
+ {
+ return this.u8[byteOffset];
+ },
+ getUint16: function(byteOffset, littleEndian)
+ {
+ var low = 1, high = 0;
+ if (littleEndian)
+ {
+ low = 0;
+ high = 1;
+ }
+
+ return (this.u8[byteOffset + high] << 8) | this.u8[byteOffset + low];
+ },
+ getUint32: function(byteOffset, littleEndian)
+ {
+ var low = 2, high = 0;
+ if (littleEndian)
+ {
+ low = 0;
+ high = 2;
+ }
+
+ return (this.getUint16(byteOffset + high, littleEndian) << 16) |
+ this.getUint16(byteOffset + low, littleEndian);
+ },
+ setUint8: function(byteOffset, b)
+ {
+ this.u8[byteOffset] = (b & 0xff);
+ },
+ setUint16: function(byteOffset, i, littleEndian)
+ {
+ var low = 1, high = 0;
+ if (littleEndian)
+ {
+ low = 0;
+ high = 1;
+ }
+ this.u8[byteOffset + high] = (i & 0xffff) >> 8;
+ this.u8[byteOffset + low] = (i & 0x00ff);
+ },
+ setUint32: function(byteOffset, w, littleEndian)
+ {
+ var low = 2, high = 0;
+ if (littleEndian)
+ {
+ low = 0;
+ high = 2;
+ }
+
+ this.setUint16(byteOffset + high, (w & 0xffffffff) >> 16, littleEndian);
+ this.setUint16(byteOffset + low, (w & 0x0000ffff), littleEndian);
+ },
+}
diff --git a/ui/js/spice/spicemsg.js b/ui/js/spice/spicemsg.js
new file mode 100644
index 0000000..dac4b30
--- /dev/null
+++ b/ui/js/spice/spicemsg.js
@@ -0,0 +1,883 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*----------------------------------------------------------------------------
+** Spice messages
+** This file contains classes for passing messages to and from
+** a spice server. This file should arguably be generated from
+** spice.proto, but it was instead put together by hand.
+**--------------------------------------------------------------------------*/
+function SpiceLinkHeader(a, at)
+{
+ this.magic = SPICE_MAGIC;
+ this.major_version = SPICE_VERSION_MAJOR;
+ this.minor_version = SPICE_VERSION_MINOR;
+ this.size = 0;
+ if (a !== undefined)
+ this.from_buffer(a, at);
+}
+
+SpiceLinkHeader.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.magic = "";
+ for (var i = 0; i < 4; i++)
+ this.magic += String.fromCharCode(dv.getUint8(at + i));
+ at += 4;
+
+ this.major_version = dv.getUint32(at, true); at += 4;
+ this.minor_version = dv.getUint32(at, true); at += 4;
+ this.size = dv.getUint32(at, true); at += 4;
+ },
+
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ for (var i = 0; i < 4; i++)
+ dv.setUint8(at + i, this.magic.charCodeAt(i));
+ at += 4;
+
+ dv.setUint32(at, this.major_version, true); at += 4;
+ dv.setUint32(at, this.minor_version, true); at += 4;
+ dv.setUint32(at, this.size, true); at += 4;
+ },
+ buffer_size: function()
+ {
+ return 16;
+ },
+}
+
+function SpiceLinkMess(a, at)
+{
+ this.connection_id = 0;
+ this.channel_type = 0;
+ this.channel_id = 0;
+ this.common_caps = [];
+ this.channel_caps = [];
+
+ if (a !== undefined)
+ this.from_buffer(a, at);
+}
+
+SpiceLinkMess.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var i;
+ var orig_at = at;
+ var dv = new SpiceDataView(a);
+ this.connection_id = dv.getUint32(at, true); at += 4;
+ this.channel_type = dv.getUint8(at, true); at++;
+ this.channel_id = dv.getUint8(at, true); at++;
+ var num_common_caps = dv.getUint32(at, true); at += 4;
+ var num_channel_caps = dv.getUint32(at, true); at += 4;
+ var caps_offset = dv.getUint32(at, true); at += 4;
+
+ at = orig_at + caps_offset;
+ this.common_caps = [];
+ for (i = 0; i < num_common_caps; i++)
+ {
+ this.common_caps.unshift(dv.getUint32(at, true)); at += 4;
+ }
+
+ this.channel_caps = [];
+ for (i = 0; i < num_channel_caps; i++)
+ {
+ this.channel_caps.unshift(dv.getUint32(at, true)); at += 4;
+ }
+ },
+
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var orig_at = at;
+ var i;
+ var dv = new SpiceDataView(a);
+ dv.setUint32(at, this.connection_id, true); at += 4;
+ dv.setUint8(at, this.channel_type, true); at++;
+ dv.setUint8(at, this.channel_id, true); at++;
+ dv.setUint32(at, this.common_caps.length, true); at += 4;
+ dv.setUint32(at, this.channel_caps.length, true); at += 4;
+ dv.setUint32(at, (at - orig_at) + 4, true); at += 4;
+
+ for (i = 0; i < this.common_caps.length; i++)
+ {
+ dv.setUint32(at, this.common_caps[i], true); at += 4;
+ }
+
+ for (i = 0; i < this.channel_caps.length; i++)
+ {
+ dv.setUint32(at, this.channel_caps[i], true); at += 4;
+ }
+ },
+ buffer_size: function()
+ {
+ return 18 + (4 * this.common_caps.length) + (4 * this.channel_caps.length);
+ }
+}
+
+function SpiceLinkReply(a, at)
+{
+ this.error = 0;
+ this.pub_key = undefined;
+ this.common_caps = [];
+ this.channel_caps = [];
+
+ if (a !== undefined)
+ this.from_buffer(a, at);
+}
+
+SpiceLinkReply.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var i;
+ var orig_at = at;
+ var dv = new SpiceDataView(a);
+ this.error = dv.getUint32(at, true); at += 4;
+
+ this.pub_key = create_rsa_from_mb(a, at);
+ at += SPICE_TICKET_PUBKEY_BYTES;
+
+ var num_common_caps = dv.getUint32(at, true); at += 4;
+ var num_channel_caps = dv.getUint32(at, true); at += 4;
+ var caps_offset = dv.getUint32(at, true); at += 4;
+
+ at = orig_at + caps_offset;
+ this.common_caps = [];
+ for (i = 0; i < num_common_caps; i++)
+ {
+ this.common_caps.unshift(dv.getUint32(at, true)); at += 4;
+ }
+
+ this.channel_caps = [];
+ for (i = 0; i < num_channel_caps; i++)
+ {
+ this.channel_caps.unshift(dv.getUint32(at, true)); at += 4;
+ }
+ },
+}
+
+function SpiceLinkAuthTicket(a, at)
+{
+ this.auth_mechanism = 0;
+ this.encrypted_data = undefined;
+}
+
+SpiceLinkAuthTicket.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var i;
+ var dv = new SpiceDataView(a);
+ dv.setUint32(at, this.auth_mechanism, true); at += 4;
+ for (i = 0; i < SPICE_TICKET_KEY_PAIR_LENGTH / 8; i++)
+ {
+ if (this.encrypted_data && i < this.encrypted_data.length)
+ dv.setUint8(at, this.encrypted_data[i], true);
+ else
+ dv.setUint8(at, 0, true);
+ at++;
+ }
+ },
+ buffer_size: function()
+ {
+ return 4 + (SPICE_TICKET_KEY_PAIR_LENGTH / 8);
+ }
+}
+
+function SpiceLinkAuthReply(a, at)
+{
+ this.auth_code = 0;
+ if (a !== undefined)
+ this.from_buffer(a, at);
+}
+
+SpiceLinkAuthReply.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.auth_code = dv.getUint32(at, true); at += 4;
+ },
+ buffer_size: function()
+ {
+ return 4;
+ }
+}
+
+function SpiceMiniData(a, at)
+{
+ this.type = 0;
+ this.size = 0;
+ this.data = undefined;
+ if (a !== undefined)
+ this.from_buffer(a, at);
+}
+
+SpiceMiniData.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var i;
+ var dv = new SpiceDataView(a);
+ this.type = dv.getUint16(at, true); at += 2;
+ this.size = dv.getUint32(at, true); at += 4;
+ if (a.byteLength > at)
+ {
+ this.data = a.slice(at);
+ at += this.data.byteLength;
+ }
+ },
+ to_buffer : function(a, at)
+ {
+ at = at || 0;
+ var i;
+ var dv = new SpiceDataView(a);
+ dv.setUint16(at, this.type, true); at += 2;
+ dv.setUint32(at, this.data ? this.data.byteLength : 0, true); at += 4;
+ if (this.data && this.data.byteLength > 0)
+ {
+ var u8arr = new Uint8Array(this.data);
+ for (i = 0; i < u8arr.length; i++, at++)
+ dv.setUint8(at, u8arr[i], true);
+ }
+ },
+ build_msg : function(in_type, extra)
+ {
+ this.type = in_type;
+ this.size = extra.buffer_size();
+ this.data = new ArrayBuffer(this.size);
+ extra.to_buffer(this.data);
+ },
+ buffer_size: function()
+ {
+ if (this.data)
+ return 6 + this.data.byteLength;
+ else
+ return 6;
+ },
+}
+
+function SpiceMsgChannels(a, at)
+{
+ this.num_of_channels = 0;
+ this.channels = [];
+ if (a !== undefined)
+ this.from_buffer(a, at);
+}
+
+SpiceMsgChannels.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var i;
+ var dv = new SpiceDataView(a);
+ this.num_of_channels = dv.getUint32(at, true); at += 4;
+ for (i = 0; i < this.num_of_channels; i++)
+ {
+ var chan = new SpiceChannelId();
+ at = chan.from_dv(dv, at, a);
+ this.channels.push(chan);
+ }
+ },
+}
+
+function SpiceMsgMainInit(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgMainInit.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.session_id = dv.getUint32(at, true); at += 4;
+ this.display_channels_hint = dv.getUint32(at, true); at += 4;
+ this.supported_mouse_modes = dv.getUint32(at, true); at += 4;
+ this.current_mouse_mode = dv.getUint32(at, true); at += 4;
+ this.agent_connected = dv.getUint32(at, true); at += 4;
+ this.agent_tokens = dv.getUint32(at, true); at += 4;
+ this.multi_media_time = dv.getUint32(at, true); at += 4;
+ this.ram_hint = dv.getUint32(at, true); at += 4;
+ },
+}
+
+function SpiceMsgMainMouseMode(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgMainMouseMode.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.supported_modes = dv.getUint16(at, true); at += 2;
+ this.current_mode = dv.getUint16(at, true); at += 2;
+ },
+}
+
+function SpiceMsgSetAck(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgSetAck.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.generation = dv.getUint32(at, true); at += 4;
+ this.window = dv.getUint32(at, true); at += 4;
+ },
+}
+
+function SpiceMsgcAckSync(ack)
+{
+ this.generation = ack.generation;
+}
+
+SpiceMsgcAckSync.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ dv.setUint32(at, this.generation, true); at += 4;
+ },
+ buffer_size: function()
+ {
+ return 4;
+ }
+}
+
+function SpiceMsgcMainMouseModeRequest(mode)
+{
+ this.mode = mode;
+}
+
+SpiceMsgcMainMouseModeRequest.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ dv.setUint16(at, this.mode, true); at += 2;
+ },
+ buffer_size: function()
+ {
+ return 2;
+ }
+}
+
+function SpiceMsgNotify(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgNotify.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var i;
+ var dv = new SpiceDataView(a);
+ this.time_stamp = [];
+ this.time_stamp[0] = dv.getUint32(at, true); at += 4;
+ this.time_stamp[1] = dv.getUint32(at, true); at += 4;
+ this.severity = dv.getUint32(at, true); at += 4;
+ this.visibility = dv.getUint32(at, true); at += 4;
+ this.what = dv.getUint32(at, true); at += 4;
+ this.message_len = dv.getUint32(at, true); at += 4;
+ this.message = "";
+ for (i = 0; i < this.message_len; i++)
+ {
+ var c = dv.getUint8(at, true); at++;
+ this.message += String.fromCharCode(c);
+ }
+ },
+}
+
+function SpiceMsgcDisplayInit()
+{
+ this.pixmap_cache_id = 1;
+ this.glz_dictionary_id = 0;
+ this.pixmap_cache_size = 10 * 1024 * 1024;
+ this.glz_dictionary_window_size = 0;
+}
+
+SpiceMsgcDisplayInit.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ dv.setUint8(at, this.pixmap_cache_id, true); at++;
+ dv.setUint32(at, 0, true); at += 4;
+ dv.setUint32(at, this.pixmap_cache_size, true); at += 4;
+ dv.setUint8(at, this.glz_dictionary_id, true); at++;
+ dv.setUint32(at, this.glz_dictionary_window_size, true); at += 4;
+ },
+ buffer_size: function()
+ {
+ return 14;
+ }
+}
+
+function SpiceMsgDisplayBase()
+{
+}
+
+SpiceMsgDisplayBase.prototype =
+{
+ from_dv : function(dv, at, mb)
+ {
+ this.surface_id = dv.getUint32(at, true); at += 4;
+ this.box = new SpiceRect;
+ at = this.box.from_dv(dv, at, mb);
+ this.clip = new SpiceClip;
+ return this.clip.from_dv(dv, at, mb);
+ },
+}
+
+function SpiceMsgDisplayDrawCopy(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgDisplayDrawCopy.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.base = new SpiceMsgDisplayBase;
+ at = this.base.from_dv(dv, at, a);
+ this.data = new SpiceCopy;
+ return this.data.from_dv(dv, at, a);
+ },
+}
+
+function SpiceMsgDisplayDrawFill(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgDisplayDrawFill.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.base = new SpiceMsgDisplayBase;
+ at = this.base.from_dv(dv, at, a);
+ this.data = new SpiceFill;
+ return this.data.from_dv(dv, at, a);
+ },
+}
+
+function SpiceMsgDisplayCopyBits(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgDisplayCopyBits.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.base = new SpiceMsgDisplayBase;
+ at = this.base.from_dv(dv, at, a);
+ this.src_pos = new SpicePoint;
+ return this.src_pos.from_dv(dv, at, a);
+ },
+}
+
+
+function SpiceMsgSurfaceCreate(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgSurfaceCreate.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.surface = new SpiceSurface;
+ return this.surface.from_dv(dv, at, a);
+ },
+}
+
+function SpiceMsgSurfaceDestroy(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgSurfaceDestroy.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.surface_id = dv.getUint32(at, true); at += 4;
+ },
+}
+
+function SpiceMsgInputsInit(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgInputsInit.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.keyboard_modifiers = dv.getUint16(at, true); at += 2;
+ return at;
+ },
+}
+
+function SpiceMsgInputsKeyModifiers(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgInputsKeyModifiers.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.keyboard_modifiers = dv.getUint16(at, true); at += 2;
+ return at;
+ },
+}
+
+function SpiceMsgCursorInit(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgCursorInit.prototype =
+{
+ from_buffer: function(a, at, mb)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.position = new SpicePoint16;
+ at = this.position.from_dv(dv, at, mb);
+ this.trail_length = dv.getUint16(at, true); at += 2;
+ this.trail_frequency = dv.getUint16(at, true); at += 2;
+ this.visible = dv.getUint8(at, true); at ++;
+ this.cursor = new SpiceCursor;
+ return this.cursor.from_dv(dv, at, a);
+ },
+}
+
+
+function SpiceMsgCursorSet(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgCursorSet.prototype =
+{
+ from_buffer: function(a, at, mb)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.position = new SpicePoint16;
+ at = this.position.from_dv(dv, at, mb);
+ this.visible = dv.getUint8(at, true); at ++;
+ this.cursor = new SpiceCursor;
+ return this.cursor.from_dv(dv, at, a);
+ },
+}
+
+
+function SpiceMsgcMousePosition(sc, e)
+{
+ // FIXME - figure out how to correctly compute display_id
+ this.display_id = 0;
+ this.buttons_state = sc.buttons_state;
+ if (e)
+ {
+ this.x = e.clientX - sc.display.surfaces[sc.display.primary_surface].canvas.offsetLeft + document.body.scrollLeft;
+ this.y = e.clientY - sc.display.surfaces[sc.display.primary_surface].canvas.offsetTop + document.body.scrollTop;
+ sc.mousex = this.x;
+ sc.mousey = this.y;
+ }
+ else
+ {
+ this.x = this.y = this.buttons_state = 0;
+ }
+}
+
+SpiceMsgcMousePosition.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ dv.setUint32(at, this.x, true); at += 4;
+ dv.setUint32(at, this.y, true); at += 4;
+ dv.setUint16(at, this.buttons_state, true); at += 2;
+ dv.setUint8(at, this.display_id, true); at += 1;
+ return at;
+ },
+ buffer_size: function()
+ {
+ return 11;
+ }
+}
+
+function SpiceMsgcMouseMotion(sc, e)
+{
+ // FIXME - figure out how to correctly compute display_id
+ this.display_id = 0;
+ this.buttons_state = sc.buttons_state;
+ if (e)
+ {
+ this.x = e.clientX - sc.display.surfaces[sc.display.primary_surface].canvas.offsetLeft;
+ this.y = e.clientY - sc.display.surfaces[sc.display.primary_surface].canvas.offsetTop;
+
+ if (sc.mousex !== undefined)
+ {
+ this.x -= sc.mousex;
+ this.y -= sc.mousey;
+ }
+ sc.mousex = e.clientX - sc.display.surfaces[sc.display.primary_surface].canvas.offsetLeft;
+ sc.mousey = e.clientY - sc.display.surfaces[sc.display.primary_surface].canvas.offsetTop;
+ }
+ else
+ {
+ this.x = this.y = this.buttons_state = 0;
+ }
+}
+
+/* Use the same functions as for MousePosition */
+SpiceMsgcMouseMotion.prototype.to_buffer = SpiceMsgcMousePosition.prototype.to_buffer;
+SpiceMsgcMouseMotion.prototype.buffer_size = SpiceMsgcMousePosition.prototype.buffer_size;
+
+function SpiceMsgcMousePress(sc, e)
+{
+ if (e)
+ {
+ this.button = e.button + 1;
+ this.buttons_state = 1 << e.button;
+ sc.buttons_state = this.buttons_state;
+ }
+ else
+ {
+ this.button = SPICE_MOUSE_BUTTON_LEFT;
+ this.buttons_state = SPICE_MOUSE_BUTTON_MASK_LEFT;
+ }
+}
+
+SpiceMsgcMousePress.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ dv.setUint8(at, this.button, true); at ++;
+ dv.setUint16(at, this.buttons_state, true); at += 2;
+ return at;
+ },
+ buffer_size: function()
+ {
+ return 3;
+ }
+}
+
+function SpiceMsgcMouseRelease(sc, e)
+{
+ if (e)
+ {
+ this.button = e.button + 1;
+ this.buttons_state = 0;
+ sc.buttons_state = this.buttons_state;
+ }
+ else
+ {
+ this.button = SPICE_MOUSE_BUTTON_LEFT;
+ this.buttons_state = 0;
+ }
+}
+
+/* Use the same functions as for MousePress */
+SpiceMsgcMouseRelease.prototype.to_buffer = SpiceMsgcMousePress.prototype.to_buffer;
+SpiceMsgcMouseRelease.prototype.buffer_size = SpiceMsgcMousePress.prototype.buffer_size;
+
+
+function SpiceMsgcKeyDown(e)
+{
+ if (e)
+ {
+ this.code = keycode_to_start_scan(e.keyCode);
+ }
+ else
+ {
+ this.code = 0;
+ }
+}
+
+SpiceMsgcKeyDown.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ dv.setUint32(at, this.code, true); at += 4;
+ return at;
+ },
+ buffer_size: function()
+ {
+ return 4;
+ }
+}
+
+function SpiceMsgcKeyUp(e)
+{
+ if (e)
+ {
+ this.code = keycode_to_end_scan(e.keyCode);
+ }
+ else
+ {
+ this.code = 0;
+ }
+}
+
+/* Use the same functions as for KeyDown */
+SpiceMsgcKeyUp.prototype.to_buffer = SpiceMsgcKeyDown.prototype.to_buffer;
+SpiceMsgcKeyUp.prototype.buffer_size = SpiceMsgcKeyDown.prototype.buffer_size;
+
+function SpiceMsgDisplayStreamCreate(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgDisplayStreamCreate.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.surface_id = dv.getUint32(at, true); at += 4;
+ this.id = dv.getUint32(at, true); at += 4;
+ this.flags = dv.getUint8(at, true); at += 1;
+ this.codec_type = dv.getUint8(at, true); at += 1;
+ /*stamp */ at += 8;
+ this.stream_width = dv.getUint32(at, true); at += 4;
+ this.stream_height = dv.getUint32(at, true); at += 4;
+ this.src_width = dv.getUint32(at, true); at += 4;
+ this.src_height = dv.getUint32(at, true); at += 4;
+
+ this.dest = new SpiceRect;
+ at = this.dest.from_dv(dv, at, a);
+ this.clip = new SpiceClip;
+ this.clip.from_dv(dv, at, a);
+ },
+}
+
+function SpiceStreamDataHeader(a, at)
+{
+}
+
+SpiceStreamDataHeader.prototype =
+{
+ from_dv : function(dv, at, mb)
+ {
+ this.id = dv.getUint32(at, true); at += 4;
+ this.multi_media_time = dv.getUint32(at, true); at += 4;
+ return at;
+ },
+}
+
+function SpiceMsgDisplayStreamData(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgDisplayStreamData.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.base = new SpiceStreamDataHeader;
+ at = this.base.from_dv(dv, at, a);
+ this.data_size = dv.getUint32(at, true); at += 4;
+ this.data = dv.u8.subarray(at, at + this.data_size);
+ },
+}
+
+function SpiceMsgDisplayStreamClip(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgDisplayStreamClip.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.id = dv.getUint32(at, true); at += 4;
+ this.clip = new SpiceClip;
+ this.clip.from_dv(dv, at, a);
+ },
+}
+
+function SpiceMsgDisplayStreamDestroy(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgDisplayStreamDestroy.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.id = dv.getUint32(at, true); at += 4;
+ },
+}
diff --git a/ui/js/spice/spicetype.js b/ui/js/spice/spicetype.js
new file mode 100644
index 0000000..b88ba28
--- /dev/null
+++ b/ui/js/spice/spicetype.js
@@ -0,0 +1,480 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*----------------------------------------------------------------------------
+** Spice types
+** This file contains classes for common spice types.
+** Generally, they are used as helpers in reading and writing messages
+** to and from the server.
+**--------------------------------------------------------------------------*/
+
+function SpiceChannelId()
+{
+}
+SpiceChannelId.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.type = dv.getUint8(at, true); at ++;
+ this.id = dv.getUint8(at, true); at ++;
+ return at;
+ },
+}
+
+function SpiceRect()
+{
+}
+
+SpiceRect.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.top = dv.getUint32(at, true); at += 4;
+ this.left = dv.getUint32(at, true); at += 4;
+ this.bottom = dv.getUint32(at, true); at += 4;
+ this.right = dv.getUint32(at, true); at += 4;
+ return at;
+ },
+ is_same_size : function(r)
+ {
+ if ((this.bottom - this.top) == (r.bottom - r.top) &&
+ (this.right - this.left) == (r.right - r.left) )
+ return true;
+
+ return false;
+ },
+}
+
+function SpiceClipRects()
+{
+}
+
+SpiceClipRects.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ var i;
+ this.num_rects = dv.getUint32(at, true); at += 4;
+ if (this.num_rects > 0)
+ this.rects = [];
+ for (i = 0; i < this.num_rects; i++)
+ {
+ this.rects[i] = new SpiceRect();
+ at = this.rects[i].from_dv(dv, at, mb);
+ }
+ return at;
+ },
+}
+
+function SpiceClip()
+{
+}
+
+SpiceClip.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.type = dv.getUint8(at, true); at ++;
+ if (this.type == SPICE_CLIP_TYPE_RECTS)
+ {
+ this.rects = new SpiceClipRects();
+ at = this.rects.from_dv(dv, at, mb);
+ }
+ return at;
+ },
+}
+
+function SpiceImageDescriptor()
+{
+}
+
+SpiceImageDescriptor.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.id = dv.getUint32(at, true); at += 4;
+ this.id += (dv.getUint32(at, true) << 32); at += 4;
+ this.type = dv.getUint8(at, true); at ++;
+ this.flags = dv.getUint8(at, true); at ++;
+ this.width = dv.getUint32(at, true); at += 4;
+ this.height= dv.getUint32(at, true); at += 4;
+ return at;
+ },
+}
+
+function SpicePalette()
+{
+}
+
+SpicePalette.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ var i;
+ this.unique = [];
+ this.unique[0] = dv.getUint32(at, true); at += 4;
+ this.unique[1] = dv.getUint32(at, true); at += 4;
+ this.num_ents = dv.getUint16(at, true); at += 2;
+ this.ents = [];
+ for (i = 0; i < this.num_ents; i++)
+ {
+ this.ents[i] = dv.getUint32(at, true); at += 4;
+ }
+ return at;
+ },
+}
+
+function SpiceBitmap()
+{
+}
+
+SpiceBitmap.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.format = dv.getUint8(at, true); at++;
+ this.flags = dv.getUint8(at, true); at++;
+ this.x = dv.getUint32(at, true); at += 4;
+ this.y = dv.getUint32(at, true); at += 4;
+ this.stride = dv.getUint32(at, true); at += 4;
+ if (this.flags & SPICE_BITMAP_FLAGS_PAL_FROM_CACHE)
+ {
+ this.palette_id = [];
+ this.palette_id[0] = dv.getUint32(at, true); at += 4;
+ this.palette_id[1] = dv.getUint32(at, true); at += 4;
+ }
+ else
+ {
+ var offset = dv.getUint32(at, true); at += 4;
+ if (offset == 0)
+ this.palette = null;
+ else
+ {
+ this.palette = new SpicePalette;
+ this.palette.from_dv(dv, offset, mb);
+ }
+ }
+ // FIXME - should probably constrain this to the offset
+ // of palette, if non zero
+ this.data = mb.slice(at);
+ at += this.data.byteLength;
+ return at;
+ },
+}
+
+function SpiceImage()
+{
+}
+
+SpiceImage.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.descriptor = new SpiceImageDescriptor;
+ at = this.descriptor.from_dv(dv, at, mb);
+
+ if (this.descriptor.type == SPICE_IMAGE_TYPE_LZ_RGB)
+ {
+ this.lz_rgb = new Object();
+ this.lz_rgb.length = dv.getUint32(at, true); at += 4;
+ var initial_at = at;
+ this.lz_rgb.magic = "";
+ for (var i = 3; i >= 0; i--)
+ this.lz_rgb.magic += String.fromCharCode(dv.getUint8(at + i));
+ at += 4;
+
+ // NOTE: The endian change is *correct*
+ this.lz_rgb.version = dv.getUint32(at); at += 4;
+ this.lz_rgb.type = dv.getUint32(at); at += 4;
+ this.lz_rgb.width = dv.getUint32(at); at += 4;
+ this.lz_rgb.height = dv.getUint32(at); at += 4;
+ this.lz_rgb.stride = dv.getUint32(at); at += 4;
+ this.lz_rgb.top_down = dv.getUint32(at); at += 4;
+
+ var header_size = at - initial_at;
+
+ this.lz_rgb.data = mb.slice(at, this.lz_rgb.length + at - header_size);
+ at += this.lz_rgb.data.byteLength;
+
+ }
+
+ if (this.descriptor.type == SPICE_IMAGE_TYPE_BITMAP)
+ {
+ this.bitmap = new SpiceBitmap;
+ at = this.bitmap.from_dv(dv, at, mb);
+ }
+
+ if (this.descriptor.type == SPICE_IMAGE_TYPE_SURFACE)
+ {
+ this.surface_id = dv.getUint32(at, true); at += 4;
+ }
+
+ if (this.descriptor.type == SPICE_IMAGE_TYPE_JPEG)
+ {
+ this.jpeg = new Object;
+ this.jpeg.data_size = dv.getUint32(at, true); at += 4;
+ this.jpeg.data = mb.slice(at);
+ at += this.jpeg.data.byteLength;
+ }
+
+ if (this.descriptor.type == SPICE_IMAGE_TYPE_JPEG_ALPHA)
+ {
+ this.jpeg_alpha = new Object;
+ this.jpeg_alpha.flags = dv.getUint8(at, true); at += 1;
+ this.jpeg_alpha.jpeg_size = dv.getUint32(at, true); at += 4;
+ this.jpeg_alpha.data_size = dv.getUint32(at, true); at += 4;
+ this.jpeg_alpha.data = mb.slice(at, this.jpeg_alpha.jpeg_size + at);
+ at += this.jpeg_alpha.data.byteLength;
+ // Alpha channel is an LZ image
+ this.jpeg_alpha.alpha = new Object();
+ this.jpeg_alpha.alpha.length = this.jpeg_alpha.data_size - this.jpeg_alpha.jpeg_size;
+ var initial_at = at;
+ this.jpeg_alpha.alpha.magic = "";
+ for (var i = 3; i >= 0; i--)
+ this.jpeg_alpha.alpha.magic += String.fromCharCode(dv.getUint8(at + i));
+ at += 4;
+
+ // NOTE: The endian change is *correct*
+ this.jpeg_alpha.alpha.version = dv.getUint32(at); at += 4;
+ this.jpeg_alpha.alpha.type = dv.getUint32(at); at += 4;
+ this.jpeg_alpha.alpha.width = dv.getUint32(at); at += 4;
+ this.jpeg_alpha.alpha.height = dv.getUint32(at); at += 4;
+ this.jpeg_alpha.alpha.stride = dv.getUint32(at); at += 4;
+ this.jpeg_alpha.alpha.top_down = dv.getUint32(at); at += 4;
+
+ var header_size = at - initial_at;
+
+ this.jpeg_alpha.alpha.data = mb.slice(at, this.jpeg_alpha.alpha.length + at - header_size);
+ at += this.jpeg_alpha.alpha.data.byteLength;
+ }
+
+ if (this.descriptor.type == SPICE_IMAGE_TYPE_QUIC)
+ {
+ this.quic = new SpiceQuic;
+ at = this.quic.from_dv(dv, at, mb);
+ }
+ return at;
+ },
+}
+
+
+function SpiceQMask()
+{
+}
+
+SpiceQMask.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.flags = dv.getUint8(at, true); at++;
+ this.pos = new SpicePoint;
+ at = this.pos.from_dv(dv, at, mb);
+ var offset = dv.getUint32(at, true); at += 4;
+ if (offset == 0)
+ {
+ this.bitmap = null;
+ return at;
+ }
+
+ this.bitmap = new SpiceImage;
+ return this.bitmap.from_dv(dv, offset, mb);
+ },
+}
+
+
+function SpicePattern()
+{
+}
+
+SpicePattern.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ var offset = dv.getUint32(at, true); at += 4;
+ if (offset == 0)
+ {
+ this.pat = null;
+ }
+ else
+ {
+ this.pat = new SpiceImage;
+ this.pat.from_dv(dv, offset, mb);
+ }
+
+ this.pos = new SpicePoint;
+ return this.pos.from_dv(dv, at, mb);
+ }
+}
+
+function SpiceBrush()
+{
+}
+
+SpiceBrush.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.type = dv.getUint8(at, true); at ++;
+ if (this.type == SPICE_BRUSH_TYPE_SOLID)
+ {
+ this.color = dv.getUint32(at, true); at += 4;
+ }
+ else if (this.type == SPICE_BRUSH_TYPE_PATTERN)
+ {
+ this.pattern = new SpicePattern;
+ at = this.pattern.from_dv(dv, at, mb);
+ }
+ return at;
+ },
+}
+
+function SpiceFill()
+{
+}
+
+SpiceFill.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.brush = new SpiceBrush;
+ at = this.brush.from_dv(dv, at, mb);
+ this.rop_descriptor = dv.getUint16(at, true); at += 2;
+ this.mask = new SpiceQMask;
+ return this.mask.from_dv(dv, at, mb);
+ },
+}
+
+
+function SpiceCopy()
+{
+}
+
+SpiceCopy.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ var offset = dv.getUint32(at, true); at += 4;
+ if (offset == 0)
+ {
+ this.src_bitmap = null;
+ }
+ else
+ {
+ this.src_bitmap = new SpiceImage;
+ this.src_bitmap.from_dv(dv, offset, mb);
+ }
+ this.src_area = new SpiceRect;
+ at = this.src_area.from_dv(dv, at, mb);
+ this.rop_descriptor = dv.getUint16(at, true); at += 2;
+ this.scale_mode = dv.getUint8(at, true); at ++;
+ this.mask = new SpiceQMask;
+ return this.mask.from_dv(dv, at, mb);
+ },
+}
+
+function SpicePoint16()
+{
+}
+
+SpicePoint16.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.x = dv.getUint16(at, true); at += 2;
+ this.y = dv.getUint16(at, true); at += 2;
+ return at;
+ },
+}
+
+function SpicePoint()
+{
+}
+
+SpicePoint.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.x = dv.getUint32(at, true); at += 4;
+ this.y = dv.getUint32(at, true); at += 4;
+ return at;
+ },
+}
+
+function SpiceCursorHeader()
+{
+}
+
+SpiceCursorHeader.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.unique = [];
+ this.unique[0] = dv.getUint32(at, true); at += 4;
+ this.unique[1] = dv.getUint32(at, true); at += 4;
+ this.type = dv.getUint8(at, true); at ++;
+ this.width = dv.getUint16(at, true); at += 2;
+ this.height = dv.getUint16(at, true); at += 2;
+ this.hot_spot_x = dv.getUint16(at, true); at += 2;
+ this.hot_spot_y = dv.getUint16(at, true); at += 2;
+ return at;
+ },
+}
+
+function SpiceCursor()
+{
+}
+
+SpiceCursor.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.flags = dv.getUint16(at, true); at += 2;
+ if (this.flags & SPICE_CURSOR_FLAGS_NONE)
+ this.header = null;
+ else
+ {
+ this.header = new SpiceCursorHeader;
+ at = this.header.from_dv(dv, at, mb);
+ this.data = mb.slice(at);
+ at += this.data.byteLength;
+ }
+ return at;
+ },
+}
+
+function SpiceSurface()
+{
+}
+
+SpiceSurface.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.surface_id = dv.getUint32(at, true); at += 4;
+ this.width = dv.getUint32(at, true); at += 4;
+ this.height = dv.getUint32(at, true); at += 4;
+ this.format = dv.getUint32(at, true); at += 4;
+ this.flags = dv.getUint32(at, true); at += 4;
+ return at;
+ },
+}
+
+/* FIXME - SpiceImage types lz_plt, jpeg, zlib_glz, and jpeg_alpha are
+ completely unimplemented */
diff --git a/ui/js/spice/ticket.js b/ui/js/spice/ticket.js
new file mode 100644
index 0000000..96577a3
--- /dev/null
+++ b/ui/js/spice/ticket.js
@@ -0,0 +1,250 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+var SHA_DIGEST_LENGTH = 20;
+
+/*----------------------------------------------------------------------------
+** General ticket RSA encryption functions - just good enough to
+** support what we need to send back an encrypted ticket.
+**--------------------------------------------------------------------------*/
+
+
+/*----------------------------------------------------------------------------
+** OAEP padding functions. Inspired by the OpenSSL implementation.
+**--------------------------------------------------------------------------*/
+function MGF1(mask, seed)
+{
+ var i, j, outlen;
+ for (i = 0, outlen = 0; outlen < mask.length; i++)
+ {
+ var combo_buf = new String;
+
+ for (j = 0; j < seed.length; j++)
+ combo_buf += String.fromCharCode(seed[j]);
+ combo_buf += String.fromCharCode((i >> 24) & 255);
+ combo_buf += String.fromCharCode((i >> 16) & 255);
+ combo_buf += String.fromCharCode((i >> 8) & 255);
+ combo_buf += String.fromCharCode((i) & 255);
+
+ var combo_hash = rstr_sha1(combo_buf);
+ for (j = 0; j < combo_hash.length && outlen < mask.length; j++, outlen++)
+ {
+ mask[outlen] = combo_hash.charCodeAt(j);
+ }
+ }
+}
+
+
+function RSA_padding_add_PKCS1_OAEP(tolen, from, param)
+{
+ var seed = new Array(SHA_DIGEST_LENGTH);
+ var rand = new SecureRandom();
+ rand.nextBytes(seed);
+
+ var dblen = tolen - 1 - seed.length;
+ var db = new Array(dblen);
+ var padlen = dblen - from.length - 1;
+ var i;
+
+ if (param === undefined)
+ param = "";
+
+ if (padlen < SHA_DIGEST_LENGTH)
+ {
+ console.log("Error - data too large for key size.");
+ return null;
+ }
+
+ for (i = 0; i < padlen; i++)
+ db[i] = 0;
+
+ var param_hash = rstr_sha1(param);
+ for (i = 0; i < param_hash.length; i++)
+ db[i] = param_hash.charCodeAt(i);
+
+ db[padlen] = 1;
+ for (i = 0; i < from.length; i++)
+ db[i + padlen + 1] = from.charCodeAt(i);
+
+ var dbmask = new Array(dblen);
+ if (MGF1(dbmask, seed) < 0)
+ return null;
+
+ for (i = 0; i < dbmask.length; i++)
+ db[i] ^= dbmask[i];
+
+
+ var seedmask = Array(SHA_DIGEST_LENGTH);
+ if (MGF1(seedmask, db) < 0)
+ return null;
+
+ for (i = 0; i < seedmask.length; i++)
+ seed[i] ^= seedmask[i];
+
+ var ret = new String;
+ ret += String.fromCharCode(0);
+ for (i = 0; i < seed.length; i++)
+ ret += String.fromCharCode(seed[i]);
+ for (i = 0; i < db.length; i++)
+ ret += String.fromCharCode(db[i]);
+ return ret;
+}
+
+
+function asn_get_length(u8, at)
+{
+ var len = u8[at++];
+ if (len > 0x80)
+ {
+ if (len != 0x81)
+ {
+ console.log("Error: we lazily don't support keys bigger than 255 bytes. It'd be easy to fix.");
+ return null;
+ }
+ len = u8[at++];
+ }
+
+ return [ at, len];
+}
+
+function find_sequence(u8, at)
+{
+ var lenblock;
+ at = at || 0;
+ if (u8[at++] != 0x30)
+ {
+ console.log("Error: public key should start with a sequence flag.");
+ return null;
+ }
+
+ lenblock = asn_get_length(u8, at);
+ if (! lenblock)
+ return null;
+ return lenblock;
+}
+
+/*----------------------------------------------------------------------------
+** Extract an RSA key from a memory buffer
+**--------------------------------------------------------------------------*/
+function create_rsa_from_mb(mb, at)
+{
+ var u8 = new Uint8Array(mb);
+ var lenblock;
+ var seq;
+ var ba;
+ var i;
+ var ret;
+
+ /* We have a sequence which contains a sequence followed by a bit string */
+ seq = find_sequence(u8, at);
+ if (! seq)
+ return null;
+
+ at = seq[0];
+ seq = find_sequence(u8, at);
+ if (! seq)
+ return null;
+
+ /* Skip over the contained sequence */
+ at = seq[0] + seq[1];
+ if (u8[at++] != 0x3)
+ {
+ console.log("Error: expecting bit string next.");
+ return null;
+ }
+
+ /* Get the bit string, which is *itself* a sequence. Having fun yet? */
+ lenblock = asn_get_length(u8, at);
+ if (! lenblock)
+ return null;
+
+ at = lenblock[0];
+ if (u8[at] != 0 && u8[at + 1] != 0x30)
+ {
+ console.log("Error: unexpected values in bit string.");
+ return null;
+ }
+
+ /* Okay, now we have a sequence of two binary values, we hope. */
+ seq = find_sequence(u8, at + 1);
+ if (! seq)
+ return null;
+
+ at = seq[0];
+ if (u8[at++] != 0x02)
+ {
+ console.log("Error: expecting integer n next.");
+ return null;
+ }
+ lenblock = asn_get_length(u8, at);
+ if (! lenblock)
+ return null;
+ at = lenblock[0];
+
+ ba = new Array(lenblock[1]);
+ for (i = 0; i < lenblock[1]; i++)
+ ba[i] = u8[at + i];
+
+ ret = new RSAKey();
+ ret.n = new BigInteger(ba);
+
+ at += lenblock[1];
+
+ if (u8[at++] != 0x02)
+ {
+ console.log("Error: expecting integer e next.");
+ return null;
+ }
+ lenblock = asn_get_length(u8, at);
+ if (! lenblock)
+ return null;
+ at = lenblock[0];
+
+ ret.e = u8[at++];
+ for (i = 1; i < lenblock[1]; i++)
+ {
+ ret.e <<= 8;
+ ret.e |= u8[at++];
+ }
+
+ return ret;
+}
+
+function rsa_encrypt(rsa, str)
+{
+ var i;
+ var ret = [];
+ var oaep = RSA_padding_add_PKCS1_OAEP((rsa.n.bitLength()+7)>>3, str);
+ if (! oaep)
+ return null;
+
+ var ba = new Array(oaep.length);
+
+ for (i = 0; i < oaep.length; i++)
+ ba[i] = oaep.charCodeAt(i);
+ var bigint = new BigInteger(ba);
+ var enc = rsa.doPublic(bigint);
+ var h = enc.toString(16);
+ if ((h.length & 1) != 0)
+ h = "0" + h;
+ for (i = 0; i < h.length; i += 2)
+ ret[i / 2] = parseInt(h.substring(i, i + 2), 16);
+ return ret;
+}
diff --git a/ui/js/spice/utils.js b/ui/js/spice/utils.js
new file mode 100644
index 0000000..5ef23d6
--- /dev/null
+++ b/ui/js/spice/utils.js
@@ -0,0 +1,261 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*----------------------------------------------------------------------------
+** Utility settings and functions for Spice
+**--------------------------------------------------------------------------*/
+var DEBUG = 0;
+var DUMP_DRAWS = false;
+var DUMP_CANVASES = false;
+
+
+/*----------------------------------------------------------------------------
+** combine_array_buffers
+** Combine two array buffers.
+** FIXME - this can't be optimal. See wire.js about eliminating the need.
+**--------------------------------------------------------------------------*/
+function combine_array_buffers(a1, a2)
+{
+ var in1 = new Uint8Array(a1);
+ var in2 = new Uint8Array(a2);
+ var ret = new ArrayBuffer(a1.byteLength + a2.byteLength);
+ var out = new Uint8Array(ret);
+ var o = 0;
+ var i;
+ for (i = 0; i < in1.length; i++)
+ out[o++] = in1[i];
+ for (i = 0; i < in2.length; i++)
+ out[o++] = in2[i];
+
+ return ret;
+}
+
+/*----------------------------------------------------------------------------
+** hexdump_buffer
+**--------------------------------------------------------------------------*/
+function hexdump_buffer(a)
+{
+ var mg = new Uint8Array(a);
+ var hex = "";
+ var str = "";
+ var last_zeros = 0;
+ for (var i = 0; i < mg.length; i++)
+ {
+ var h = Number(mg[i]).toString(16);
+ if (h.length == 1)
+ hex += "0";
+ hex += h + " ";
+
+ str += String.fromCharCode(mg[i]);
+
+ if ((i % 16 == 15) || (i == (mg.length - 1)))
+ {
+ while (i % 16 != 15)
+ {
+ hex += " ";
+ i++;
+ }
+
+ if (last_zeros == 0)
+ console.log(hex + " | " + str);
+
+ if (hex == "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ")
+ {
+ if (last_zeros == 1)
+ {
+ console.log(".");
+ last_zeros++;
+ }
+ else if (last_zeros == 0)
+ last_zeros++;
+ }
+ else
+ last_zeros = 0;
+
+ hex = str = "";
+ }
+ }
+}
+
+/*----------------------------------------------------------------------------
+** Converting keycodes to AT scancodes is very hard.
+** luckly there are some resources on the web and in the Xorg driver that help
+** us figure out what browser depenend keycodes match to what scancodes.
+**
+** This will most likely not work for non US keyboard and browsers other than
+** modern Chrome and FireFox.
+**--------------------------------------------------------------------------*/
+var common_scanmap = [];
+common_scanmap['Q'.charCodeAt(0)] = KEY_Q;
+common_scanmap['W'.charCodeAt(0)] = KEY_W;
+common_scanmap['E'.charCodeAt(0)] = KEY_E;
+common_scanmap['R'.charCodeAt(0)] = KEY_R;
+common_scanmap['T'.charCodeAt(0)] = KEY_T;
+common_scanmap['Y'.charCodeAt(0)] = KEY_Y;
+common_scanmap['U'.charCodeAt(0)] = KEY_U;
+common_scanmap['I'.charCodeAt(0)] = KEY_I;
+common_scanmap['O'.charCodeAt(0)] = KEY_O;
+common_scanmap['P'.charCodeAt(0)] = KEY_P;
+common_scanmap['A'.charCodeAt(0)] = KEY_A;
+common_scanmap['S'.charCodeAt(0)] = KEY_S;
+common_scanmap['D'.charCodeAt(0)] = KEY_D;
+common_scanmap['F'.charCodeAt(0)] = KEY_F;
+common_scanmap['G'.charCodeAt(0)] = KEY_G;
+common_scanmap['H'.charCodeAt(0)] = KEY_H;
+common_scanmap['J'.charCodeAt(0)] = KEY_J;
+common_scanmap['K'.charCodeAt(0)] = KEY_K;
+common_scanmap['L'.charCodeAt(0)] = KEY_L;
+common_scanmap['Z'.charCodeAt(0)] = KEY_Z;
+common_scanmap['X'.charCodeAt(0)] = KEY_X;
+common_scanmap['C'.charCodeAt(0)] = KEY_C;
+common_scanmap['V'.charCodeAt(0)] = KEY_V;
+common_scanmap['B'.charCodeAt(0)] = KEY_B;
+common_scanmap['N'.charCodeAt(0)] = KEY_N;
+common_scanmap['M'.charCodeAt(0)] = KEY_M;
+common_scanmap[' '.charCodeAt(0)] = KEY_Space;
+common_scanmap[13] = KEY_Enter;
+common_scanmap[27] = KEY_Escape;
+common_scanmap[8] = KEY_BackSpace;
+common_scanmap[9] = KEY_Tab;
+common_scanmap[16] = KEY_ShiftL;
+common_scanmap[17] = KEY_LCtrl;
+common_scanmap[18] = KEY_Alt;
+common_scanmap[20] = KEY_CapsLock;
+common_scanmap[144] = KEY_NumLock;
+common_scanmap[112] = KEY_F1;
+common_scanmap[113] = KEY_F2;
+common_scanmap[114] = KEY_F3;
+common_scanmap[115] = KEY_F4;
+common_scanmap[116] = KEY_F5;
+common_scanmap[117] = KEY_F6;
+common_scanmap[118] = KEY_F7;
+common_scanmap[119] = KEY_F8;
+common_scanmap[120] = KEY_F9;
+common_scanmap[121] = KEY_F10;
+common_scanmap[122] = KEY_F11;
+common_scanmap[123] = KEY_F12;
+
+/* These externded scancodes do not line up with values from atKeynames */
+common_scanmap[42] = 99;
+common_scanmap[19] = 101; // Break
+common_scanmap[111] = 0xE035; // KP_Divide
+common_scanmap[106] = 0xE037; // KP_Multiply
+common_scanmap[36] = 0xE047; // Home
+common_scanmap[38] = 0xE048; // Up
+common_scanmap[33] = 0xE049; // PgUp
+common_scanmap[37] = 0xE04B; // Left
+common_scanmap[39] = 0xE04D; // Right
+common_scanmap[35] = 0xE04F; // End
+common_scanmap[40] = 0xE050; // Down
+common_scanmap[34] = 0xE051; // PgDown
+common_scanmap[45] = 0xE052; // Insert
+common_scanmap[46] = 0xE053; // Delete
+common_scanmap[44] = 0x2A37; // Print
+
+/* These are not common between ALL browsers but are between Firefox and DOM3 */
+common_scanmap['1'.charCodeAt(0)] = KEY_1;
+common_scanmap['2'.charCodeAt(0)] = KEY_2;
+common_scanmap['3'.charCodeAt(0)] = KEY_3;
+common_scanmap['4'.charCodeAt(0)] = KEY_4;
+common_scanmap['5'.charCodeAt(0)] = KEY_5;
+common_scanmap['6'.charCodeAt(0)] = KEY_6;
+common_scanmap['7'.charCodeAt(0)] = KEY_7;
+common_scanmap['8'.charCodeAt(0)] = KEY_8;
+common_scanmap['9'.charCodeAt(0)] = KEY_9;
+common_scanmap['0'.charCodeAt(0)] = KEY_0;
+common_scanmap[145] = KEY_ScrollLock;
+common_scanmap[103] = KEY_KP_7;
+common_scanmap[104] = KEY_KP_8;
+common_scanmap[105] = KEY_KP_9;
+common_scanmap[100] = KEY_KP_4;
+common_scanmap[101] = KEY_KP_5;
+common_scanmap[102] = KEY_KP_6;
+common_scanmap[107] = KEY_KP_Plus;
+common_scanmap[97] = KEY_KP_1;
+common_scanmap[98] = KEY_KP_2;
+common_scanmap[99] = KEY_KP_3;
+common_scanmap[96] = KEY_KP_0;
+common_scanmap[110] = KEY_KP_Decimal;
+common_scanmap[191] = KEY_Slash;
+common_scanmap[190] = KEY_Period;
+common_scanmap[188] = KEY_Comma;
+common_scanmap[220] = KEY_BSlash;
+common_scanmap[192] = KEY_Tilde;
+common_scanmap[222] = KEY_Quote;
+common_scanmap[219] = KEY_LBrace;
+common_scanmap[221] = KEY_RBrace;
+
+common_scanmap[91] = 0xE05B; //KEY_LMeta
+common_scanmap[92] = 0xE05C; //KEY_RMeta
+common_scanmap[93] = 0xE05D; //KEY_Menu
+
+/* Firefox/Mozilla codes */
+var firefox_scanmap = [];
+firefox_scanmap[109] = KEY_Minus;
+firefox_scanmap[61] = KEY_Equal;
+firefox_scanmap[59] = KEY_SemiColon;
+
+/* DOM3 codes */
+var DOM_scanmap = [];
+DOM_scanmap[189] = KEY_Minus;
+DOM_scanmap[187] = KEY_Equal;
+DOM_scanmap[186] = KEY_SemiColon;
+
+function get_scancode(code)
+{
+ if (common_scanmap[code] === undefined)
+ {
+ if (navigator.userAgent.indexOf("Firefox") != -1)
+ return firefox_scanmap[code];
+ else
+ return DOM_scanmap[code];
+ }
+ else
+ return common_scanmap[code];
+}
+
+function keycode_to_start_scan(code)
+{
+ var scancode = get_scancode(code);
+ if (scancode === undefined)
+ {
+ alert('no map for ' + code);
+ return 0;
+ }
+
+ if (scancode < 0x100) {
+ return scancode;
+ } else {
+ return 0xe0 | ((scancode - 0x100) << 8);
+ }
+}
+
+function keycode_to_end_scan(code)
+{
+ var scancode = get_scancode(code);
+ if (scancode === undefined)
+ return 0;
+
+ if (scancode < 0x100) {
+ return scancode | 0x80;
+ } else {
+ return 0x80e0 | ((scancode - 0x100) << 8);
+ }
+}
diff --git a/ui/js/spice/wire.js b/ui/js/spice/wire.js
new file mode 100644
index 0000000..2c7f096
--- /dev/null
+++ b/ui/js/spice/wire.js
@@ -0,0 +1,123 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*--------------------------------------------------------------------------------------
+** SpiceWireReader
+** This class will receive messages from a WebSocket and relay it to a given
+** callback. It will optionally save and pass along a header, useful in processing
+** the mini message format.
+**--------------------------------------------------------------------------------------*/
+function SpiceWireReader(sc, callback)
+{
+ this.sc = sc;
+ this.callback = callback;
+ this.needed = 0;
+
+ this.buffers = [];
+
+ this.sc.ws.wire_reader = this;
+ this.sc.ws.binaryType = "arraybuffer";
+ this.sc.ws.addEventListener('message', wire_blob_catcher);
+}
+
+SpiceWireReader.prototype =
+{
+
+ /*------------------------------------------------------------------------
+ ** Process messages coming in from our WebSocket
+ **----------------------------------------------------------------------*/
+ inbound: function (mb)
+ {
+ var at;
+
+ /* Just buffer if we don't need anything yet */
+ if (this.needed == 0)
+ {
+ this.buffers.push(mb);
+ return;
+ }
+
+ /* Optimization - if we have just one inbound block, and it's
+ suitable for our needs, just use it. */
+ if (this.buffers.length == 0 && mb.byteLength >= this.needed)
+ {
+ if (mb.byteLength > this.needed)
+ {
+ this.buffers.push(mb.slice(this.needed));
+ mb = mb.slice(0, this.needed);
+ }
+ this.callback.call(this.sc, mb,
+ this.saved_msg_header || undefined);
+ }
+ else
+ {
+ this.buffers.push(mb);
+ }
+
+
+ /* If we have fragments that add up to what we need, combine them */
+ /* FIXME - it would be faster to revise the processing code to handle
+ ** multiple fragments directly. Essentially, we should be
+ ** able to do this without any slice() or combine_array_buffers() calls */
+ while (this.buffers.length > 1 && this.buffers[0].byteLength < this.needed)
+ {
+ var mb1 = this.buffers.shift();
+ var mb2 = this.buffers.shift();
+
+ this.buffers.unshift(combine_array_buffers(mb1, mb2));
+ }
+
+
+ while (this.buffers.length > 0 && this.buffers[0].byteLength >= this.needed)
+ {
+ mb = this.buffers.shift();
+ if (mb.byteLength > this.needed)
+ {
+ this.buffers.unshift(mb.slice(this.needed));
+ mb = mb.slice(0, this.needed);
+ }
+ this.callback.call(this.sc, mb,
+ this.saved_msg_header || undefined);
+ }
+
+ },
+
+ request: function(n)
+ {
+ this.needed = n;
+ },
+
+ save_header: function(h)
+ {
+ this.saved_msg_header = h;
+ },
+
+ clear_header: function()
+ {
+ this.saved_msg_header = undefined;
+ },
+}
+
+function wire_blob_catcher(e)
+{
+ DEBUG > 1 && console.log(">> WebSockets.onmessage");
+ DEBUG > 1 && console.log("id " + this.wire_reader.sc.connection_id +"; type " + this.wire_reader.sc.type);
+ SpiceWireReader.prototype.inbound.call(this.wire_reader, e.data);
+}
diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js
index fbcf4a2..a18288f 100644
--- a/ui/js/src/kimchi.api.js
+++ b/ui/js/src/kimchi.api.js
@@ -309,6 +309,27 @@ var kimchi = {
});
},
+ spiceToVM : function(vm) {
+ kimchi.requestJSON({
+ url : '/config',
+ type : 'GET',
+ dataType : 'json'
+ }).done(function(data, textStatus, xhr) {
+ http_port = data['http_port'];
+ kimchi.requestJSON({
+ url : "/vms/" + encodeURIComponent(vm) + "/connect",
+ type : "POST",
+ dataType : "json"
+ }).done(function(data, textStatus, xhr) {
+ url = 'http://' + location.hostname + ':' + http_port;
+ url += "/spice.html?port=" + data.graphics.port + "&listen=" + data.graphics.listen;
+ window.open(url);
+ });
+ }).error(function() {
+ kimchi.message.error(i18n['msg.fail.get.config']);
+ });
+ },
+
listVMs : function(suc, err) {
kimchi.requestJSON({
url : kimchi.url + 'vms',
diff --git a/ui/js/src/kimchi.guest_main.js b/ui/js/src/kimchi.guest_main.js
index a36c59c..bff5d8f 100644
--- a/ui/js/src/kimchi.guest_main.js
+++ b/ui/js/src/kimchi.guest_main.js
@@ -119,6 +119,10 @@ kimchi.initVmButtonsAction = function() {
kimchi.vncToVM($(this).data('vm'));
});
+ $(".vm-spice").on("click", function(event) {
+ kimchi.spiceToVM($(this).data('vm'));
+ });
+
kimchi.init_button_stat();
};
@@ -127,13 +131,25 @@ kimchi.init_button_stat = function() {
$('.vm-action').each(function() {
var vm_action = $(this);
var vm_vnc = vm_action.find('.vm-vnc');
- if ((vm_action.data('graphics') === 'vnc')
- && (vm_action.data('vmstate') === 'running')) {
- vm_vnc.removeAttr('disabled');
+ var vm_spice = vm_action.find('.vm-spice');
+ if (vm_action.data('graphics') === 'vnc') {
+ vm_spice.hide();
+ if (vm_action.data('vmstate') === 'running') {
+ vm_vnc.removeAttr('disabled');
+ } else {
+ vm_vnc.attr('disabled', 'disabled');
+ }
+ } else if (vm_action.data('graphics') === 'spice') {
+ vm_vnc.hide();
+ if (vm_action.data('vmstate') === 'running') {
+ vm_spice.removeAttr('disabled');
+ } else {
+ vm_spice.attr('disabled', 'disabled');
+ }
} else {
- vm_vnc.attr('disabled', 'disabled');
+ vm_vnc.hide();
+ vm_spice.hide();
}
-
var editButton = vm_action.find('.vm-edit');
editButton.prop('disabled', vm_action.data('vmstate') !== 'shutoff');
})
diff --git a/ui/pages/guest.html.tmpl b/ui/pages/guest.html.tmpl
index 6bd6853..6d83d57 100644
--- a/ui/pages/guest.html.tmpl
+++ b/ui/pages/guest.html.tmpl
@@ -58,6 +58,7 @@
<span class="text">$_("Actions")</span><span class="arrow"></span>
<div class="popover actionsheet right-side" style="width: 250px">
<button class="button-big vm-vnc" data-vm="{name}"><span class="text">VNC</span></button>
+ <button class="button-big vm-spice" data-vm="{name}"><span class="text">SPICE</span></button>
<button class="button-big vm-edit" data-vm="{name}"><span class="text">$_("Edit")</span></button>
<a class="button-big red vm-delete" data-vm="{name}">$_("Delete")</a>
</div>
diff --git a/ui/pages/spice.html.tmpl b/ui/pages/spice.html.tmpl
new file mode 100644
index 0000000..07e7510
--- /dev/null
+++ b/ui/pages/spice.html.tmpl
@@ -0,0 +1,138 @@
+<!--
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+
+ --------------------------------------------------
+ Spice Javascript client template.
+ Refer to main.js for more detailed information
+ --------------------------------------------------
+
+-->
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
+ <title>Spice Javascript client</title>
+ <script src="js/spice/enums.js"></script>
+ <script src="js/spice/atKeynames.js"></script>
+ <script src="js/spice/utils.js"></script>
+ <script src="js/spice/png.js"></script>
+ <script src="js/spice/lz.js"></script>
+ <script src="js/spice/quic.js"></script>
+ <script src="js/spice/bitmap.js"></script>
+ <script src="js/spice/spicedataview.js"></script>
+ <script src="js/spice/spicetype.js"></script>
+ <script src="js/spice/spicemsg.js"></script>
+ <script src="js/spice/wire.js"></script>
+ <script src="js/spice/spiceconn.js"></script>
+ <script src="js/spice/display.js"></script>
+ <script src="js/spice/main.js"></script>
+ <script src="js/spice/inputs.js"></script>
+ <script src="js/spice/cursor.js"></script>
+ <script src="js/spice/jsbn.js"></script>
+ <script src="js/spice/rsa.js"></script>
+ <script src="js/spice/prng4.js"></script>
+ <script src="js/spice/rng.js"></script>
+ <script src="js/spice/sha1.js"></script>
+ <script src="js/spice/ticket.js"></script>
+ <link rel="stylesheet" type="text/css" href="css/spice/spice.css">
+ </head>
+ <script>
+ var host = null, port = null;
+ var sc;
+ function spice_error(e) {
+ disconnect();
+ }
+
+ function connect() {
+ var host, port, password, scheme = "ws://", uri;
+ host = getParameter("listen");
+ port = getParameter("port");
+ document.getElementById("host").value = host;
+ document.getElementById("port").value = port;
+ if ((!host) || (!port)) {
+ console.log("must set host and port");
+ return;
+ }
+
+ if (sc) {
+ sc.stop();
+ }
+
+ uri = scheme + host + ":" + port;
+ try {
+ sc = new SpiceMainConn({
+ uri : uri,
+ screen_id : "spice-screen",
+ dump_id : "debug-div",
+ message_id : "message-div",
+ password : "",
+ onerror : spice_error
+ });
+ } catch (e) {
+ alert(e.toString());
+ disconnect();
+ }
+
+ }
+
+ function disconnect()
+ {
+ console.log(">> disconnect");
+ if (sc) {
+ sc.stop();
+ }
+ console.log("<< disconnect");
+ }
+
+ function getParameter(name) {
+ var paramStr = location.search;
+ if (paramStr.length == 0)
+ return null;
+ if (paramStr.charAt(0) != '?')
+ return null;
+ paramStr = unescape(paramStr);
+ paramStr = paramStr.substring(1);
+ if (paramStr.length == 0)
+ return null;
+ var params = paramStr.split('&');
+ for ( var i = 0; i < params.length; i++) {
+ var parts = params[i].split('=', 2);
+ if (parts[0] == name) {
+ if (parts.length < 2 || typeof (parts[1]) == "undefined" || parts[1] == "undefined" || parts[1] == "null")
+ return "";
+ return parts[1];
+ }
+ }
+ return null;
+ }
+ </script>
+ <body onload="connect();" onunload="disconnect();">
+ <div id="login">
+ <span class="logo">SPICE</span>
+ <label for="host">Host:</label> <input id="host" value="localhost" type="text" disabled="disabled"> <!-- localhost -->
+ <label for="port">Port:</label> <input id="port" value="5959" type="text" disabled="disabled">
+ </div>
+ <div id="spice-area">
+ <div id="spice-screen" class="spice-screen"></div>
+ </div>
+ <div id="message-div" class="spice-message"></div>
+ <div id="debug-div">
+ <!-- If DUMPXXX is turned on, dumped images will go here -->
+ </div>
+ </body>
+</html>
\ No newline at end of file
--
1.7.1
2
1
This patch is working adding a select box in create nfs pool,
when user don't want to input the server ip or name, he can
simply select "used the server I have used before" and choose
a server in a select box, this is more convinent.
Signed-off-by: zhoumeina <zhoumein(a)linux.vnet.ibm.com>
---
ui/css/theme-default/button.css | 11 +++----
ui/css/theme-default/form.css | 6 ++++
ui/css/theme-default/storage.css | 4 +-
ui/js/src/kimchi.api.js | 26 +++++++++++++++++-
ui/js/src/kimchi.storagepool_add_main.js | 42 ++++++++++++++++++++++++++++++
ui/pages/i18n.html.tmpl | 3 +-
ui/pages/storagepool-add.html.tmpl | 39 +++++++++++++++++++++------
7 files changed, 112 insertions(+), 19 deletions(-)
diff --git a/ui/css/theme-default/button.css b/ui/css/theme-default/button.css
index c7ed3f6..f99679a 100644
--- a/ui/css/theme-default/button.css
+++ b/ui/css/theme-default/button.css
@@ -247,17 +247,16 @@
.btn-select {
display: inline-block;
position: relative;
- height: 38px;
+ height: 30px;
padding-right: 20px;
margin: 5px;
vertical-align: top;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
- border-radius: 5px;
- background: #eee;
+ background: #fff;
box-shadow: -1px -1px 1px #666, 1px 1px 1px #fff, 2px 2px 2px rgba(0, 0, 0, .15) inset;
font-size: 13px;
- line-height: 38px;
+ line-height: 30px;
text-align: left;
cursor: pointer;
}
@@ -269,8 +268,8 @@
.btn-select .arrow {
position: absolute;
width: 15px;
- height: 38px;
- line-height: 38px;
+ height: 30px;
+ line-height: 30px;
top: 0;
right: 5px;
background: url(../images/theme-default/arrow-down-black.png) no-repeat center center;
diff --git a/ui/css/theme-default/form.css b/ui/css/theme-default/form.css
index c24b277..9db4bba 100644
--- a/ui/css/theme-default/form.css
+++ b/ui/css/theme-default/form.css
@@ -45,3 +45,9 @@
line-height: 30px;
padding: 0 5px;
}
+
+.text-help {
+ font-size: 12px;
+ color: #333;
+ margin: 0 0 5px 5px;
+}
diff --git a/ui/css/theme-default/storage.css b/ui/css/theme-default/storage.css
index d81dc75..ae89f1b 100644
--- a/ui/css/theme-default/storage.css
+++ b/ui/css/theme-default/storage.css
@@ -529,7 +529,7 @@
width: 300px;
display: inline-block;
vertical-align: top;
- padding: 5px 5px 5px 20px;
+ padding: 5px 5px 5px 22px;
}
.storage-type-wrapper-controls input[type="text"] {
@@ -548,7 +548,7 @@
.storage-type-wrapper-controls > .dropdown {
margin: 5px 0 0 1px;
- width: 250px;
+ width: 150px;
}
.storage-type-wrapper-controls input[type="text"][disabled] {
diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js
index fbcf4a2..46072cd 100644
--- a/ui/js/src/kimchi.api.js
+++ b/ui/js/src/kimchi.api.js
@@ -681,5 +681,29 @@ var kimchi = {
success : suc,
error : err
});
+ },
+
+ getStorageServers: function(type, suc, err) {
+ var url = kimchi.url + 'storageservers?target_type=' + type;
+ kimchi.requestJSON({
+ url : url,
+ type : 'GET',
+ contentType : 'application/json',
+ dataType : 'json',
+ success : suc,
+ error : err
+ });
+ },
+
+ getStorageTargets: function(server,type, suc, err) {
+ var url = kimchi.url + 'storageservers/' + server + '/storagetargets?type=' + type;
+ kimchi.requestJSON({
+ url : url,
+ type : 'GET',
+ contentType : 'application/json',
+ dataType : 'json',
+ success : suc,
+ error : err
+ });
}
-};
+};
\ No newline at end of file
diff --git a/ui/js/src/kimchi.storagepool_add_main.js b/ui/js/src/kimchi.storagepool_add_main.js
index b31610a..504ba01 100644
--- a/ui/js/src/kimchi.storagepool_add_main.js
+++ b/ui/js/src/kimchi.storagepool_add_main.js
@@ -52,6 +52,22 @@ kimchi.initStorageAddPage = function() {
$('.host-partition').html(listHtml);
}
kimchi.select('storagePool-list', options);
+ kimchi.getStorageServers('netfs', function(data) {
+ var serverContent = [];
+ serverContent.push({
+ label : i18n['select_default'],
+ value : ''
+ });
+ if (data.length > 0) {
+ $.each(data, function(index, value) {
+ serverContent.push({
+ label : value,
+ value : value
+ });
+ });
+ }
+ kimchi.select('nfs-server-used', serverContent);
+ });
$('#poolType').change(function() {
if ($(this).val() === 'dir') {
$('.path-section').removeClass('tmpl-html');
@@ -67,6 +83,32 @@ kimchi.initStorageAddPage = function() {
$('.nfs-section').addClass('tmpl-html');
}
});
+ $('input[name=nfsServerType]').change(function() {
+ if ($(this).val() === 'input') {
+ $('#nfsServerInputDiv').removeClass('tmpl-html');
+ $('#nfsServerChooseDiv').addClass('tmpl-html');
+ } else {
+ $('#nfsServerInputDiv').addClass('tmpl-html');
+ $('#nfsServerChooseDiv').removeClass('tmpl-html');
+ }
+ });
+ $('#nfsServerSelect').change(function() {
+ $('#nfsserverId').val($(this).val());
+ });
+ $('#nfsserverId').blur(function() {
+ kimchi.getStorageTargets($(this).val(),'netfs', function(data) {
+ var serverContent = [];
+ if (data.length > 0) {
+ $.each(data, function(index, value) {
+ serverContent.push({
+ label : value,
+ value : value
+ });
+ });
+ }
+ kimchi.select('nfs-server-target', serverContent);
+ });
+ });
});
};
diff --git a/ui/pages/i18n.html.tmpl b/ui/pages/i18n.html.tmpl
index c1fc3d1..45c301d 100644
--- a/ui/pages/i18n.html.tmpl
+++ b/ui/pages/i18n.html.tmpl
@@ -121,7 +121,8 @@ var i18n = {
'action_create': "$_("Create")",
'msg_warning': "$_("Warning")",
'msg.logicalpool.confirm.delete': "$_("It will format your disk and you will loose any data in"
- " there, are you sure to continue? ")"
+ " there, are you sure to continue? ")",
+ 'select_default': "$_("Please choose")",
};
</script>
</body>
diff --git a/ui/pages/storagepool-add.html.tmpl b/ui/pages/storagepool-add.html.tmpl
index 5a2dd45..04354ee 100644
--- a/ui/pages/storagepool-add.html.tmpl
+++ b/ui/pages/storagepool-add.html.tmpl
@@ -27,7 +27,7 @@
<!DOCTYPE html>
<html>
<body>
- <div class="window" style="width: 600px; height: 600px;">
+ <div class="window" style="width: 800px; height: 600px;">
<header>
<h1 class="title">$_("Define a New Storage Pool")</h1>
<div class="close">X</div>
@@ -47,7 +47,7 @@
<section class="form-section">
<h2>2. $_("Storage Pool Type")</h2>
<div class="storage-type-wrapper-controls">
- <div class="btn dropdown popable">
+ <div class="btn-select dropdown popable">
<input id="poolType" name="type" type="hidden" value="dir"/>
<span class="text" id="pool-type-label"></span><span class="arrow"></span>
<div class="popover" style="width: 100%">
@@ -74,22 +74,43 @@
<section class="form-section">
<h2>3. $_("NFS server IP")</h2>
<div class="field">
+ <input type="radio" id="nfsServerInput" value="input" name="nfsServerType" checked="true">
+ <label>$_("I want to input the server myself.")</label>
+ <input type="radio" id="nfsServerChoose" value="choose" name="nfsServerType">
+ <label>$_("I want to choose a server I used before.")</label>
+ </div>
+ <div id="nfsServerInputDiv" class="field">
<p class="text-help">
$_("NFS server IP or hostname. It should not be empty.")</p>
<input id="nfsserverId" type="text" class="text"
style="width: 300px">
</div>
+ <div id="nfsServerChooseDiv" class="field tmpl-html" style="overflow:visible">
+ <p class="text-help">
+ $_("Please choose the nfs server you want to create storage pool.")</p>
+ <div class="btn-select dropdown popable" style="width: 285px">
+ <input id="nfsServerSelect" type="hidden"/>
+ <span class="text" id="nfs-server-label"></span><span class="arrow"></span>
+ <div class="popover" style="width: 100%">
+ <ul class="select-list" id="nfs-server-used" data-target="nfsServerSelect" data-label="nfs-server-label">
+ </ul>
+ </div>
+ </div>
+ </div>
</section>
<section class="form-section">
<h2>4. $_("NFS Path")</h2>
- <div class="field">
+ <div class="field" style="overflow:visible">
<p class="text-help">$_("The nfs exported path on nfs server")</p>
- <input id="nfspathId" type="text" class="text"
- style="width: 300px">
- <input type="hidden" id="localpathId" class="text"
- value="none">
- </div>
- <div class="clear"></div>
+ <div class="btn-select dropdown popable" style="width: 285px">
+ <input id="nfspathId" class="text" style="width:293px;"/>
+ <span class="text" id="nfs-target-label"></span><span class="arrow"></span>
+ <div class="popover" style="width: 100%">
+ <ul class="select-list" id="nfs-server-target" data-target="nfspathId" data-label="nfs-target-label">
+ </ul>
+ </div>
+ </div>
+ </div>
</section>
</div>
<div class="logical-section tmpl-html">
--
1.7.1
2
1
This patch is working adding a select box in create nfs pool,
when user don't want to input the server ip or name, he can
simply select "used the server I have used before" and choose
a server in a select box, this is more convinent.
zhoumeina (2):
Add the nfs server select UI
Add the translate messages
po/en_US.po | 16 +++++++++++++-
po/kimchi.pot | 15 ++++++++++++-
po/pt_BR.po | 18 ++++++++++++++--
po/zh_CN.po | 16 +++++++++++++-
ui/css/theme-default/storage.css | 2 +-
ui/js/src/kimchi.api.js | 2 +-
ui/js/src/kimchi.storagepool_add_main.js | 32 ++++++++++++++++++-----------
ui/pages/i18n.html.tmpl | 3 +-
ui/pages/storagepool-add.html.tmpl | 17 +++++++--------
9 files changed, 88 insertions(+), 33 deletions(-)
2
1
This patch is working adding a select box in create nfs pool,
when user don't want to input the server ip or name, he can
simply select "used the server I have used before" and choose
a server in a select box, this is more convinent.
Signed-off-by: zhoumeina <zhoumein(a)linux.vnet.ibm.com>
---
ui/css/theme-default/storage.css | 2 +-
ui/js/src/kimchi.api.js | 2 +-
ui/js/src/kimchi.storagepool_add_main.js | 32 ++++++++++++++++++-----------
ui/pages/i18n.html.tmpl | 3 +-
ui/pages/storagepool-add.html.tmpl | 17 +++++++--------
5 files changed, 32 insertions(+), 24 deletions(-)
diff --git a/ui/css/theme-default/storage.css b/ui/css/theme-default/storage.css
index 7bb685f..ae89f1b 100644
--- a/ui/css/theme-default/storage.css
+++ b/ui/css/theme-default/storage.css
@@ -548,7 +548,7 @@
.storage-type-wrapper-controls > .dropdown {
margin: 5px 0 0 1px;
- width: 100px;
+ width: 150px;
}
.storage-type-wrapper-controls input[type="text"][disabled] {
diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js
index 0461b30..3af1673 100644
--- a/ui/js/src/kimchi.api.js
+++ b/ui/js/src/kimchi.api.js
@@ -684,7 +684,7 @@ var kimchi = {
},
getStorageServers: function(type, suc, err) {
- var url = kimchi.url + 'storageservers?type=' + type;
+ var url = kimchi.url + 'storageservers?target_type=' + type;
kimchi.requestJSON({
url : url,
type : 'GET',
diff --git a/ui/js/src/kimchi.storagepool_add_main.js b/ui/js/src/kimchi.storagepool_add_main.js
index 42c6bf5..5cd5b15 100644
--- a/ui/js/src/kimchi.storagepool_add_main.js
+++ b/ui/js/src/kimchi.storagepool_add_main.js
@@ -52,6 +52,23 @@ kimchi.initStorageAddPage = function() {
$('.host-partition').html(listHtml);
}
kimchi.select('storagePool-list', options);
+ kimchi.getStorageServers('netfs', function(data) {
+ console.log($("#nfsServerSelect").val());
+ var serverContent = [];
+ serverContent.push({
+ label : i18n['select_default'],
+ value : ''
+ });
+ if (data.length > 0) {
+ $.each(data, function(index, value) {
+ serverContent.push({
+ label : value,
+ value : value
+ });
+ });
+ }
+ kimchi.select('nfs-server-used', serverContent);
+ });
$('#poolType').change(function() {
if ($(this).val() === 'dir') {
$('.path-section').removeClass('tmpl-html');
@@ -74,20 +91,11 @@ kimchi.initStorageAddPage = function() {
} else {
$('#nfsServerInputDiv').addClass('tmpl-html');
$('#nfsServerChooseDiv').removeClass('tmpl-html');
- kimchi.getStorageServers('netfs', function(data) {
- var serverContent = [];
- if (data.length > 0) {
- $.each(data, function(index, value) {
- serverContent.push({
- label : data.value,
- value : data.value
- });
- });
- }
- kimchi.select('nfs-server-used', serverContent);
- });
}
});
+ $('#nfsServerSelect').change(function() {
+ $('#nfsserverId').val($(this).val());
+ });
});
};
diff --git a/ui/pages/i18n.html.tmpl b/ui/pages/i18n.html.tmpl
index c1fc3d1..45c301d 100644
--- a/ui/pages/i18n.html.tmpl
+++ b/ui/pages/i18n.html.tmpl
@@ -121,7 +121,8 @@ var i18n = {
'action_create': "$_("Create")",
'msg_warning': "$_("Warning")",
'msg.logicalpool.confirm.delete': "$_("It will format your disk and you will loose any data in"
- " there, are you sure to continue? ")"
+ " there, are you sure to continue? ")",
+ 'select_default': "$_("Please choose")",
};
</script>
</body>
diff --git a/ui/pages/storagepool-add.html.tmpl b/ui/pages/storagepool-add.html.tmpl
index 9d16ae6..31af028 100644
--- a/ui/pages/storagepool-add.html.tmpl
+++ b/ui/pages/storagepool-add.html.tmpl
@@ -83,19 +83,18 @@
<input id="nfsserverId" type="text" class="text"
style="width: 300px">
</div>
- <div id="nfsServerChooseDiv" class="tmpl-html">
+ <div id="nfsServerChooseDiv" class="field tmpl-html" style="overflow:visible">
<p class="text-help">
$_("Please choose the nfs server you want to create storage pool.")</p>
- <div class="storage-type-wrapper-controls">
- <div class="btn-select dropdown popable">
- <span class="text" id="nfs-server-label"></span><span class="arrow"></span>
- <div class="popover" style="width: 100%">
- <ul class="select-list" id="nfs-server-used" data-target="nfsServerSelect" data-label="nfs-server-label">
- </ul>
+ <div class="btn-select dropdown popable" style="width: 285px">
+ <input id="nfsServerSelect" type="hidden"/>
+ <span class="text" id="nfs-server-label"></span><span class="arrow"></span>
+ <div class="popover" style="width: 100%">
+ <ul class="select-list" id="nfs-server-used" data-target="nfsServerSelect" data-label="nfs-server-label">
+ </ul>
+ </div>
</div>
</div>
- </div>
- </div>
</section>
<section class="form-section">
<h2>4. $_("NFS Path")</h2>
--
1.7.1
3
2
This patch add the front end support of spice.
1 If there were a spice vm in host, show a "spice" button
but not "vnc" in guest page.
2 click "spice" we can show a screen just like the demo
http://www.spice-space.org/spice-html5/spice.html
Signed-off-by: zhoumeina <zhoumein(a)linux.vnet.ibm.com>
---
ui/js/spice/atKeynames.js | 183 ++++++
ui/js/spice/bitmap.js | 51 ++
ui/js/spice/cursor.js | 92 +++
ui/js/spice/display.js | 806 ++++++++++++++++++++++++
ui/js/spice/enums.js | 282 +++++++++
ui/js/spice/inputs.js | 251 ++++++++
ui/js/spice/jsbn.js | 589 ++++++++++++++++++
ui/js/spice/lz.js | 166 +++++
ui/js/spice/main.js | 176 ++++++
ui/js/spice/png.js | 256 ++++++++
ui/js/spice/prng4.js | 79 +++
ui/js/spice/quic.js | 1335 ++++++++++++++++++++++++++++++++++++++++
ui/js/spice/rng.js | 102 +++
ui/js/spice/rsa.js | 146 +++++
ui/js/spice/sha1.js | 346 +++++++++++
ui/js/spice/spiceconn.js | 447 ++++++++++++++
ui/js/spice/spicedataview.js | 96 +++
ui/js/spice/spicemsg.js | 883 ++++++++++++++++++++++++++
ui/js/spice/spicetype.js | 480 +++++++++++++++
ui/js/spice/ticket.js | 250 ++++++++
ui/js/spice/utils.js | 261 ++++++++
ui/js/spice/wire.js | 123 ++++
ui/js/src/kimchi.api.js | 21 +
ui/js/src/kimchi.guest_main.js | 26 +-
ui/pages/guest.html.tmpl | 1 +
25 files changed, 7443 insertions(+), 5 deletions(-)
create mode 100644 ui/js/spice/atKeynames.js
create mode 100644 ui/js/spice/bitmap.js
create mode 100644 ui/js/spice/cursor.js
create mode 100644 ui/js/spice/display.js
create mode 100644 ui/js/spice/enums.js
create mode 100644 ui/js/spice/inputs.js
create mode 100644 ui/js/spice/jsbn.js
create mode 100644 ui/js/spice/lz.js
create mode 100644 ui/js/spice/main.js
create mode 100644 ui/js/spice/png.js
create mode 100644 ui/js/spice/prng4.js
create mode 100644 ui/js/spice/quic.js
create mode 100644 ui/js/spice/rng.js
create mode 100644 ui/js/spice/rsa.js
create mode 100644 ui/js/spice/sha1.js
create mode 100644 ui/js/spice/spiceconn.js
create mode 100644 ui/js/spice/spicedataview.js
create mode 100644 ui/js/spice/spicemsg.js
create mode 100644 ui/js/spice/spicetype.js
create mode 100644 ui/js/spice/ticket.js
create mode 100644 ui/js/spice/utils.js
create mode 100644 ui/js/spice/wire.js
diff --git a/ui/js/spice/atKeynames.js b/ui/js/spice/atKeynames.js
new file mode 100644
index 0000000..e1e27fd
--- /dev/null
+++ b/ui/js/spice/atKeynames.js
@@ -0,0 +1,183 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Aric Stewart <aric(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+/*
+ * Copyright 1990,91 by Thomas Roell, Dinkelscherben, Germany.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of Thomas Roell not be used in
+ * advertising or publicity pertaining to distribution of the software without
+ * specific, written prior permission. Thomas Roell makes no representations
+ * about the suitability of this software for any purpose. It is provided
+ * "as is" without express or implied warranty.
+ *
+ * THOMAS ROELL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THOMAS ROELL BE LIABLE FOR ANY SPECIAL, 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.
+ *
+ */
+/*
+ * Copyright (c) 1994-2003 by The XFree86 Project, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Except as contained in this notice, the name of the copyright holder(s)
+ * and author(s) shall not be used in advertising or otherwise to promote
+ * the sale, use or other dealings in this Software without prior written
+ * authorization from the copyright holder(s) and author(s).
+ */
+
+/*
+ * NOTE: The AT/MF keyboards can generate (via the 8042) two (MF: three)
+ * sets of scancodes. Set3 can only be generated by a MF keyboard.
+ * Set2 sends a makecode for keypress, and the same code prefixed by a
+ * F0 for keyrelease. This is a little bit ugly to handle. Thus we use
+ * here for X386 the PC/XT compatible Set1. This set uses 8bit scancodes.
+ * Bit 7 ist set if the key is released. The code E0 switches to a
+ * different meaning to add the new MF cursorkeys, while not breaking old
+ * applications. E1 is another special prefix. Since I assume that there
+ * will be further versions of PC/XT scancode compatible keyboards, we
+ * may be in trouble one day.
+ *
+ * IDEA: 1) Use Set2 on AT84 keyboards and translate it to MF Set3.
+ * 2) Use the keyboards native set and translate it to common keysyms.
+ */
+
+/*
+ * definition of the AT84/MF101/MF102 Keyboard:
+ * ============================================================
+ * Defined Key Cap Glyphs Pressed value
+ * Key Name Main Also (hex) (dec)
+ * ---------------- ---------- ------- ------ ------
+ */
+
+var KEY_Escape =/* Escape 0x01 */ 1
+var KEY_1 =/* 1 ! 0x02 */ 2
+var KEY_2 =/* 2 @ 0x03 */ 3
+var KEY_3 =/* 3 # 0x04 */ 4
+var KEY_4 =/* 4 $ 0x05 */ 5
+var KEY_5 =/* 5 % 0x06 */ 6
+var KEY_6 =/* 6 ^ 0x07 */ 7
+var KEY_7 =/* 7 & 0x08 */ 8
+var KEY_8 =/* 8 * 0x09 */ 9
+var KEY_9 =/* 9 ( 0x0a */ 10
+var KEY_0 =/* 0 ) 0x0b */ 11
+var KEY_Minus =/* - (Minus) _ (Under) 0x0c */ 12
+var KEY_Equal =/* = (Equal) + 0x0d */ 13
+var KEY_BackSpace =/* Back Space 0x0e */ 14
+var KEY_Tab =/* Tab 0x0f */ 15
+var KEY_Q =/* Q 0x10 */ 16
+var KEY_W =/* W 0x11 */ 17
+var KEY_E =/* E 0x12 */ 18
+var KEY_R =/* R 0x13 */ 19
+var KEY_T =/* T 0x14 */ 20
+var KEY_Y =/* Y 0x15 */ 21
+var KEY_U =/* U 0x16 */ 22
+var KEY_I =/* I 0x17 */ 23
+var KEY_O =/* O 0x18 */ 24
+var KEY_P =/* P 0x19 */ 25
+var KEY_LBrace =/* [ { 0x1a */ 26
+var KEY_RBrace =/* ] } 0x1b */ 27
+var KEY_Enter =/* Enter 0x1c */ 28
+var KEY_LCtrl =/* Ctrl(left) 0x1d */ 29
+var KEY_A =/* A 0x1e */ 30
+var KEY_S =/* S 0x1f */ 31
+var KEY_D =/* D 0x20 */ 32
+var KEY_F =/* F 0x21 */ 33
+var KEY_G =/* G 0x22 */ 34
+var KEY_H =/* H 0x23 */ 35
+var KEY_J =/* J 0x24 */ 36
+var KEY_K =/* K 0x25 */ 37
+var KEY_L =/* L 0x26 */ 38
+var KEY_SemiColon =/* ;(SemiColon) :(Colon) 0x27 */ 39
+var KEY_Quote =/* ' (Apostr) " (Quote) 0x28 */ 40
+var KEY_Tilde =/* ` (Accent) ~ (Tilde) 0x29 */ 41
+var KEY_ShiftL =/* Shift(left) 0x2a */ 42
+var KEY_BSlash =/* \(BckSlash) |(VertBar)0x2b */ 43
+var KEY_Z =/* Z 0x2c */ 44
+var KEY_X =/* X 0x2d */ 45
+var KEY_C =/* C 0x2e */ 46
+var KEY_V =/* V 0x2f */ 47
+var KEY_B =/* B 0x30 */ 48
+var KEY_N =/* N 0x31 */ 49
+var KEY_M =/* M 0x32 */ 50
+var KEY_Comma =/* , (Comma) < (Less) 0x33 */ 51
+var KEY_Period =/* . (Period) >(Greater)0x34 */ 52
+var KEY_Slash =/* / (Slash) ? 0x35 */ 53
+var KEY_ShiftR =/* Shift(right) 0x36 */ 54
+var KEY_KP_Multiply =/* * 0x37 */ 55
+var KEY_Alt =/* Alt(left) 0x38 */ 56
+var KEY_Space =/* (SpaceBar) 0x39 */ 57
+var KEY_CapsLock =/* CapsLock 0x3a */ 58
+var KEY_F1 =/* F1 0x3b */ 59
+var KEY_F2 =/* F2 0x3c */ 60
+var KEY_F3 =/* F3 0x3d */ 61
+var KEY_F4 =/* F4 0x3e */ 62
+var KEY_F5 =/* F5 0x3f */ 63
+var KEY_F6 =/* F6 0x40 */ 64
+var KEY_F7 =/* F7 0x41 */ 65
+var KEY_F8 =/* F8 0x42 */ 66
+var KEY_F9 =/* F9 0x43 */ 67
+var KEY_F10 =/* F10 0x44 */ 68
+var KEY_NumLock =/* NumLock 0x45 */ 69
+var KEY_ScrollLock =/* ScrollLock 0x46 */ 70
+var KEY_KP_7 =/* 7 Home 0x47 */ 71
+var KEY_KP_8 =/* 8 Up 0x48 */ 72
+var KEY_KP_9 =/* 9 PgUp 0x49 */ 73
+var KEY_KP_Minus =/* - (Minus) 0x4a */ 74
+var KEY_KP_4 =/* 4 Left 0x4b */ 75
+var KEY_KP_5 =/* 5 0x4c */ 76
+var KEY_KP_6 =/* 6 Right 0x4d */ 77
+var KEY_KP_Plus =/* + (Plus) 0x4e */ 78
+var KEY_KP_1 =/* 1 End 0x4f */ 79
+var KEY_KP_2 =/* 2 Down 0x50 */ 80
+var KEY_KP_3 =/* 3 PgDown 0x51 */ 81
+var KEY_KP_0 =/* 0 Insert 0x52 */ 82
+var KEY_KP_Decimal =/* . (Decimal) Delete 0x53 */ 83
+var KEY_SysReqest =/* SysReqest 0x54 */ 84
+ /* NOTUSED 0x55 */
+var KEY_Less =/* < (Less) >(Greater) 0x56 */ 86
+var KEY_F11 =/* F11 0x57 */ 87
+var KEY_F12 =/* F12 0x58 */ 88
+
+var KEY_Prefix0 =/* special 0x60 */ 96
+var KEY_Prefix1 =/* specail 0x61 */ 97
diff --git a/ui/js/spice/bitmap.js b/ui/js/spice/bitmap.js
new file mode 100644
index 0000000..03f5127
--- /dev/null
+++ b/ui/js/spice/bitmap.js
@@ -0,0 +1,51 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+/*----------------------------------------------------------------------------
+** bitmap.js
+** Handle SPICE_IMAGE_TYPE_BITMAP
+**--------------------------------------------------------------------------*/
+function convert_spice_bitmap_to_web(context, spice_bitmap)
+{
+ var ret;
+ var offset, x;
+ var u8 = new Uint8Array(spice_bitmap.data);
+ if (spice_bitmap.format != SPICE_BITMAP_FMT_32BIT &&
+ spice_bitmap.format != SPICE_BITMAP_FMT_RGBA)
+ return undefined;
+
+ ret = context.createImageData(spice_bitmap.x, spice_bitmap.y);
+ for (offset = 0; offset < (spice_bitmap.y * spice_bitmap.stride); )
+ for (x = 0; x < spice_bitmap.x; x++, offset += 4)
+ {
+ ret.data[offset + 0 ] = u8[offset + 2];
+ ret.data[offset + 1 ] = u8[offset + 1];
+ ret.data[offset + 2 ] = u8[offset + 0];
+
+ // FIXME - We effectively treat all images as having SPICE_IMAGE_FLAGS_HIGH_BITS_SET
+ if (spice_bitmap.format == SPICE_BITMAP_FMT_32BIT)
+ ret.data[offset + 3] = 255;
+ else
+ ret.data[offset + 3] = u8[offset];
+ }
+
+ return ret;
+}
diff --git a/ui/js/spice/cursor.js b/ui/js/spice/cursor.js
new file mode 100644
index 0000000..b3184c3
--- /dev/null
+++ b/ui/js/spice/cursor.js
@@ -0,0 +1,92 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+/*----------------------------------------------------------------------------
+** SpiceCursorConn
+** Drive the Spice Cursor Channel
+**--------------------------------------------------------------------------*/
+function SpiceCursorConn()
+{
+ SpiceConn.apply(this, arguments);
+}
+
+SpiceCursorConn.prototype = Object.create(SpiceConn.prototype);
+SpiceCursorConn.prototype.process_channel_message = function(msg)
+{
+ if (msg.type == SPICE_MSG_CURSOR_INIT)
+ {
+ var cursor_init = new SpiceMsgCursorInit(msg.data);
+ DEBUG > 1 && console.log("SpiceMsgCursorInit");
+ if (this.parent && this.parent.inputs &&
+ this.parent.inputs.mouse_mode == SPICE_MOUSE_MODE_SERVER)
+ {
+ // FIXME - this imagines that the server actually
+ // provides the current cursor position,
+ // instead of 0,0. As of May 11, 2012,
+ // that assumption was false :-(.
+ this.parent.inputs.mousex = cursor_init.position.x;
+ this.parent.inputs.mousey = cursor_init.position.y;
+ }
+ // FIXME - We don't handle most of the parameters here...
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_CURSOR_SET)
+ {
+ var cursor_set = new SpiceMsgCursorSet(msg.data);
+ DEBUG > 1 && console.log("SpiceMsgCursorSet");
+ if (cursor_set.flags & SPICE_CURSOR_FLAGS_NONE)
+ {
+ document.getElementById(this.parent.screen_id).style.cursor = "none";
+ return true;
+ }
+
+ if (cursor_set.flags > 0)
+ this.log_warn("FIXME: No support for cursor flags " + cursor_set.flags);
+
+ if (cursor_set.cursor.header.type != SPICE_CURSOR_TYPE_ALPHA)
+ {
+ this.log_warn("FIXME: No support for cursor type " + cursor_set.cursor.header.type);
+ return false;
+ }
+
+ this.set_cursor(cursor_set.cursor);
+
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_CURSOR_HIDE)
+ {
+ DEBUG > 1 && console.log("SpiceMsgCursorHide");
+ document.getElementById(this.parent.screen_id).style.cursor = "none";
+ return true;
+ }
+
+ return false;
+}
+
+SpiceCursorConn.prototype.set_cursor = function(cursor)
+{
+ var pngstr = create_rgba_png(cursor.header.height, cursor.header.width, cursor.data);
+ var curstr = 'url(data:image/png,' + pngstr + ') ' +
+ cursor.header.hot_spot_x + ' ' + cursor.header.hot_spot_y + ", default";
+ document.getElementById(this.parent.screen_id).style.cursor = curstr;
+}
diff --git a/ui/js/spice/display.js b/ui/js/spice/display.js
new file mode 100644
index 0000000..fdf9dc5
--- /dev/null
+++ b/ui/js/spice/display.js
@@ -0,0 +1,806 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+/*----------------------------------------------------------------------------
+** FIXME: putImageData does not support Alpha blending
+** or compositing. So if we have data in an ImageData
+** format, we have to draw it onto a context,
+** and then use drawImage to put it onto the target,
+** as drawImage does alpha.
+**--------------------------------------------------------------------------*/
+function putImageDataWithAlpha(context, d, x, y)
+{
+ var c = document.createElement("canvas");
+ var t = c.getContext("2d");
+ c.setAttribute('width', d.width);
+ c.setAttribute('height', d.height);
+ t.putImageData(d, 0, 0);
+ context.drawImage(c, x, y, d.width, d.height);
+}
+
+/*----------------------------------------------------------------------------
+** FIXME: Spice will send an image with '0' alpha when it is intended to
+** go on a surface w/no alpha. So in that case, we have to strip
+** out the alpha. The test case for this was flux box; in a Xspice
+** server, right click on the desktop to get the menu; the top bar
+** doesn't paint/highlight correctly w/out this change.
+**--------------------------------------------------------------------------*/
+function stripAlpha(d)
+{
+ var i;
+ for (i = 0; i < (d.width * d.height * 4); i += 4)
+ d.data[i + 3] = 255;
+}
+
+/*----------------------------------------------------------------------------
+** SpiceDisplayConn
+** Drive the Spice Display Channel
+**--------------------------------------------------------------------------*/
+function SpiceDisplayConn()
+{
+ SpiceConn.apply(this, arguments);
+}
+
+SpiceDisplayConn.prototype = Object.create(SpiceConn.prototype);
+SpiceDisplayConn.prototype.process_channel_message = function(msg)
+{
+ if (msg.type == SPICE_MSG_DISPLAY_MARK)
+ {
+ // FIXME - DISPLAY_MARK not implemented (may be hard or impossible)
+ this.known_unimplemented(msg.type, "Display Mark");
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_RESET)
+ {
+ DEBUG > 2 && console.log("Display reset");
+ this.surfaces[this.primary_surface].canvas.context.restore();
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_DRAW_COPY)
+ {
+ var draw_copy = new SpiceMsgDisplayDrawCopy(msg.data);
+
+ DEBUG > 1 && this.log_draw("DrawCopy", draw_copy);
+
+ if (! draw_copy.base.box.is_same_size(draw_copy.data.src_area))
+ this.log_warn("FIXME: DrawCopy src_area is a different size than base.box; we do not handle that yet.");
+ if (draw_copy.base.clip.type != SPICE_CLIP_TYPE_NONE)
+ this.log_warn("FIXME: DrawCopy we don't handle clipping yet");
+ if (draw_copy.data.rop_descriptor != SPICE_ROPD_OP_PUT)
+ this.log_warn("FIXME: DrawCopy we don't handle ropd type: " + draw_copy.data.rop_descriptor);
+ if (draw_copy.data.mask.flags)
+ this.log_warn("FIXME: DrawCopy we don't handle mask flag: " + draw_copy.data.mask.flags);
+ if (draw_copy.data.mask.bitmap)
+ this.log_warn("FIXME: DrawCopy we don't handle mask");
+
+ if (draw_copy.data && draw_copy.data.src_bitmap)
+ {
+ if (draw_copy.data.src_bitmap.descriptor.flags &&
+ draw_copy.data.src_bitmap.descriptor.flags != SPICE_IMAGE_FLAGS_CACHE_ME &&
+ draw_copy.data.src_bitmap.descriptor.flags != SPICE_IMAGE_FLAGS_HIGH_BITS_SET)
+ {
+ this.log_warn("FIXME: DrawCopy unhandled image flags: " + draw_copy.data.src_bitmap.descriptor.flags);
+ DEBUG <= 1 && this.log_draw("DrawCopy", draw_copy);
+ }
+
+ if (draw_copy.data.src_bitmap.descriptor.type == SPICE_IMAGE_TYPE_QUIC)
+ {
+ var canvas = this.surfaces[draw_copy.base.surface_id].canvas;
+ if (! draw_copy.data.src_bitmap.quic)
+ {
+ this.log_warn("FIXME: DrawCopy could not handle this QUIC file.");
+ return false;
+ }
+ var source_img = convert_spice_quic_to_web(canvas.context,
+ draw_copy.data.src_bitmap.quic);
+
+ return this.draw_copy_helper(
+ { base: draw_copy.base,
+ src_area: draw_copy.data.src_area,
+ image_data: source_img,
+ tag: "copyquic." + draw_copy.data.src_bitmap.quic.type,
+ has_alpha: (draw_copy.data.src_bitmap.quic.type == QUIC_IMAGE_TYPE_RGBA ? true : false) ,
+ descriptor : draw_copy.data.src_bitmap.descriptor
+ });
+ }
+ else if (draw_copy.data.src_bitmap.descriptor.type == SPICE_IMAGE_TYPE_FROM_CACHE ||
+ draw_copy.data.src_bitmap.descriptor.type == SPICE_IMAGE_TYPE_FROM_CACHE_LOSSLESS)
+ {
+ if (! this.cache || ! this.cache[draw_copy.data.src_bitmap.descriptor.id])
+ {
+ this.log_warn("FIXME: DrawCopy did not find image id " + draw_copy.data.src_bitmap.descriptor.id + " in cache.");
+ return false;
+ }
+
+ return this.draw_copy_helper(
+ { base: draw_copy.base,
+ src_area: draw_copy.data.src_area,
+ image_data: this.cache[draw_copy.data.src_bitmap.descriptor.id],
+ tag: "copycache." + draw_copy.data.src_bitmap.descriptor.id,
+ has_alpha: true, /* FIXME - may want this to be false... */
+ descriptor : draw_copy.data.src_bitmap.descriptor
+ });
+
+ /* FIXME - LOSSLESS CACHE ramifications not understood or handled */
+ }
+ else if (draw_copy.data.src_bitmap.descriptor.type == SPICE_IMAGE_TYPE_SURFACE)
+ {
+ var source_context = this.surfaces[draw_copy.data.src_bitmap.surface_id].canvas.context;
+ var target_context = this.surfaces[draw_copy.base.surface_id].canvas.context;
+
+ var source_img = source_context.getImageData(
+ draw_copy.data.src_area.left, draw_copy.data.src_area.top,
+ draw_copy.data.src_area.right - draw_copy.data.src_area.left,
+ draw_copy.data.src_area.bottom - draw_copy.data.src_area.top);
+ var computed_src_area = new SpiceRect;
+ computed_src_area.top = computed_src_area.left = 0;
+ computed_src_area.right = source_img.width;
+ computed_src_area.bottom = source_img.height;
+
+ /* FIXME - there is a potential optimization here.
+ That is, if the surface is from 0,0, and
+ both surfaces are alpha surfaces, you should
+ be able to just do a drawImage, which should
+ save time. */
+
+ return this.draw_copy_helper(
+ { base: draw_copy.base,
+ src_area: computed_src_area,
+ image_data: source_img,
+ tag: "copysurf." + draw_copy.data.src_bitmap.surface_id,
+ has_alpha: this.surfaces[draw_copy.data.src_bitmap.surface_id].format == SPICE_SURFACE_FMT_32_xRGB ? false : true,
+ descriptor : draw_copy.data.src_bitmap.descriptor
+ });
+
+ return true;
+ }
+ else if (draw_copy.data.src_bitmap.descriptor.type == SPICE_IMAGE_TYPE_JPEG)
+ {
+ if (! draw_copy.data.src_bitmap.jpeg)
+ {
+ this.log_warn("FIXME: DrawCopy could not handle this JPEG file.");
+ return false;
+ }
+
+ // FIXME - how lame is this. Be have it in binary format, and we have
+ // to put it into string to get it back into jpeg. Blech.
+ var tmpstr = "data:image/jpeg,";
+ var img = new Image;
+ var i;
+ var qdv = new Uint8Array(draw_copy.data.src_bitmap.jpeg.data);
+ for (i = 0; i < qdv.length; i++)
+ {
+ tmpstr += '%';
+ if (qdv[i] < 16)
+ tmpstr += '0';
+ tmpstr += qdv[i].toString(16);
+ }
+
+ img.o =
+ { base: draw_copy.base,
+ tag: "jpeg." + draw_copy.data.src_bitmap.surface_id,
+ descriptor : draw_copy.data.src_bitmap.descriptor,
+ sc : this,
+ };
+ img.onload = handle_draw_jpeg_onload;
+ img.src = tmpstr;
+
+ return true;
+ }
+ else if (draw_copy.data.src_bitmap.descriptor.type == SPICE_IMAGE_TYPE_JPEG_ALPHA)
+ {
+ if (! draw_copy.data.src_bitmap.jpeg_alpha)
+ {
+ this.log_warn("FIXME: DrawCopy could not handle this JPEG ALPHA file.");
+ return false;
+ }
+
+ // FIXME - how lame is this. Be have it in binary format, and we have
+ // to put it into string to get it back into jpeg. Blech.
+ var tmpstr = "data:image/jpeg,";
+ var img = new Image;
+ var i;
+ var qdv = new Uint8Array(draw_copy.data.src_bitmap.jpeg_alpha.data);
+ for (i = 0; i < qdv.length; i++)
+ {
+ tmpstr += '%';
+ if (qdv[i] < 16)
+ tmpstr += '0';
+ tmpstr += qdv[i].toString(16);
+ }
+
+ img.o =
+ { base: draw_copy.base,
+ tag: "jpeg." + draw_copy.data.src_bitmap.surface_id,
+ descriptor : draw_copy.data.src_bitmap.descriptor,
+ sc : this,
+ };
+
+ if (this.surfaces[draw_copy.base.surface_id].format == SPICE_SURFACE_FMT_32_ARGB)
+ {
+
+ var canvas = this.surfaces[draw_copy.base.surface_id].canvas;
+ img.alpha_img = convert_spice_lz_to_web(canvas.context,
+ draw_copy.data.src_bitmap.jpeg_alpha.alpha);
+ }
+ img.onload = handle_draw_jpeg_onload;
+ img.src = tmpstr;
+
+ return true;
+ }
+ else if (draw_copy.data.src_bitmap.descriptor.type == SPICE_IMAGE_TYPE_BITMAP)
+ {
+ var canvas = this.surfaces[draw_copy.base.surface_id].canvas;
+ if (! draw_copy.data.src_bitmap.bitmap)
+ {
+ this.log_err("null bitmap");
+ return false;
+ }
+
+ var source_img = convert_spice_bitmap_to_web(canvas.context,
+ draw_copy.data.src_bitmap.bitmap);
+ if (! source_img)
+ {
+ this.log_warn("FIXME: Unable to interpret bitmap of format: " +
+ draw_copy.data.src_bitmap.bitmap.format);
+ return false;
+ }
+
+ return this.draw_copy_helper(
+ { base: draw_copy.base,
+ src_area: draw_copy.data.src_area,
+ image_data: source_img,
+ tag: "bitmap." + draw_copy.data.src_bitmap.bitmap.format,
+ has_alpha: draw_copy.data.src_bitmap.bitmap == SPICE_BITMAP_FMT_32BIT ? false : true,
+ descriptor : draw_copy.data.src_bitmap.descriptor
+ });
+ }
+ else if (draw_copy.data.src_bitmap.descriptor.type == SPICE_IMAGE_TYPE_LZ_RGB)
+ {
+ var canvas = this.surfaces[draw_copy.base.surface_id].canvas;
+ if (! draw_copy.data.src_bitmap.lz_rgb)
+ {
+ this.log_err("null lz_rgb ");
+ return false;
+ }
+
+ if (draw_copy.data.src_bitmap.lz_rgb.top_down != 1)
+ this.log_warn("FIXME: Implement non top down support for lz_rgb");
+
+ var source_img = convert_spice_lz_to_web(canvas.context,
+ draw_copy.data.src_bitmap.lz_rgb);
+ if (! source_img)
+ {
+ this.log_warn("FIXME: Unable to interpret bitmap of type: " +
+ draw_copy.data.src_bitmap.lz_rgb.type);
+ return false;
+ }
+
+ return this.draw_copy_helper(
+ { base: draw_copy.base,
+ src_area: draw_copy.data.src_area,
+ image_data: source_img,
+ tag: "lz_rgb." + draw_copy.data.src_bitmap.lz_rgb.type,
+ has_alpha: draw_copy.data.src_bitmap.lz_rgb.type == LZ_IMAGE_TYPE_RGBA ? true : false ,
+ descriptor : draw_copy.data.src_bitmap.descriptor
+ });
+ }
+ else
+ {
+ this.log_warn("FIXME: DrawCopy unhandled image type: " + draw_copy.data.src_bitmap.descriptor.type);
+ this.log_draw("DrawCopy", draw_copy);
+ return false;
+ }
+ }
+
+ this.log_warn("FIXME: DrawCopy no src_bitmap.");
+ return false;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_DRAW_FILL)
+ {
+ var draw_fill = new SpiceMsgDisplayDrawFill(msg.data);
+
+ DEBUG > 1 && this.log_draw("DrawFill", draw_fill);
+
+ if (draw_fill.data.rop_descriptor != SPICE_ROPD_OP_PUT)
+ this.log_warn("FIXME: DrawFill we don't handle ropd type: " + draw_fill.data.rop_descriptor);
+ if (draw_fill.data.mask.flags)
+ this.log_warn("FIXME: DrawFill we don't handle mask flag: " + draw_fill.data.mask.flags);
+ if (draw_fill.data.mask.bitmap)
+ this.log_warn("FIXME: DrawFill we don't handle mask");
+
+ if (draw_fill.data.brush.type == SPICE_BRUSH_TYPE_SOLID)
+ {
+ // FIXME - do brushes ever have alpha?
+ var color = draw_fill.data.brush.color & 0xffffff;
+ var color_str = "rgb(" + (color >> 16) + ", " + ((color >> 8) & 0xff) + ", " + (color & 0xff) + ")";
+ this.surfaces[draw_fill.base.surface_id].canvas.context.fillStyle = color_str;
+
+ this.surfaces[draw_fill.base.surface_id].canvas.context.fillRect(
+ draw_fill.base.box.left, draw_fill.base.box.top,
+ draw_fill.base.box.right - draw_fill.base.box.left,
+ draw_fill.base.box.bottom - draw_fill.base.box.top);
+
+ if (DUMP_DRAWS && this.parent.dump_id)
+ {
+ var debug_canvas = document.createElement("canvas");
+ debug_canvas.setAttribute('width', this.surfaces[draw_fill.base.surface_id].canvas.width);
+ debug_canvas.setAttribute('height', this.surfaces[draw_fill.base.surface_id].canvas.height);
+ debug_canvas.setAttribute('id', "fillbrush." + draw_fill.base.surface_id + "." + this.surfaces[draw_fill.base.surface_id].draw_count);
+ debug_canvas.getContext("2d").fillStyle = color_str;
+ debug_canvas.getContext("2d").fillRect(
+ draw_fill.base.box.left, draw_fill.base.box.top,
+ draw_fill.base.box.right - draw_fill.base.box.left,
+ draw_fill.base.box.bottom - draw_fill.base.box.top);
+ document.getElementById(this.parent.dump_id).appendChild(debug_canvas);
+ }
+
+ this.surfaces[draw_fill.base.surface_id].draw_count++;
+
+ }
+ else
+ {
+ this.log_warn("FIXME: DrawFill can't handle brush type: " + draw_fill.data.brush.type);
+ }
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_COPY_BITS)
+ {
+ var copy_bits = new SpiceMsgDisplayCopyBits(msg.data);
+
+ DEBUG > 1 && this.log_draw("CopyBits", copy_bits);
+
+ var source_canvas = this.surfaces[copy_bits.base.surface_id].canvas;
+ var source_context = source_canvas.context;
+
+ var width = source_canvas.width - copy_bits.src_pos.x;
+ var height = source_canvas.height - copy_bits.src_pos.y;
+ if (width > (copy_bits.base.box.right - copy_bits.base.box.left))
+ width = copy_bits.base.box.right - copy_bits.base.box.left;
+ if (height > (copy_bits.base.box.bottom - copy_bits.base.box.top))
+ height = copy_bits.base.box.bottom - copy_bits.base.box.top;
+
+ var source_img = source_context.getImageData(
+ copy_bits.src_pos.x, copy_bits.src_pos.y, width, height);
+ //source_context.putImageData(source_img, copy_bits.base.box.left, copy_bits.base.box.top);
+ putImageDataWithAlpha(source_context, source_img, copy_bits.base.box.left, copy_bits.base.box.top);
+
+ if (DUMP_DRAWS && this.parent.dump_id)
+ {
+ var debug_canvas = document.createElement("canvas");
+ debug_canvas.setAttribute('width', width);
+ debug_canvas.setAttribute('height', height);
+ debug_canvas.setAttribute('id', "copybits" + copy_bits.base.surface_id + "." + this.surfaces[copy_bits.base.surface_id].draw_count);
+ debug_canvas.getContext("2d").putImageData(source_img, 0, 0);
+ document.getElementById(this.parent.dump_id).appendChild(debug_canvas);
+ }
+
+
+ this.surfaces[copy_bits.base.surface_id].draw_count++;
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_INVAL_ALL_PALETTES)
+ {
+ this.known_unimplemented(msg.type, "Inval All Palettes");
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_SURFACE_CREATE)
+ {
+ if (! ("surfaces" in this))
+ this.surfaces = [];
+
+ var m = new SpiceMsgSurfaceCreate(msg.data);
+ DEBUG > 1 && console.log(this.type + ": MsgSurfaceCreate id " + m.surface.surface_id
+ + "; " + m.surface.width + "x" + m.surface.height
+ + "; format " + m.surface.format
+ + "; flags " + m.surface.flags);
+ if (m.surface.format != SPICE_SURFACE_FMT_32_xRGB &&
+ m.surface.format != SPICE_SURFACE_FMT_32_ARGB)
+ {
+ this.log_warn("FIXME: cannot handle surface format " + m.surface.format + " yet.");
+ return false;
+ }
+
+ var canvas = document.createElement("canvas");
+ canvas.setAttribute('width', m.surface.width);
+ canvas.setAttribute('height', m.surface.height);
+ canvas.setAttribute('id', "spice_surface_" + m.surface.surface_id);
+ canvas.setAttribute('tabindex', m.surface.surface_id);
+ canvas.context = canvas.getContext("2d");
+
+ if (DUMP_CANVASES && this.parent.dump_id)
+ document.getElementById(this.parent.dump_id).appendChild(canvas);
+
+ m.surface.canvas = canvas;
+ m.surface.draw_count = 0;
+ this.surfaces[m.surface.surface_id] = m.surface;
+
+ if (m.surface.flags & SPICE_SURFACE_FLAGS_PRIMARY)
+ {
+ this.primary_surface = m.surface.surface_id;
+
+ /* This .save() is done entirely to enable SPICE_MSG_DISPLAY_RESET */
+ canvas.context.save();
+ document.getElementById(this.parent.screen_id).appendChild(canvas);
+ document.getElementById(this.parent.screen_id).setAttribute('width', m.surface.width);
+ document.getElementById(this.parent.screen_id).setAttribute('height', m.surface.height);
+ this.hook_events();
+ }
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_SURFACE_DESTROY)
+ {
+ var m = new SpiceMsgSurfaceDestroy(msg.data);
+ DEBUG > 1 && console.log(this.type + ": MsgSurfaceDestroy id " + m.surface_id);
+ this.delete_surface(m.surface_id);
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_STREAM_CREATE)
+ {
+ var m = new SpiceMsgDisplayStreamCreate(msg.data);
+ DEBUG > 1 && console.log(this.type + ": MsgStreamCreate id" + m.id);
+ if (!this.streams)
+ this.streams = new Array();
+ if (this.streams[m.id])
+ console.log("Stream already exists");
+ else
+ this.streams[m.id] = m;
+ if (m.codec_type != SPICE_VIDEO_CODEC_TYPE_MJPEG)
+ console.log("Unhandled stream codec: "+m.codec_type);
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_STREAM_DATA)
+ {
+ var m = new SpiceMsgDisplayStreamData(msg.data);
+ if (!this.streams[m.base.id])
+ {
+ console.log("no stream for data");
+ return false;
+ }
+ if (this.streams[m.base.id].codec_type === SPICE_VIDEO_CODEC_TYPE_MJPEG)
+ {
+ var tmpstr = "data:image/jpeg,";
+ var img = new Image;
+ var i;
+ for (i = 0; i < m.data.length; i++)
+ {
+ tmpstr += '%';
+ if (m.data[i] < 16)
+ tmpstr += '0';
+ tmpstr += m.data[i].toString(16);
+ }
+ var strm_base = new SpiceMsgDisplayBase();
+ strm_base.surface_id = this.streams[m.base.id].surface_id;
+ strm_base.box = this.streams[m.base.id].dest;
+ strm_base.clip = this.streams[m.base.id].clip;
+ img.o =
+ { base: strm_base,
+ tag: "mjpeg." + m.base.id,
+ descriptor: null,
+ sc : this,
+ };
+ img.onload = handle_draw_jpeg_onload;
+ img.src = tmpstr;
+ }
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_STREAM_CLIP)
+ {
+ var m = new SpiceMsgDisplayStreamClip(msg.data);
+ DEBUG > 1 && console.log(this.type + ": MsgStreamClip id" + m.id);
+ this.streams[m.id].clip = m.clip;
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_DISPLAY_STREAM_DESTROY)
+ {
+ var m = new SpiceMsgDisplayStreamDestroy(msg.data);
+ DEBUG > 1 && console.log(this.type + ": MsgStreamDestroy id" + m.id);
+ this.streams[m.id] = undefined;
+ return true;
+ }
+
+ return false;
+}
+
+SpiceDisplayConn.prototype.delete_surface = function(surface_id)
+{
+ var canvas = document.getElementById("spice_surface_" + surface_id);
+ if (DUMP_CANVASES && this.parent.dump_id)
+ document.getElementById(this.parent.dump_id).removeChild(canvas);
+ if (this.primary_surface == surface_id)
+ {
+ this.unhook_events();
+ this.primary_surface = undefined;
+ document.getElementById(this.parent.screen_id).removeChild(canvas);
+ }
+
+ delete this.surfaces[surface_id];
+}
+
+
+SpiceDisplayConn.prototype.draw_copy_helper = function(o)
+{
+
+ var canvas = this.surfaces[o.base.surface_id].canvas;
+ if (o.has_alpha)
+ {
+ /* FIXME - This is based on trial + error, not a serious thoughtful
+ analysis of what Spice requires. See display.js for more. */
+ if (this.surfaces[o.base.surface_id].format == SPICE_SURFACE_FMT_32_xRGB)
+ {
+ stripAlpha(o.image_data);
+ canvas.context.putImageData(o.image_data, o.base.box.left, o.base.box.top);
+ }
+ else
+ putImageDataWithAlpha(canvas.context, o.image_data,
+ o.base.box.left, o.base.box.top);
+ }
+ else
+ canvas.context.putImageData(o.image_data, o.base.box.left, o.base.box.top);
+
+ if (o.src_area.left > 0 || o.src_area.top > 0)
+ {
+ this.log_warn("FIXME: DrawCopy not shifting draw copies just yet...");
+ }
+
+ if (o.descriptor && (o.descriptor.flags & SPICE_IMAGE_FLAGS_CACHE_ME))
+ {
+ if (! ("cache" in this))
+ this.cache = [];
+ this.cache[o.descriptor.id] = o.image_data;
+ }
+
+ if (DUMP_DRAWS && this.parent.dump_id)
+ {
+ var debug_canvas = document.createElement("canvas");
+ debug_canvas.setAttribute('width', o.image_data.width);
+ debug_canvas.setAttribute('height', o.image_data.height);
+ debug_canvas.setAttribute('id', o.tag + "." +
+ this.surfaces[o.base.surface_id].draw_count + "." +
+ o.base.surface_id + "@" + o.base.box.left + "x" + o.base.box.top);
+ debug_canvas.getContext("2d").putImageData(o.image_data, 0, 0);
+ document.getElementById(this.parent.dump_id).appendChild(debug_canvas);
+ }
+
+ this.surfaces[o.base.surface_id].draw_count++;
+
+ return true;
+}
+
+
+SpiceDisplayConn.prototype.log_draw = function(prefix, draw)
+{
+ var str = prefix + "." + draw.base.surface_id + "." + this.surfaces[draw.base.surface_id].draw_count + ": ";
+ str += "base.box " + draw.base.box.left + ", " + draw.base.box.top + " to " +
+ draw.base.box.right + ", " + draw.base.box.bottom;
+ str += "; clip.type " + draw.base.clip.type;
+
+ if (draw.data)
+ {
+ if (draw.data.src_area)
+ str += "; src_area " + draw.data.src_area.left + ", " + draw.data.src_area.top + " to "
+ + draw.data.src_area.right + ", " + draw.data.src_area.bottom;
+
+ if (draw.data.src_bitmap && draw.data.src_bitmap != null)
+ {
+ str += "; src_bitmap id: " + draw.data.src_bitmap.descriptor.id;
+ str += "; src_bitmap width " + draw.data.src_bitmap.descriptor.width + ", height " + draw.data.src_bitmap.descriptor.height;
+ str += "; src_bitmap type " + draw.data.src_bitmap.descriptor.type + ", flags " + draw.data.src_bitmap.descriptor.flags;
+ if (draw.data.src_bitmap.surface_id !== undefined)
+ str += "; src_bitmap surface_id " + draw.data.src_bitmap.surface_id;
+ if (draw.data.src_bitmap.quic)
+ str += "; QUIC type " + draw.data.src_bitmap.quic.type +
+ "; width " + draw.data.src_bitmap.quic.width +
+ "; height " + draw.data.src_bitmap.quic.height ;
+ if (draw.data.src_bitmap.lz_rgb)
+ str += "; LZ_RGB length " + draw.data.src_bitmap.lz_rgb.length +
+ "; magic " + draw.data.src_bitmap.lz_rgb.magic +
+ "; version 0x" + draw.data.src_bitmap.lz_rgb.version.toString(16) +
+ "; type " + draw.data.src_bitmap.lz_rgb.type +
+ "; width " + draw.data.src_bitmap.lz_rgb.width +
+ "; height " + draw.data.src_bitmap.lz_rgb.height +
+ "; stride " + draw.data.src_bitmap.lz_rgb.stride +
+ "; top down " + draw.data.src_bitmap.lz_rgb.top_down;
+ }
+ else
+ str += "; src_bitmap is null";
+
+ if (draw.data.brush)
+ {
+ if (draw.data.brush.type == SPICE_BRUSH_TYPE_SOLID)
+ str += "; brush.color 0x" + draw.data.brush.color.toString(16);
+ if (draw.data.brush.type == SPICE_BRUSH_TYPE_PATTERN)
+ {
+ str += "; brush.pat ";
+ if (draw.data.brush.pattern.pat != null)
+ str += "[SpiceImage]";
+ else
+ str += "[null]";
+ str += " at " + draw.data.brush.pattern.pos.x + ", " + draw.data.brush.pattern.pos.y;
+ }
+ }
+
+ str += "; rop_descriptor " + draw.data.rop_descriptor;
+ if (draw.data.scale_mode !== undefined)
+ str += "; scale_mode " + draw.data.scale_mode;
+ str += "; mask.flags " + draw.data.mask.flags;
+ str += "; mask.pos " + draw.data.mask.pos.x + ", " + draw.data.mask.pos.y;
+ if (draw.data.mask.bitmap != null)
+ {
+ str += "; mask.bitmap width " + draw.data.mask.bitmap.descriptor.width + ", height " + draw.data.mask.bitmap.descriptor.height;
+ str += "; mask.bitmap type " + draw.data.mask.bitmap.descriptor.type + ", flags " + draw.data.mask.bitmap.descriptor.flags;
+ }
+ else
+ str += "; mask.bitmap is null";
+ }
+
+ console.log(str);
+}
+
+SpiceDisplayConn.prototype.hook_events = function()
+{
+ if (this.primary_surface !== undefined)
+ {
+ var canvas = this.surfaces[this.primary_surface].canvas;
+ canvas.sc = this.parent;
+ canvas.addEventListener('mousemove', handle_mousemove);
+ canvas.addEventListener('mousedown', handle_mousedown);
+ canvas.addEventListener('contextmenu', handle_contextmenu);
+ canvas.addEventListener('mouseup', handle_mouseup);
+ canvas.addEventListener('keydown', handle_keydown);
+ canvas.addEventListener('keyup', handle_keyup);
+ canvas.addEventListener('mouseout', handle_mouseout);
+ canvas.addEventListener('mouseover', handle_mouseover);
+ canvas.addEventListener('mousewheel', handle_mousewheel);
+ canvas.focus();
+ }
+}
+
+SpiceDisplayConn.prototype.unhook_events = function()
+{
+ if (this.primary_surface !== undefined)
+ {
+ var canvas = this.surfaces[this.primary_surface].canvas;
+ canvas.removeEventListener('mousemove', handle_mousemove);
+ canvas.removeEventListener('mousedown', handle_mousedown);
+ canvas.removeEventListener('contextmenu', handle_contextmenu);
+ canvas.removeEventListener('mouseup', handle_mouseup);
+ canvas.removeEventListener('keydown', handle_keydown);
+ canvas.removeEventListener('keyup', handle_keyup);
+ canvas.removeEventListener('mouseout', handle_mouseout);
+ canvas.removeEventListener('mouseover', handle_mouseover);
+ canvas.removeEventListener('mousewheel', handle_mousewheel);
+ }
+}
+
+
+SpiceDisplayConn.prototype.destroy_surfaces = function()
+{
+ for (var s in this.surfaces)
+ {
+ this.delete_surface(this.surfaces[s].surface_id);
+ }
+
+ this.surfaces = undefined;
+}
+
+
+function handle_mouseover(e)
+{
+ this.focus();
+}
+
+function handle_mouseout(e)
+{
+ this.blur();
+}
+
+function handle_draw_jpeg_onload()
+{
+ var temp_canvas = null;
+ var context;
+
+ /*------------------------------------------------------------
+ ** FIXME:
+ ** The helper should be extended to be able to handle actual HtmlImageElements
+ ** ...and the cache should be modified to do so as well
+ **----------------------------------------------------------*/
+ if (this.o.sc.surfaces[this.o.base.surface_id] === undefined)
+ {
+ // This can happen; if the jpeg image loads after our surface
+ // has been destroyed (e.g. open a menu, close it quickly),
+ // we'll find we have no surface.
+ DEBUG > 2 && this.o.sc.log_info("Discarding jpeg; presumed lost surface " + this.o.base.surface_id);
+ temp_canvas = document.createElement("canvas");
+ temp_canvas.setAttribute('width', this.o.base.box.right);
+ temp_canvas.setAttribute('height', this.o.base.box.bottom);
+ context = temp_canvas.getContext("2d");
+ }
+ else
+ context = this.o.sc.surfaces[this.o.base.surface_id].canvas.context;
+
+ if (this.alpha_img)
+ {
+ var c = document.createElement("canvas");
+ var t = c.getContext("2d");
+ c.setAttribute('width', this.alpha_img.width);
+ c.setAttribute('height', this.alpha_img.height);
+ t.putImageData(this.alpha_img, 0, 0);
+ t.globalCompositeOperation = 'source-in';
+ t.drawImage(this, 0, 0);
+
+ context.drawImage(c, this.o.base.box.left, this.o.base.box.top);
+
+ if (this.o.descriptor &&
+ (this.o.descriptor.flags & SPICE_IMAGE_FLAGS_CACHE_ME))
+ {
+ if (! ("cache" in this.o.sc))
+ this.o.sc.cache = [];
+
+ this.o.sc.cache[this.o.descriptor.id] =
+ t.getImageData(0, 0,
+ this.alpha_img.width,
+ this.alpha_img.height);
+ }
+ }
+ else
+ {
+ context.drawImage(this, this.o.base.box.left, this.o.base.box.top);
+
+ if (this.o.descriptor &&
+ (this.o.descriptor.flags & SPICE_IMAGE_FLAGS_CACHE_ME))
+ {
+ if (! ("cache" in this.o.sc))
+ this.o.sc.cache = [];
+
+ this.o.sc.cache[this.o.descriptor.id] =
+ context.getImageData(this.o.base.box.left, this.o.base.box.top,
+ this.o.base.box.right - this.o.base.box.left,
+ this.o.base.box.bottom - this.o.base.box.top);
+ }
+ }
+
+ if (temp_canvas == null)
+ {
+ if (DUMP_DRAWS && this.o.sc.parent.dump_id)
+ {
+ var debug_canvas = document.createElement("canvas");
+ debug_canvas.setAttribute('id', this.o.tag + "." +
+ this.o.sc.surfaces[this.o.base.surface_id].draw_count + "." +
+ this.o.base.surface_id + "@" + this.o.base.box.left + "x" + this.o.base.box.top);
+ debug_canvas.getContext("2d").drawImage(this, 0, 0);
+ document.getElementById(this.o.sc.parent.dump_id).appendChild(debug_canvas);
+ }
+
+ this.o.sc.surfaces[this.o.base.surface_id].draw_count++;
+ }
+}
diff --git a/ui/js/spice/enums.js b/ui/js/spice/enums.js
new file mode 100644
index 0000000..fd42a14
--- /dev/null
+++ b/ui/js/spice/enums.js
@@ -0,0 +1,282 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+/*----------------------------------------------------------------------------
+** enums.js
+** 'constants' for Spice
+**--------------------------------------------------------------------------*/
+var SPICE_MAGIC = "REDQ";
+var SPICE_VERSION_MAJOR = 2;
+var SPICE_VERSION_MINOR = 2;
+
+var SPICE_CONNECT_TIMEOUT = (30 * 1000);
+
+var SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION = 0;
+var SPICE_COMMON_CAP_AUTH_SPICE = 1;
+var SPICE_COMMON_CAP_AUTH_SASL = 2;
+var SPICE_COMMON_CAP_MINI_HEADER = 3;
+
+var SPICE_TICKET_KEY_PAIR_LENGTH = 1024;
+var SPICE_TICKET_PUBKEY_BYTES = (SPICE_TICKET_KEY_PAIR_LENGTH / 8 + 34);
+
+var SPICE_LINK_ERR_OK = 0,
+ SPICE_LINK_ERR_ERROR = 1,
+ SPICE_LINK_ERR_INVALID_MAGIC = 2,
+ SPICE_LINK_ERR_INVALID_DATA = 3,
+ SPICE_LINK_ERR_VERSION_MISMATCH = 4,
+ SPICE_LINK_ERR_NEED_SECURED = 5,
+ SPICE_LINK_ERR_NEED_UNSECURED = 6,
+ SPICE_LINK_ERR_PERMISSION_DENIED = 7,
+ SPICE_LINK_ERR_BAD_CONNECTION_ID = 8,
+ SPICE_LINK_ERR_CHANNEL_NOT_AVAILABLE = 9;
+
+var SPICE_MSG_MIGRATE = 1;
+var SPICE_MSG_MIGRATE_DATA = 2;
+var SPICE_MSG_SET_ACK = 3;
+var SPICE_MSG_PING = 4;
+var SPICE_MSG_WAIT_FOR_CHANNELS = 5;
+var SPICE_MSG_DISCONNECTING = 6;
+var SPICE_MSG_NOTIFY = 7;
+var SPICE_MSG_LIST = 8;
+
+var SPICE_MSG_MAIN_MIGRATE_BEGIN = 101;
+var SPICE_MSG_MAIN_MIGRATE_CANCEL = 102;
+var SPICE_MSG_MAIN_INIT = 103;
+var SPICE_MSG_MAIN_CHANNELS_LIST = 104;
+var SPICE_MSG_MAIN_MOUSE_MODE = 105;
+var SPICE_MSG_MAIN_MULTI_MEDIA_TIME = 106;
+var SPICE_MSG_MAIN_AGENT_CONNECTED = 107;
+var SPICE_MSG_MAIN_AGENT_DISCONNECTED = 108;
+var SPICE_MSG_MAIN_AGENT_DATA = 109;
+var SPICE_MSG_MAIN_AGENT_TOKEN = 110;
+var SPICE_MSG_MAIN_MIGRATE_SWITCH_HOST = 111;
+var SPICE_MSG_MAIN_MIGRATE_END = 112;
+var SPICE_MSG_MAIN_NAME = 113;
+var SPICE_MSG_MAIN_UUID = 114;
+var SPICE_MSG_END_MAIN = 115;
+
+
+var SPICE_MSGC_ACK_SYNC = 1;
+var SPICE_MSGC_ACK = 2;
+var SPICE_MSGC_PONG = 3;
+var SPICE_MSGC_MIGRATE_FLUSH_MARK = 4;
+var SPICE_MSGC_MIGRATE_DATA = 5;
+var SPICE_MSGC_DISCONNECTING = 6;
+
+
+var SPICE_MSGC_MAIN_CLIENT_INFO = 101;
+var SPICE_MSGC_MAIN_MIGRATE_CONNECTED = 102;
+var SPICE_MSGC_MAIN_MIGRATE_CONNECT_ERROR = 103;
+var SPICE_MSGC_MAIN_ATTACH_CHANNELS = 104;
+var SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST = 105;
+var SPICE_MSGC_MAIN_AGENT_START = 106;
+var SPICE_MSGC_MAIN_AGENT_DATA = 107;
+var SPICE_MSGC_MAIN_AGENT_TOKEN = 108;
+var SPICE_MSGC_MAIN_MIGRATE_END = 109;
+var SPICE_MSGC_END_MAIN = 110;
+
+var SPICE_MSG_DISPLAY_MODE = 101;
+var SPICE_MSG_DISPLAY_MARK = 102;
+var SPICE_MSG_DISPLAY_RESET = 103;
+var SPICE_MSG_DISPLAY_COPY_BITS = 104;
+var SPICE_MSG_DISPLAY_INVAL_LIST = 105;
+var SPICE_MSG_DISPLAY_INVAL_ALL_PIXMAPS = 106;
+var SPICE_MSG_DISPLAY_INVAL_PALETTE = 107;
+var SPICE_MSG_DISPLAY_INVAL_ALL_PALETTES= 108;
+
+var SPICE_MSG_DISPLAY_STREAM_CREATE = 122;
+var SPICE_MSG_DISPLAY_STREAM_DATA = 123;
+var SPICE_MSG_DISPLAY_STREAM_CLIP = 124;
+var SPICE_MSG_DISPLAY_STREAM_DESTROY = 125;
+var SPICE_MSG_DISPLAY_STREAM_DESTROY_ALL= 126;
+
+var SPICE_MSG_DISPLAY_DRAW_FILL = 302;
+var SPICE_MSG_DISPLAY_DRAW_OPAQUE = 303;
+var SPICE_MSG_DISPLAY_DRAW_COPY = 304;
+var SPICE_MSG_DISPLAY_DRAW_BLEND = 305;
+var SPICE_MSG_DISPLAY_DRAW_BLACKNESS = 306;
+var SPICE_MSG_DISPLAY_DRAW_WHITENESS = 307;
+var SPICE_MSG_DISPLAY_DRAW_INVERS = 308;
+var SPICE_MSG_DISPLAY_DRAW_ROP3 = 309;
+var SPICE_MSG_DISPLAY_DRAW_STROKE = 310;
+var SPICE_MSG_DISPLAY_DRAW_TEXT = 311;
+var SPICE_MSG_DISPLAY_DRAW_TRANSPARENT = 312;
+var SPICE_MSG_DISPLAY_DRAW_ALPHA_BLEND = 313;
+var SPICE_MSG_DISPLAY_SURFACE_CREATE = 314;
+var SPICE_MSG_DISPLAY_SURFACE_DESTROY = 315;
+
+var SPICE_MSGC_DISPLAY_INIT = 101;
+
+var SPICE_MSG_INPUTS_INIT = 101;
+var SPICE_MSG_INPUTS_KEY_MODIFIERS = 102;
+
+var SPICE_MSG_INPUTS_MOUSE_MOTION_ACK = 111;
+
+var SPICE_MSGC_INPUTS_KEY_DOWN = 101;
+var SPICE_MSGC_INPUTS_KEY_UP = 102;
+var SPICE_MSGC_INPUTS_KEY_MODIFIERS = 103;
+
+var SPICE_MSGC_INPUTS_MOUSE_MOTION = 111;
+var SPICE_MSGC_INPUTS_MOUSE_POSITION = 112;
+var SPICE_MSGC_INPUTS_MOUSE_PRESS = 113;
+var SPICE_MSGC_INPUTS_MOUSE_RELEASE = 114;
+
+var SPICE_MSG_CURSOR_INIT = 101;
+var SPICE_MSG_CURSOR_RESET = 102;
+var SPICE_MSG_CURSOR_SET = 103;
+var SPICE_MSG_CURSOR_MOVE = 104;
+var SPICE_MSG_CURSOR_HIDE = 105;
+var SPICE_MSG_CURSOR_TRAIL = 106;
+var SPICE_MSG_CURSOR_INVAL_ONE = 107;
+var SPICE_MSG_CURSOR_INVAL_ALL = 108;
+
+
+var SPICE_CHANNEL_MAIN = 1;
+var SPICE_CHANNEL_DISPLAY = 2;
+var SPICE_CHANNEL_INPUTS = 3;
+var SPICE_CHANNEL_CURSOR = 4;
+var SPICE_CHANNEL_PLAYBACK = 5;
+var SPICE_CHANNEL_RECORD = 6;
+var SPICE_CHANNEL_TUNNEL = 7;
+var SPICE_CHANNEL_SMARTCARD = 8;
+var SPICE_CHANNEL_USBREDIR = 9;
+
+var SPICE_SURFACE_FLAGS_PRIMARY = (1 << 0);
+
+var SPICE_NOTIFY_SEVERITY_INFO = 0;
+var SPICE_NOTIFY_SEVERITY_WARN = 1;
+var SPICE_NOTIFY_SEVERITY_ERROR = 2;
+
+var SPICE_MOUSE_MODE_SERVER = (1 << 0),
+ SPICE_MOUSE_MODE_CLIENT = (1 << 1),
+ SPICE_MOUSE_MODE_MASK = 0x3;
+
+var SPICE_CLIP_TYPE_NONE = 0;
+var SPICE_CLIP_TYPE_RECTS = 1;
+
+var SPICE_IMAGE_TYPE_BITMAP = 0;
+var SPICE_IMAGE_TYPE_QUIC = 1;
+var SPICE_IMAGE_TYPE_RESERVED = 2;
+var SPICE_IMAGE_TYPE_LZ_PLT = 100;
+var SPICE_IMAGE_TYPE_LZ_RGB = 101;
+var SPICE_IMAGE_TYPE_GLZ_RGB = 102;
+var SPICE_IMAGE_TYPE_FROM_CACHE = 103;
+var SPICE_IMAGE_TYPE_SURFACE = 104;
+var SPICE_IMAGE_TYPE_JPEG = 105;
+var SPICE_IMAGE_TYPE_FROM_CACHE_LOSSLESS = 106;
+var SPICE_IMAGE_TYPE_ZLIB_GLZ_RGB = 107;
+var SPICE_IMAGE_TYPE_JPEG_ALPHA = 108;
+
+var SPICE_IMAGE_FLAGS_CACHE_ME = (1 << 0),
+ SPICE_IMAGE_FLAGS_HIGH_BITS_SET = (1 << 1),
+ SPICE_IMAGE_FLAGS_CACHE_REPLACE_ME = (1 << 2);
+
+var SPICE_BITMAP_FLAGS_PAL_CACHE_ME = (1 << 0),
+ SPICE_BITMAP_FLAGS_PAL_FROM_CACHE = (1 << 1),
+ SPICE_BITMAP_FLAGS_TOP_DOWN = (1 << 2),
+ SPICE_BITMAP_FLAGS_MASK = 0x7;
+
+var SPICE_BITMAP_FMT_INVALID = 0,
+ SPICE_BITMAP_FMT_1BIT_LE = 1,
+ SPICE_BITMAP_FMT_1BIT_BE = 2,
+ SPICE_BITMAP_FMT_4BIT_LE = 3,
+ SPICE_BITMAP_FMT_4BIT_BE = 4,
+ SPICE_BITMAP_FMT_8BIT = 5,
+ SPICE_BITMAP_FMT_16BIT = 6,
+ SPICE_BITMAP_FMT_24BIT = 7,
+ SPICE_BITMAP_FMT_32BIT = 8,
+ SPICE_BITMAP_FMT_RGBA = 9;
+
+
+var SPICE_CURSOR_FLAGS_NONE = (1 << 0),
+ SPICE_CURSOR_FLAGS_CACHE_ME = (1 << 1),
+ SPICE_CURSOR_FLAGS_FROM_CACHE = (1 << 2),
+ SPICE_CURSOR_FLAGS_MASK = 0x7;
+
+var SPICE_MOUSE_BUTTON_MASK_LEFT = (1 << 0),
+ SPICE_MOUSE_BUTTON_MASK_MIDDLE = (1 << 1),
+ SPICE_MOUSE_BUTTON_MASK_RIGHT = (1 << 2),
+ SPICE_MOUSE_BUTTON_MASK_MASK = 0x7;
+
+var SPICE_MOUSE_BUTTON_INVALID = 0;
+var SPICE_MOUSE_BUTTON_LEFT = 1;
+var SPICE_MOUSE_BUTTON_MIDDLE = 2;
+var SPICE_MOUSE_BUTTON_RIGHT = 3;
+var SPICE_MOUSE_BUTTON_UP = 4;
+var SPICE_MOUSE_BUTTON_DOWN = 5;
+
+var SPICE_BRUSH_TYPE_NONE = 0,
+ SPICE_BRUSH_TYPE_SOLID = 1,
+ SPICE_BRUSH_TYPE_PATTERN = 2;
+
+var SPICE_SURFACE_FMT_INVALID = 0,
+ SPICE_SURFACE_FMT_1_A = 1,
+ SPICE_SURFACE_FMT_8_A = 8,
+ SPICE_SURFACE_FMT_16_555 = 16,
+ SPICE_SURFACE_FMT_32_xRGB = 32,
+ SPICE_SURFACE_FMT_16_565 = 80,
+ SPICE_SURFACE_FMT_32_ARGB = 96;
+
+var SPICE_ROPD_INVERS_SRC = (1 << 0),
+ SPICE_ROPD_INVERS_BRUSH = (1 << 1),
+ SPICE_ROPD_INVERS_DEST = (1 << 2),
+ SPICE_ROPD_OP_PUT = (1 << 3),
+ SPICE_ROPD_OP_OR = (1 << 4),
+ SPICE_ROPD_OP_AND = (1 << 5),
+ SPICE_ROPD_OP_XOR = (1 << 6),
+ SPICE_ROPD_OP_BLACKNESS = (1 << 7),
+ SPICE_ROPD_OP_WHITENESS = (1 << 8),
+ SPICE_ROPD_OP_INVERS = (1 << 9),
+ SPICE_ROPD_INVERS_RES = (1 << 10),
+ SPICE_ROPD_MASK = 0x7ff;
+
+var LZ_IMAGE_TYPE_INVALID = 0,
+ LZ_IMAGE_TYPE_PLT1_LE = 1,
+ LZ_IMAGE_TYPE_PLT1_BE = 2, // PLT stands for palette
+ LZ_IMAGE_TYPE_PLT4_LE = 3,
+ LZ_IMAGE_TYPE_PLT4_BE = 4,
+ LZ_IMAGE_TYPE_PLT8 = 5,
+ LZ_IMAGE_TYPE_RGB16 = 6,
+ LZ_IMAGE_TYPE_RGB24 = 7,
+ LZ_IMAGE_TYPE_RGB32 = 8,
+ LZ_IMAGE_TYPE_RGBA = 9,
+ LZ_IMAGE_TYPE_XXXA = 10;
+
+
+var QUIC_IMAGE_TYPE_INVALID = 0,
+ QUIC_IMAGE_TYPE_GRAY = 1,
+ QUIC_IMAGE_TYPE_RGB16 = 2,
+ QUIC_IMAGE_TYPE_RGB24 = 3,
+ QUIC_IMAGE_TYPE_RGB32 = 4,
+ QUIC_IMAGE_TYPE_RGBA = 5;
+
+var SPICE_INPUT_MOTION_ACK_BUNCH = 4;
+
+
+var SPICE_CURSOR_TYPE_ALPHA = 0,
+ SPICE_CURSOR_TYPE_MONO = 1,
+ SPICE_CURSOR_TYPE_COLOR4 = 2,
+ SPICE_CURSOR_TYPE_COLOR8 = 3,
+ SPICE_CURSOR_TYPE_COLOR16 = 4,
+ SPICE_CURSOR_TYPE_COLOR24 = 5,
+ SPICE_CURSOR_TYPE_COLOR32 = 6;
+
+var SPICE_VIDEO_CODEC_TYPE_MJPEG = 1;
diff --git a/ui/js/spice/inputs.js b/ui/js/spice/inputs.js
new file mode 100644
index 0000000..4d3b28f
--- /dev/null
+++ b/ui/js/spice/inputs.js
@@ -0,0 +1,251 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*----------------------------------------------------------------------------
+ ** Modifier Keystates
+ ** These need to be tracked because focus in and out can get the keyboard
+ ** out of sync.
+ **------------------------------------------------------------------------*/
+var Shift_state = -1;
+var Ctrl_state = -1;
+var Alt_state = -1;
+var Meta_state = -1;
+
+/*----------------------------------------------------------------------------
+** SpiceInputsConn
+** Drive the Spice Inputs channel (e.g. mouse + keyboard)
+**--------------------------------------------------------------------------*/
+function SpiceInputsConn()
+{
+ SpiceConn.apply(this, arguments);
+
+ this.mousex = undefined;
+ this.mousey = undefined;
+ this.button_state = 0;
+ this.waiting_for_ack = 0;
+}
+
+SpiceInputsConn.prototype = Object.create(SpiceConn.prototype);
+SpiceInputsConn.prototype.process_channel_message = function(msg)
+{
+ if (msg.type == SPICE_MSG_INPUTS_INIT)
+ {
+ var inputs_init = new SpiceMsgInputsInit(msg.data);
+ this.keyboard_modifiers = inputs_init.keyboard_modifiers;
+ DEBUG > 1 && console.log("MsgInputsInit - modifier " + this.keyboard_modifiers);
+ // FIXME - We don't do anything with the keyboard modifiers...
+ return true;
+ }
+ if (msg.type == SPICE_MSG_INPUTS_KEY_MODIFIERS)
+ {
+ var key = new SpiceMsgInputsKeyModifiers(msg.data);
+ this.keyboard_modifiers = key.keyboard_modifiers;
+ DEBUG > 1 && console.log("MsgInputsKeyModifiers - modifier " + this.keyboard_modifiers);
+ // FIXME - We don't do anything with the keyboard modifiers...
+ return true;
+ }
+ if (msg.type == SPICE_MSG_INPUTS_MOUSE_MOTION_ACK)
+ {
+ DEBUG > 1 && console.log("mouse motion ack");
+ this.waiting_for_ack -= SPICE_INPUT_MOTION_ACK_BUNCH;
+ return true;
+ }
+ return false;
+}
+
+
+
+function handle_mousemove(e)
+{
+ var msg = new SpiceMiniData();
+ var move;
+ if (this.sc.mouse_mode == SPICE_MOUSE_MODE_CLIENT)
+ {
+ move = new SpiceMsgcMousePosition(this.sc, e)
+ msg.build_msg(SPICE_MSGC_INPUTS_MOUSE_POSITION, move);
+ }
+ else
+ {
+ move = new SpiceMsgcMouseMotion(this.sc, e)
+ msg.build_msg(SPICE_MSGC_INPUTS_MOUSE_MOTION, move);
+ }
+ if (this.sc && this.sc.inputs && this.sc.inputs.state === "ready")
+ {
+ if (this.sc.inputs.waiting_for_ack < (2 * SPICE_INPUT_MOTION_ACK_BUNCH))
+ {
+ this.sc.inputs.send_msg(msg);
+ this.sc.inputs.waiting_for_ack++;
+ }
+ else
+ {
+ DEBUG > 0 && this.sc.log_info("Discarding mouse motion");
+ }
+ }
+}
+
+function handle_mousedown(e)
+{
+ var press = new SpiceMsgcMousePress(this.sc, e)
+ var msg = new SpiceMiniData();
+ msg.build_msg(SPICE_MSGC_INPUTS_MOUSE_PRESS, press);
+ if (this.sc && this.sc.inputs && this.sc.inputs.state === "ready")
+ this.sc.inputs.send_msg(msg);
+
+ e.preventDefault();
+}
+
+function handle_contextmenu(e)
+{
+ e.preventDefault();
+ return false;
+}
+
+function handle_mouseup(e)
+{
+ var release = new SpiceMsgcMouseRelease(this.sc, e)
+ var msg = new SpiceMiniData();
+ msg.build_msg(SPICE_MSGC_INPUTS_MOUSE_RELEASE, release);
+ if (this.sc && this.sc.inputs && this.sc.inputs.state === "ready")
+ this.sc.inputs.send_msg(msg);
+
+ e.preventDefault();
+}
+
+function handle_mousewheel(e)
+{
+ var press = new SpiceMsgcMousePress;
+ var release = new SpiceMsgcMouseRelease;
+ if (e.wheelDelta > 0)
+ press.button = release.button = SPICE_MOUSE_BUTTON_UP;
+ else
+ press.button = release.button = SPICE_MOUSE_BUTTON_DOWN;
+ press.buttons_state = 0;
+ release.buttons_state = 0;
+
+ var msg = new SpiceMiniData();
+ msg.build_msg(SPICE_MSGC_INPUTS_MOUSE_PRESS, press);
+ if (this.sc && this.sc.inputs && this.sc.inputs.state === "ready")
+ this.sc.inputs.send_msg(msg);
+
+ msg.build_msg(SPICE_MSGC_INPUTS_MOUSE_RELEASE, release);
+ if (this.sc && this.sc.inputs && this.sc.inputs.state === "ready")
+ this.sc.inputs.send_msg(msg);
+
+ e.preventDefault();
+}
+
+function handle_keydown(e)
+{
+ var key = new SpiceMsgcKeyDown(e)
+ var msg = new SpiceMiniData();
+ check_and_update_modifiers(e, key.code, this.sc);
+ msg.build_msg(SPICE_MSGC_INPUTS_KEY_DOWN, key);
+ if (this.sc && this.sc.inputs && this.sc.inputs.state === "ready")
+ this.sc.inputs.send_msg(msg);
+
+ e.preventDefault();
+}
+
+function handle_keyup(e)
+{
+ var key = new SpiceMsgcKeyUp(e)
+ var msg = new SpiceMiniData();
+ check_and_update_modifiers(e, key.code, this.sc);
+ msg.build_msg(SPICE_MSGC_INPUTS_KEY_UP, key);
+ if (this.sc && this.sc.inputs && this.sc.inputs.state === "ready")
+ this.sc.inputs.send_msg(msg);
+
+ e.preventDefault();
+}
+
+function update_modifier(state, code, sc)
+{
+ var msg = new SpiceMiniData();
+ if (!state)
+ {
+ var key = new SpiceMsgcKeyUp()
+ key.code =(0x80|code);
+ msg.build_msg(SPICE_MSGC_INPUTS_KEY_UP, key);
+ }
+ else
+ {
+ var key = new SpiceMsgcKeyDown()
+ key.code = code;
+ msg.build_msg(SPICE_MSGC_INPUTS_KEY_DOWN, key);
+ }
+
+ sc.inputs.send_msg(msg);
+}
+
+function check_and_update_modifiers(e, code, sc)
+{
+ if (Shift_state === -1)
+ {
+ Shift_state = e.shiftKey;
+ Ctrl_state = e.ctrlKey;
+ Alt_state = e.altKey;
+ Meta_state = e.metaKey;
+ }
+
+ if (code === KEY_ShiftL)
+ Shift_state = true;
+ else if (code === KEY_Alt)
+ Alt_state = true;
+ else if (code === KEY_LCtrl)
+ Ctrl_state = true;
+ else if (code === 0xE0B5)
+ Meta_state = true;
+ else if (code === (0x80|KEY_ShiftL))
+ Shift_state = false;
+ else if (code === (0x80|KEY_Alt))
+ Alt_state = false;
+ else if (code === (0x80|KEY_LCtrl))
+ Ctrl_state = false;
+ else if (code === (0x80|0xE0B5))
+ Meta_state = false;
+
+ if (sc && sc.inputs && sc.inputs.state === "ready")
+ {
+ if (Shift_state != e.shiftKey)
+ {
+ console.log("Shift state out of sync");
+ update_modifier(e.shiftKey, KEY_ShiftL, sc);
+ Shift_state = e.shiftKey;
+ }
+ if (Alt_state != e.altKey)
+ {
+ console.log("Alt state out of sync");
+ update_modifier(e.altKey, KEY_Alt, sc);
+ Alt_state = e.altKey;
+ }
+ if (Ctrl_state != e.ctrlKey)
+ {
+ console.log("Ctrl state out of sync");
+ update_modifier(e.ctrlKey, KEY_LCtrl, sc);
+ Ctrl_state = e.ctrlKey;
+ }
+ if (Meta_state != e.metaKey)
+ {
+ console.log("Meta state out of sync");
+ update_modifier(e.metaKey, 0xE0B5, sc);
+ Meta_state = e.metaKey;
+ }
+ }
+}
diff --git a/ui/js/spice/jsbn.js b/ui/js/spice/jsbn.js
new file mode 100644
index 0000000..d88ec54
--- /dev/null
+++ b/ui/js/spice/jsbn.js
@@ -0,0 +1,589 @@
+// Downloaded from http://www-cs-students.stanford.edu/~tjw/jsbn/ by Jeremy White on 6/1/2012
+
+/*
+ * Copyright (c) 2003-2005 Tom Wu
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
+ * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * In addition, the following condition applies:
+ *
+ * All redistributions must retain an intact copy of this copyright notice
+ * and disclaimer.
+ */
+
+
+// Basic JavaScript BN library - subset useful for RSA encryption.
+
+// Bits per digit
+var dbits;
+
+// JavaScript engine analysis
+var canary = 0xdeadbeefcafe;
+var j_lm = ((canary&0xffffff)==0xefcafe);
+
+// (public) Constructor
+function BigInteger(a,b,c) {
+ if(a != null)
+ if("number" == typeof a) this.fromNumber(a,b,c);
+ else if(b == null && "string" != typeof a) this.fromString(a,256);
+ else this.fromString(a,b);
+}
+
+// return new, unset BigInteger
+function nbi() { return new BigInteger(null); }
+
+// am: Compute w_j += (x*this_i), propagate carries,
+// c is initial carry, returns final carry.
+// c < 3*dvalue, x < 2*dvalue, this_i < dvalue
+// We need to select the fastest one that works in this environment.
+
+// am1: use a single mult and divide to get the high bits,
+// max digit bits should be 26 because
+// max internal value = 2*dvalue^2-2*dvalue (< 2^53)
+function am1(i,x,w,j,c,n) {
+ while(--n >= 0) {
+ var v = x*this[i++]+w[j]+c;
+ c = Math.floor(v/0x4000000);
+ w[j++] = v&0x3ffffff;
+ }
+ return c;
+}
+// am2 avoids a big mult-and-extract completely.
+// Max digit bits should be <= 30 because we do bitwise ops
+// on values up to 2*hdvalue^2-hdvalue-1 (< 2^31)
+function am2(i,x,w,j,c,n) {
+ var xl = x&0x7fff, xh = x>>15;
+ while(--n >= 0) {
+ var l = this[i]&0x7fff;
+ var h = this[i++]>>15;
+ var m = xh*l+h*xl;
+ l = xl*l+((m&0x7fff)<<15)+w[j]+(c&0x3fffffff);
+ c = (l>>>30)+(m>>>15)+xh*h+(c>>>30);
+ w[j++] = l&0x3fffffff;
+ }
+ return c;
+}
+// Alternately, set max digit bits to 28 since some
+// browsers slow down when dealing with 32-bit numbers.
+function am3(i,x,w,j,c,n) {
+ var xl = x&0x3fff, xh = x>>14;
+ while(--n >= 0) {
+ var l = this[i]&0x3fff;
+ var h = this[i++]>>14;
+ var m = xh*l+h*xl;
+ l = xl*l+((m&0x3fff)<<14)+w[j]+c;
+ c = (l>>28)+(m>>14)+xh*h;
+ w[j++] = l&0xfffffff;
+ }
+ return c;
+}
+if(j_lm && (navigator.appName == "Microsoft Internet Explorer")) {
+ BigInteger.prototype.am = am2;
+ dbits = 30;
+}
+else if(j_lm && (navigator.appName != "Netscape")) {
+ BigInteger.prototype.am = am1;
+ dbits = 26;
+}
+else { // Mozilla/Netscape seems to prefer am3
+ BigInteger.prototype.am = am3;
+ dbits = 28;
+}
+
+BigInteger.prototype.DB = dbits;
+BigInteger.prototype.DM = ((1<<dbits)-1);
+BigInteger.prototype.DV = (1<<dbits);
+
+var BI_FP = 52;
+BigInteger.prototype.FV = Math.pow(2,BI_FP);
+BigInteger.prototype.F1 = BI_FP-dbits;
+BigInteger.prototype.F2 = 2*dbits-BI_FP;
+
+// Digit conversions
+var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz";
+var BI_RC = new Array();
+var rr,vv;
+rr = "0".charCodeAt(0);
+for(vv = 0; vv <= 9; ++vv) BI_RC[rr++] = vv;
+rr = "a".charCodeAt(0);
+for(vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv;
+rr = "A".charCodeAt(0);
+for(vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv;
+
+function int2char(n) { return BI_RM.charAt(n); }
+function intAt(s,i) {
+ var c = BI_RC[s.charCodeAt(i)];
+ return (c==null)?-1:c;
+}
+
+// (protected) copy this to r
+function bnpCopyTo(r) {
+ for(var i = this.t-1; i >= 0; --i) r[i] = this[i];
+ r.t = this.t;
+ r.s = this.s;
+}
+
+// (protected) set from integer value x, -DV <= x < DV
+function bnpFromInt(x) {
+ this.t = 1;
+ this.s = (x<0)?-1:0;
+ if(x > 0) this[0] = x;
+ else if(x < -1) this[0] = x+DV;
+ else this.t = 0;
+}
+
+// return bigint initialized to value
+function nbv(i) { var r = nbi(); r.fromInt(i); return r; }
+
+// (protected) set from string and radix
+function bnpFromString(s,b) {
+ var k;
+ if(b == 16) k = 4;
+ else if(b == 8) k = 3;
+ else if(b == 256) k = 8; // byte array
+ else if(b == 2) k = 1;
+ else if(b == 32) k = 5;
+ else if(b == 4) k = 2;
+ else { this.fromRadix(s,b); return; }
+ this.t = 0;
+ this.s = 0;
+ var i = s.length, mi = false, sh = 0;
+ while(--i >= 0) {
+ var x = (k==8)?s[i]&0xff:intAt(s,i);
+ if(x < 0) {
+ if(s.charAt(i) == "-") mi = true;
+ continue;
+ }
+ mi = false;
+ if(sh == 0)
+ this[this.t++] = x;
+ else if(sh+k > this.DB) {
+ this[this.t-1] |= (x&((1<<(this.DB-sh))-1))<<sh;
+ this[this.t++] = (x>>(this.DB-sh));
+ }
+ else
+ this[this.t-1] |= x<<sh;
+ sh += k;
+ if(sh >= this.DB) sh -= this.DB;
+ }
+ if(k == 8 && (s[0]&0x80) != 0) {
+ this.s = -1;
+ if(sh > 0) this[this.t-1] |= ((1<<(this.DB-sh))-1)<<sh;
+ }
+ this.clamp();
+ if(mi) BigInteger.ZERO.subTo(this,this);
+}
+
+// (protected) clamp off excess high words
+function bnpClamp() {
+ var c = this.s&this.DM;
+ while(this.t > 0 && this[this.t-1] == c) --this.t;
+}
+
+// (public) return string representation in given radix
+function bnToString(b) {
+ if(this.s < 0) return "-"+this.negate().toString(b);
+ var k;
+ if(b == 16) k = 4;
+ else if(b == 8) k = 3;
+ else if(b == 2) k = 1;
+ else if(b == 32) k = 5;
+ else if(b == 4) k = 2;
+ else return this.toRadix(b);
+ var km = (1<<k)-1, d, m = false, r = "", i = this.t;
+ var p = this.DB-(i*this.DB)%k;
+ if(i-- > 0) {
+ if(p < this.DB && (d = this[i]>>p) > 0) { m = true; r = int2char(d); }
+ while(i >= 0) {
+ if(p < k) {
+ d = (this[i]&((1<<p)-1))<<(k-p);
+ d |= this[--i]>>(p+=this.DB-k);
+ }
+ else {
+ d = (this[i]>>(p-=k))&km;
+ if(p <= 0) { p += this.DB; --i; }
+ }
+ if(d > 0) m = true;
+ if(m) r += int2char(d);
+ }
+ }
+ return m?r:"0";
+}
+
+// (public) -this
+function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this,r); return r; }
+
+// (public) |this|
+function bnAbs() { return (this.s<0)?this.negate():this; }
+
+// (public) return + if this > a, - if this < a, 0 if equal
+function bnCompareTo(a) {
+ var r = this.s-a.s;
+ if(r != 0) return r;
+ var i = this.t;
+ r = i-a.t;
+ if(r != 0) return r;
+ while(--i >= 0) if((r=this[i]-a[i]) != 0) return r;
+ return 0;
+}
+
+// returns bit length of the integer x
+function nbits(x) {
+ var r = 1, t;
+ if((t=x>>>16) != 0) { x = t; r += 16; }
+ if((t=x>>8) != 0) { x = t; r += 8; }
+ if((t=x>>4) != 0) { x = t; r += 4; }
+ if((t=x>>2) != 0) { x = t; r += 2; }
+ if((t=x>>1) != 0) { x = t; r += 1; }
+ return r;
+}
+
+// (public) return the number of bits in "this"
+function bnBitLength() {
+ if(this.t <= 0) return 0;
+ return this.DB*(this.t-1)+nbits(this[this.t-1]^(this.s&this.DM));
+}
+
+// (protected) r = this << n*DB
+function bnpDLShiftTo(n,r) {
+ var i;
+ for(i = this.t-1; i >= 0; --i) r[i+n] = this[i];
+ for(i = n-1; i >= 0; --i) r[i] = 0;
+ r.t = this.t+n;
+ r.s = this.s;
+}
+
+// (protected) r = this >> n*DB
+function bnpDRShiftTo(n,r) {
+ for(var i = n; i < this.t; ++i) r[i-n] = this[i];
+ r.t = Math.max(this.t-n,0);
+ r.s = this.s;
+}
+
+// (protected) r = this << n
+function bnpLShiftTo(n,r) {
+ var bs = n%this.DB;
+ var cbs = this.DB-bs;
+ var bm = (1<<cbs)-1;
+ var ds = Math.floor(n/this.DB), c = (this.s<<bs)&this.DM, i;
+ for(i = this.t-1; i >= 0; --i) {
+ r[i+ds+1] = (this[i]>>cbs)|c;
+ c = (this[i]&bm)<<bs;
+ }
+ for(i = ds-1; i >= 0; --i) r[i] = 0;
+ r[ds] = c;
+ r.t = this.t+ds+1;
+ r.s = this.s;
+ r.clamp();
+}
+
+// (protected) r = this >> n
+function bnpRShiftTo(n,r) {
+ r.s = this.s;
+ var ds = Math.floor(n/this.DB);
+ if(ds >= this.t) { r.t = 0; return; }
+ var bs = n%this.DB;
+ var cbs = this.DB-bs;
+ var bm = (1<<bs)-1;
+ r[0] = this[ds]>>bs;
+ for(var i = ds+1; i < this.t; ++i) {
+ r[i-ds-1] |= (this[i]&bm)<<cbs;
+ r[i-ds] = this[i]>>bs;
+ }
+ if(bs > 0) r[this.t-ds-1] |= (this.s&bm)<<cbs;
+ r.t = this.t-ds;
+ r.clamp();
+}
+
+// (protected) r = this - a
+function bnpSubTo(a,r) {
+ var i = 0, c = 0, m = Math.min(a.t,this.t);
+ while(i < m) {
+ c += this[i]-a[i];
+ r[i++] = c&this.DM;
+ c >>= this.DB;
+ }
+ if(a.t < this.t) {
+ c -= a.s;
+ while(i < this.t) {
+ c += this[i];
+ r[i++] = c&this.DM;
+ c >>= this.DB;
+ }
+ c += this.s;
+ }
+ else {
+ c += this.s;
+ while(i < a.t) {
+ c -= a[i];
+ r[i++] = c&this.DM;
+ c >>= this.DB;
+ }
+ c -= a.s;
+ }
+ r.s = (c<0)?-1:0;
+ if(c < -1) r[i++] = this.DV+c;
+ else if(c > 0) r[i++] = c;
+ r.t = i;
+ r.clamp();
+}
+
+// (protected) r = this * a, r != this,a (HAC 14.12)
+// "this" should be the larger one if appropriate.
+function bnpMultiplyTo(a,r) {
+ var x = this.abs(), y = a.abs();
+ var i = x.t;
+ r.t = i+y.t;
+ while(--i >= 0) r[i] = 0;
+ for(i = 0; i < y.t; ++i) r[i+x.t] = x.am(0,y[i],r,i,0,x.t);
+ r.s = 0;
+ r.clamp();
+ if(this.s != a.s) BigInteger.ZERO.subTo(r,r);
+}
+
+// (protected) r = this^2, r != this (HAC 14.16)
+function bnpSquareTo(r) {
+ var x = this.abs();
+ var i = r.t = 2*x.t;
+ while(--i >= 0) r[i] = 0;
+ for(i = 0; i < x.t-1; ++i) {
+ var c = x.am(i,x[i],r,2*i,0,1);
+ if((r[i+x.t]+=x.am(i+1,2*x[i],r,2*i+1,c,x.t-i-1)) >= x.DV) {
+ r[i+x.t] -= x.DV;
+ r[i+x.t+1] = 1;
+ }
+ }
+ if(r.t > 0) r[r.t-1] += x.am(i,x[i],r,2*i,0,1);
+ r.s = 0;
+ r.clamp();
+}
+
+// (protected) divide this by m, quotient and remainder to q, r (HAC 14.20)
+// r != q, this != m. q or r may be null.
+function bnpDivRemTo(m,q,r) {
+ var pm = m.abs();
+ if(pm.t <= 0) return;
+ var pt = this.abs();
+ if(pt.t < pm.t) {
+ if(q != null) q.fromInt(0);
+ if(r != null) this.copyTo(r);
+ return;
+ }
+ if(r == null) r = nbi();
+ var y = nbi(), ts = this.s, ms = m.s;
+ var nsh = this.DB-nbits(pm[pm.t-1]); // normalize modulus
+ if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); }
+ else { pm.copyTo(y); pt.copyTo(r); }
+ var ys = y.t;
+ var y0 = y[ys-1];
+ if(y0 == 0) return;
+ var yt = y0*(1<<this.F1)+((ys>1)?y[ys-2]>>this.F2:0);
+ var d1 = this.FV/yt, d2 = (1<<this.F1)/yt, e = 1<<this.F2;
+ var i = r.t, j = i-ys, t = (q==null)?nbi():q;
+ y.dlShiftTo(j,t);
+ if(r.compareTo(t) >= 0) {
+ r[r.t++] = 1;
+ r.subTo(t,r);
+ }
+ BigInteger.ONE.dlShiftTo(ys,t);
+ t.subTo(y,y); // "negative" y so we can replace sub with am later
+ while(y.t < ys) y[y.t++] = 0;
+ while(--j >= 0) {
+ // Estimate quotient digit
+ var qd = (r[--i]==y0)?this.DM:Math.floor(r[i]*d1+(r[i-1]+e)*d2);
+ if((r[i]+=y.am(0,qd,r,j,0,ys)) < qd) { // Try it out
+ y.dlShiftTo(j,t);
+ r.subTo(t,r);
+ while(r[i] < --qd) r.subTo(t,r);
+ }
+ }
+ if(q != null) {
+ r.drShiftTo(ys,q);
+ if(ts != ms) BigInteger.ZERO.subTo(q,q);
+ }
+ r.t = ys;
+ r.clamp();
+ if(nsh > 0) r.rShiftTo(nsh,r); // Denormalize remainder
+ if(ts < 0) BigInteger.ZERO.subTo(r,r);
+}
+
+// (public) this mod a
+function bnMod(a) {
+ var r = nbi();
+ this.abs().divRemTo(a,null,r);
+ if(this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r,r);
+ return r;
+}
+
+// Modular reduction using "classic" algorithm
+function Classic(m) { this.m = m; }
+function cConvert(x) {
+ if(x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m);
+ else return x;
+}
+function cRevert(x) { return x; }
+function cReduce(x) { x.divRemTo(this.m,null,x); }
+function cMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
+function cSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
+
+Classic.prototype.convert = cConvert;
+Classic.prototype.revert = cRevert;
+Classic.prototype.reduce = cReduce;
+Classic.prototype.mulTo = cMulTo;
+Classic.prototype.sqrTo = cSqrTo;
+
+// (protected) return "-1/this % 2^DB"; useful for Mont. reduction
+// justification:
+// xy == 1 (mod m)
+// xy = 1+km
+// xy(2-xy) = (1+km)(1-km)
+// x[y(2-xy)] = 1-k^2m^2
+// x[y(2-xy)] == 1 (mod m^2)
+// if y is 1/x mod m, then y(2-xy) is 1/x mod m^2
+// should reduce x and y(2-xy) by m^2 at each step to keep size bounded.
+// JS multiply "overflows" differently from C/C++, so care is needed here.
+function bnpInvDigit() {
+ if(this.t < 1) return 0;
+ var x = this[0];
+ if((x&1) == 0) return 0;
+ var y = x&3; // y == 1/x mod 2^2
+ y = (y*(2-(x&0xf)*y))&0xf; // y == 1/x mod 2^4
+ y = (y*(2-(x&0xff)*y))&0xff; // y == 1/x mod 2^8
+ y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff; // y == 1/x mod 2^16
+ // last step - calculate inverse mod DV directly;
+ // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints
+ y = (y*(2-x*y%this.DV))%this.DV; // y == 1/x mod 2^dbits
+ // we really want the negative inverse, and -DV < y < DV
+ return (y>0)?this.DV-y:-y;
+}
+
+// Montgomery reduction
+function Montgomery(m) {
+ this.m = m;
+ this.mp = m.invDigit();
+ this.mpl = this.mp&0x7fff;
+ this.mph = this.mp>>15;
+ this.um = (1<<(m.DB-15))-1;
+ this.mt2 = 2*m.t;
+}
+
+// xR mod m
+function montConvert(x) {
+ var r = nbi();
+ x.abs().dlShiftTo(this.m.t,r);
+ r.divRemTo(this.m,null,r);
+ if(x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r,r);
+ return r;
+}
+
+// x/R mod m
+function montRevert(x) {
+ var r = nbi();
+ x.copyTo(r);
+ this.reduce(r);
+ return r;
+}
+
+// x = x/R mod m (HAC 14.32)
+function montReduce(x) {
+ while(x.t <= this.mt2) // pad x so am has enough room later
+ x[x.t++] = 0;
+ for(var i = 0; i < this.m.t; ++i) {
+ // faster way of calculating u0 = x[i]*mp mod DV
+ var j = x[i]&0x7fff;
+ var u0 = (j*this.mpl+(((j*this.mph+(x[i]>>15)*this.mpl)&this.um)<<15))&x.DM;
+ // use am to combine the multiply-shift-add into one call
+ j = i+this.m.t;
+ x[j] += this.m.am(0,u0,x,i,0,this.m.t);
+ // propagate carry
+ while(x[j] >= x.DV) { x[j] -= x.DV; x[++j]++; }
+ }
+ x.clamp();
+ x.drShiftTo(this.m.t,x);
+ if(x.compareTo(this.m) >= 0) x.subTo(this.m,x);
+}
+
+// r = "x^2/R mod m"; x != r
+function montSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
+
+// r = "xy/R mod m"; x,y != r
+function montMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
+
+Montgomery.prototype.convert = montConvert;
+Montgomery.prototype.revert = montRevert;
+Montgomery.prototype.reduce = montReduce;
+Montgomery.prototype.mulTo = montMulTo;
+Montgomery.prototype.sqrTo = montSqrTo;
+
+// (protected) true iff this is even
+function bnpIsEven() { return ((this.t>0)?(this[0]&1):this.s) == 0; }
+
+// (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79)
+function bnpExp(e,z) {
+ if(e > 0xffffffff || e < 1) return BigInteger.ONE;
+ var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1;
+ g.copyTo(r);
+ while(--i >= 0) {
+ z.sqrTo(r,r2);
+ if((e&(1<<i)) > 0) z.mulTo(r2,g,r);
+ else { var t = r; r = r2; r2 = t; }
+ }
+ return z.revert(r);
+}
+
+// (public) this^e % m, 0 <= e < 2^32
+function bnModPowInt(e,m) {
+ var z;
+ if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m);
+ return this.exp(e,z);
+}
+
+// protected
+BigInteger.prototype.copyTo = bnpCopyTo;
+BigInteger.prototype.fromInt = bnpFromInt;
+BigInteger.prototype.fromString = bnpFromString;
+BigInteger.prototype.clamp = bnpClamp;
+BigInteger.prototype.dlShiftTo = bnpDLShiftTo;
+BigInteger.prototype.drShiftTo = bnpDRShiftTo;
+BigInteger.prototype.lShiftTo = bnpLShiftTo;
+BigInteger.prototype.rShiftTo = bnpRShiftTo;
+BigInteger.prototype.subTo = bnpSubTo;
+BigInteger.prototype.multiplyTo = bnpMultiplyTo;
+BigInteger.prototype.squareTo = bnpSquareTo;
+BigInteger.prototype.divRemTo = bnpDivRemTo;
+BigInteger.prototype.invDigit = bnpInvDigit;
+BigInteger.prototype.isEven = bnpIsEven;
+BigInteger.prototype.exp = bnpExp;
+
+// public
+BigInteger.prototype.toString = bnToString;
+BigInteger.prototype.negate = bnNegate;
+BigInteger.prototype.abs = bnAbs;
+BigInteger.prototype.compareTo = bnCompareTo;
+BigInteger.prototype.bitLength = bnBitLength;
+BigInteger.prototype.mod = bnMod;
+BigInteger.prototype.modPowInt = bnModPowInt;
+
+// "constants"
+BigInteger.ZERO = nbv(0);
+BigInteger.ONE = nbv(1);
diff --git a/ui/js/spice/lz.js b/ui/js/spice/lz.js
new file mode 100644
index 0000000..4292eac
--- /dev/null
+++ b/ui/js/spice/lz.js
@@ -0,0 +1,166 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+/*----------------------------------------------------------------------------
+** lz.js
+** Functions for handling SPICE_IMAGE_TYPE_LZ_RGB
+** Adapted from lz.c .
+**--------------------------------------------------------------------------*/
+function lz_rgb32_decompress(in_buf, at, out_buf, type, default_alpha)
+{
+ var encoder = at;
+ var op = 0;
+ var ctrl;
+ var ctr = 0;
+
+ for (ctrl = in_buf[encoder++]; (op * 4) < out_buf.length; ctrl = in_buf[encoder++])
+ {
+ var ref = op;
+ var len = ctrl >> 5;
+ var ofs = (ctrl & 31) << 8;
+
+//if (type == LZ_IMAGE_TYPE_RGBA)
+//console.log(ctr++ + ": from " + (encoder + 28) + ", ctrl " + ctrl + ", len " + len + ", ofs " + ofs + ", op " + op);
+ if (ctrl >= 32) {
+
+ var code;
+ len--;
+
+ if (len == 7 - 1) {
+ do {
+ code = in_buf[encoder++];
+ len += code;
+ } while (code == 255);
+ }
+ code = in_buf[encoder++];
+ ofs += code;
+
+
+ if (code == 255) {
+ if ((ofs - code) == (31 << 8)) {
+ ofs = in_buf[encoder++] << 8;
+ ofs += in_buf[encoder++];
+ ofs += 8191;
+ }
+ }
+ len += 1;
+ if (type == LZ_IMAGE_TYPE_RGBA)
+ len += 2;
+
+ ofs += 1;
+
+ ref -= ofs;
+ if (ref == (op - 1)) {
+ var b = ref;
+//if (type == LZ_IMAGE_TYPE_RGBA) console.log("alpha " + out_buf[(b*4)+3] + " dupped into pixel " + op + " through pixel " + (op + len));
+ for (; len; --len) {
+ if (type == LZ_IMAGE_TYPE_RGBA)
+ {
+ out_buf[(op*4) + 3] = out_buf[(b*4)+3];
+ }
+ else
+ {
+ for (i = 0; i < 4; i++)
+ out_buf[(op*4) + i] = out_buf[(b*4)+i];
+ }
+ op++;
+ }
+ } else {
+//if (type == LZ_IMAGE_TYPE_RGBA) console.log("alpha copied to pixel " + op + " through " + (op + len) + " from " + ref);
+ for (; len; --len) {
+ if (type == LZ_IMAGE_TYPE_RGBA)
+ {
+ out_buf[(op*4) + 3] = out_buf[(ref*4)+3];
+ }
+ else
+ {
+ for (i = 0; i < 4; i++)
+ out_buf[(op*4) + i] = out_buf[(ref*4)+i];
+ }
+ op++; ref++;
+ }
+ }
+ } else {
+ ctrl++;
+
+ if (type == LZ_IMAGE_TYPE_RGBA)
+ {
+//console.log("alpha " + in_buf[encoder] + " set into pixel " + op);
+ out_buf[(op*4) + 3] = in_buf[encoder++];
+ }
+ else
+ {
+ out_buf[(op*4) + 0] = in_buf[encoder + 2];
+ out_buf[(op*4) + 1] = in_buf[encoder + 1];
+ out_buf[(op*4) + 2] = in_buf[encoder + 0];
+ if (default_alpha)
+ out_buf[(op*4) + 3] = 255;
+ encoder += 3;
+ }
+ op++;
+
+
+ for (--ctrl; ctrl; ctrl--) {
+ if (type == LZ_IMAGE_TYPE_RGBA)
+ {
+//console.log("alpha " + in_buf[encoder] + " set into pixel " + op);
+ out_buf[(op*4) + 3] = in_buf[encoder++];
+ }
+ else
+ {
+ out_buf[(op*4) + 0] = in_buf[encoder + 2];
+ out_buf[(op*4) + 1] = in_buf[encoder + 1];
+ out_buf[(op*4) + 2] = in_buf[encoder + 0];
+ if (default_alpha)
+ out_buf[(op*4) + 3] = 255;
+ encoder += 3;
+ }
+ op++;
+ }
+ }
+
+ }
+ return encoder - 1;
+}
+
+function convert_spice_lz_to_web(context, lz_image)
+{
+ var at;
+ if (lz_image.type === LZ_IMAGE_TYPE_RGB32 || lz_image.type === LZ_IMAGE_TYPE_RGBA)
+ {
+ var u8 = new Uint8Array(lz_image.data);
+ var ret = context.createImageData(lz_image.width, lz_image.height);
+
+ at = lz_rgb32_decompress(u8, 0, ret.data, LZ_IMAGE_TYPE_RGB32, lz_image.type != LZ_IMAGE_TYPE_RGBA);
+ if (lz_image.type == LZ_IMAGE_TYPE_RGBA)
+ lz_rgb32_decompress(u8, at, ret.data, LZ_IMAGE_TYPE_RGBA, false);
+ }
+ else if (lz_image.type === LZ_IMAGE_TYPE_XXXA)
+ {
+ var u8 = new Uint8Array(lz_image.data);
+ var ret = context.createImageData(lz_image.width, lz_image.height);
+ lz_rgb32_decompress(u8, 0, ret.data, LZ_IMAGE_TYPE_RGBA, false);
+ }
+ else
+ return undefined;
+
+ return ret;
+}
diff --git a/ui/js/spice/main.js b/ui/js/spice/main.js
new file mode 100644
index 0000000..e7eb1ed
--- /dev/null
+++ b/ui/js/spice/main.js
@@ -0,0 +1,176 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*----------------------------------------------------------------------------
+** SpiceMainConn
+** This is the master Javascript class for establishing and
+** managing a connection to a Spice Server.
+**
+** Invocation: You must pass an object with properties as follows:
+** uri (required) Uri of a WebSocket listener that is
+** connected to a spice server.
+** password (required) Password to send to the spice server
+** message_id (optional) Identifier of an element in the DOM
+** where SpiceConn will write messages.
+** It will use classes spice-messages-x,
+** where x is one of info, warning, or error.
+** screen_id (optional) Identifier of an element in the DOM
+** where SpiceConn will create any new
+** client screens. This is the main UI.
+** dump_id (optional) If given, an element to use for
+** dumping every single image + canvas drawn.
+** Sometimes useful for debugging.
+** onerror (optional) If given, a function to receive async
+** errors. Note that you should also catch
+** errors for ones that occur inline
+**
+** Throws error if there are troubles. Requires a modern (by 2012 standards)
+** browser, including WebSocket and WebSocket.binaryType == arraybuffer
+**
+**--------------------------------------------------------------------------*/
+function SpiceMainConn()
+{
+ if (typeof WebSocket === "undefined")
+ throw new Error("WebSocket unavailable. You need to use a different browser.");
+
+ SpiceConn.apply(this, arguments);
+
+}
+
+SpiceMainConn.prototype = Object.create(SpiceConn.prototype);
+SpiceMainConn.prototype.process_channel_message = function(msg)
+{
+ if (msg.type == SPICE_MSG_MAIN_INIT)
+ {
+ this.log_info("Connected to " + this.ws.url);
+ this.main_init = new SpiceMsgMainInit(msg.data);
+ this.connection_id = this.main_init.session_id;
+
+ if (DEBUG > 0)
+ {
+ // FIXME - there is a lot here we don't handle; mouse modes, agent,
+ // ram_hint, multi_media_time
+ this.log_info("session id " + this.main_init.session_id +
+ " ; display_channels_hint " + this.main_init.display_channels_hint +
+ " ; supported_mouse_modes " + this.main_init.supported_mouse_modes +
+ " ; current_mouse_mode " + this.main_init.current_mouse_mode +
+ " ; agent_connected " + this.main_init.agent_connected +
+ " ; agent_tokens " + this.main_init.agent_tokens +
+ " ; multi_media_time " + this.main_init.multi_media_time +
+ " ; ram_hint " + this.main_init.ram_hint);
+ }
+
+ this.mouse_mode = this.main_init.current_mouse_mode;
+ if (this.main_init.current_mouse_mode != SPICE_MOUSE_MODE_CLIENT &&
+ (this.main_init.supported_mouse_modes & SPICE_MOUSE_MODE_CLIENT))
+ {
+ var mode_request = new SpiceMsgcMainMouseModeRequest(SPICE_MOUSE_MODE_CLIENT);
+ var mr = new SpiceMiniData();
+ mr.build_msg(SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST, mode_request);
+ this.send_msg(mr);
+ }
+
+ var attach = new SpiceMiniData;
+ attach.type = SPICE_MSGC_MAIN_ATTACH_CHANNELS;
+ attach.size = attach.buffer_size();
+ this.send_msg(attach);
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_MAIN_MOUSE_MODE)
+ {
+ var mode = new SpiceMsgMainMouseMode(msg.data);
+ DEBUG > 0 && this.log_info("Mouse supported modes " + mode.supported_modes + "; current " + mode.current_mode);
+ this.mouse_mode = mode.current_mode;
+ if (this.inputs)
+ this.inputs.mouse_mode = mode.current_mode;
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_MAIN_CHANNELS_LIST)
+ {
+ var i;
+ var chans;
+ DEBUG > 0 && console.log("channels");
+ chans = new SpiceMsgChannels(msg.data);
+ for (i = 0; i < chans.channels.length; i++)
+ {
+ var conn = {
+ uri: this.ws.url,
+ parent: this,
+ connection_id : this.connection_id,
+ type : chans.channels[i].type,
+ chan_id : chans.channels[i].id
+ };
+ if (chans.channels[i].type == SPICE_CHANNEL_DISPLAY)
+ this.display = new SpiceDisplayConn(conn);
+ else if (chans.channels[i].type == SPICE_CHANNEL_INPUTS)
+ {
+ this.inputs = new SpiceInputsConn(conn);
+ this.inputs.mouse_mode = this.mouse_mode;
+ }
+ else if (chans.channels[i].type == SPICE_CHANNEL_CURSOR)
+ this.cursor = new SpiceCursorConn(conn);
+ else
+ {
+ this.log_err("Channel type " + chans.channels[i].type + " unknown.");
+ if (! ("extra_channels" in this))
+ this.extra_channels = [];
+ this.extra_channels[i] = new SpiceConn(conn);
+ }
+
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+SpiceMainConn.prototype.stop = function(msg)
+{
+ this.state = "closing";
+
+ if (this.inputs)
+ {
+ this.inputs.cleanup();
+ this.inputs = undefined;
+ }
+
+ if (this.cursor)
+ {
+ this.cursor.cleanup();
+ this.cursor = undefined;
+ }
+
+ if (this.display)
+ {
+ this.display.cleanup();
+ this.display.destroy_surfaces();
+ this.display = undefined;
+ }
+
+ this.cleanup();
+
+ if ("extra_channels" in this)
+ for (var e in this.extra_channels)
+ this.extra_channels[e].cleanup();
+ this.extra_channels = undefined;
+}
diff --git a/ui/js/spice/png.js b/ui/js/spice/png.js
new file mode 100644
index 0000000..6a26151
--- /dev/null
+++ b/ui/js/spice/png.js
@@ -0,0 +1,256 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*----------------------------------------------------------------------------
+** crc logic from rfc2083 ported to Javascript
+**--------------------------------------------------------------------------*/
+
+var rfc2083_crc_table = Array(256);
+var rfc2083_crc_table_computed = 0;
+/* Make the table for a fast CRC. */
+function rfc2083_make_crc_table()
+{
+ var c;
+ var n, k;
+ for (n = 0; n < 256; n++)
+ {
+ c = n;
+ for (k = 0; k < 8; k++)
+ {
+ if (c & 1)
+ c = ((0xedb88320 ^ (c >>> 1)) >>> 0) & 0xffffffff;
+ else
+ c = c >>> 1;
+ }
+ rfc2083_crc_table[n] = c;
+ }
+
+ rfc2083_crc_table_computed = 1;
+}
+
+/* Update a running CRC with the bytes buf[0..len-1]--the CRC
+ should be initialized to all 1's, and the transmitted value
+ is the 1's complement of the final running CRC (see the
+ crc() routine below)). */
+
+function rfc2083_update_crc(crc, u8buf, at, len)
+{
+ var c = crc;
+ var n;
+
+ if (!rfc2083_crc_table_computed)
+ rfc2083_make_crc_table();
+
+ for (n = 0; n < len; n++)
+ {
+ c = rfc2083_crc_table[(c ^ u8buf[at + n]) & 0xff] ^ (c >>> 8);
+ }
+
+ return c;
+}
+
+function rfc2083_crc(u8buf, at, len)
+{
+ return rfc2083_update_crc(0xffffffff, u8buf, at, len) ^ 0xffffffff;
+}
+
+function crc32(mb, at, len)
+{
+ var u8 = new Uint8Array(mb);
+ return rfc2083_crc(u8, at, len);
+}
+
+function PngIHDR(width, height)
+{
+ this.width = width;
+ this.height = height;
+ this.depth = 8;
+ this.type = 6;
+ this.compression = 0;
+ this.filter = 0;
+ this.interlace = 0;
+}
+
+PngIHDR.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var orig = at;
+ var dv = new SpiceDataView(a);
+ dv.setUint32(at, this.buffer_size() - 12); at += 4;
+ dv.setUint8(at, 'I'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'H'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'D'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'R'.charCodeAt(0)); at++;
+ dv.setUint32(at, this.width); at += 4;
+ dv.setUint32(at, this.height); at += 4;
+ dv.setUint8(at, this.depth); at++;
+ dv.setUint8(at, this.type); at++;
+ dv.setUint8(at, this.compression); at++;
+ dv.setUint8(at, this.filter); at++;
+ dv.setUint8(at, this.interlace); at++;
+ dv.setUint32(at, crc32(a, orig + 4, this.buffer_size() - 8)); at += 4;
+ return at;
+ },
+ buffer_size: function()
+ {
+ return 12 + 13;
+ }
+}
+
+
+function adler()
+{
+ this.s1 = 1;
+ this.s2 = 0;
+}
+
+adler.prototype.update = function(b)
+{
+ this.s1 += b;
+ this.s1 %= 65521;
+ this.s2 += this.s1;
+ this.s2 %= 65521;
+}
+
+function PngIDAT(width, height, bytes)
+{
+ if (bytes.byteLength > 65535)
+ {
+ throw new Error("Cannot handle more than 64K");
+ }
+ this.data = bytes;
+ this.width = width;
+ this.height = height;
+}
+
+PngIDAT.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var orig = at;
+ var x, y, i, j;
+ var dv = new SpiceDataView(a);
+ var zsum = new adler();
+ dv.setUint32(at, this.buffer_size() - 12); at += 4;
+ dv.setUint8(at, 'I'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'D'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'A'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'T'.charCodeAt(0)); at++;
+
+ /* zlib header. */
+ dv.setUint8(at, 0x78); at++;
+ dv.setUint8(at, 0x01); at++;
+
+ /* Deflate header. Specifies uncompressed, final bit */
+ dv.setUint8(at, 0x80); at++;
+ dv.setUint16(at, this.data.byteLength + this.height); at += 2;
+ dv.setUint16(at, ~(this.data.byteLength + this.height)); at += 2;
+ var u8 = new Uint8Array(this.data);
+ for (i = 0, y = 0; y < this.height; y++)
+ {
+ /* Filter type 0 - uncompressed */
+ dv.setUint8(at, 0); at++;
+ zsum.update(0);
+ for (x = 0; x < this.width && i < this.data.byteLength; x++)
+ {
+ zsum.update(u8[i]);
+ dv.setUint8(at, u8[i++]); at++;
+ zsum.update(u8[i]);
+ dv.setUint8(at, u8[i++]); at++;
+ zsum.update(u8[i]);
+ dv.setUint8(at, u8[i++]); at++;
+ zsum.update(u8[i]);
+ dv.setUint8(at, u8[i++]); at++;
+ }
+ }
+
+ /* zlib checksum. */
+ dv.setUint16(at, zsum.s2); at+=2;
+ dv.setUint16(at, zsum.s1); at+=2;
+
+ /* FIXME - something is not quite right with the zlib code;
+ you get an error from libpng if you open the image in
+ gimp. But it works, so it's good enough for now... */
+
+ dv.setUint32(at, crc32(a, orig + 4, this.buffer_size() - 8)); at += 4;
+ return at;
+ },
+ buffer_size: function()
+ {
+ return 12 + this.data.byteLength + this.height + 4 + 2 + 1 + 2 + 2;
+ }
+}
+
+
+function PngIEND()
+{
+}
+
+PngIEND.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var orig = at;
+ var i;
+ var dv = new SpiceDataView(a);
+ dv.setUint32(at, this.buffer_size() - 12); at += 4;
+ dv.setUint8(at, 'I'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'E'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'N'.charCodeAt(0)); at++;
+ dv.setUint8(at, 'D'.charCodeAt(0)); at++;
+ dv.setUint32(at, crc32(a, orig + 4, this.buffer_size() - 8)); at += 4;
+ return at;
+ },
+ buffer_size: function()
+ {
+ return 12;
+ }
+}
+
+
+function create_rgba_png(width, height, bytes)
+{
+ var i;
+ var ihdr = new PngIHDR(width, height);
+ var idat = new PngIDAT(width, height, bytes);
+ var iend = new PngIEND;
+
+ var mb = new ArrayBuffer(ihdr.buffer_size() + idat.buffer_size() + iend.buffer_size());
+ var at = ihdr.to_buffer(mb);
+ at = idat.to_buffer(mb, at);
+ at = iend.to_buffer(mb, at);
+
+ var u8 = new Uint8Array(mb);
+ var str = "";
+ for (i = 0; i < at; i++)
+ {
+ str += "%";
+ if (u8[i] < 16)
+ str += "0";
+ str += u8[i].toString(16);
+ }
+
+
+ return "%89PNG%0D%0A%1A%0A" + str;
+}
diff --git a/ui/js/spice/prng4.js b/ui/js/spice/prng4.js
new file mode 100644
index 0000000..ef3efd6
--- /dev/null
+++ b/ui/js/spice/prng4.js
@@ -0,0 +1,79 @@
+// Downloaded from http://www-cs-students.stanford.edu/~tjw/jsbn/ by Jeremy White on 6/1/2012
+
+/*
+ * Copyright (c) 2003-2005 Tom Wu
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
+ * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * In addition, the following condition applies:
+ *
+ * All redistributions must retain an intact copy of this copyright notice
+ * and disclaimer.
+ */
+
+
+// prng4.js - uses Arcfour as a PRNG
+
+function Arcfour() {
+ this.i = 0;
+ this.j = 0;
+ this.S = new Array();
+}
+
+// Initialize arcfour context from key, an array of ints, each from [0..255]
+function ARC4init(key) {
+ var i, j, t;
+ for(i = 0; i < 256; ++i)
+ this.S[i] = i;
+ j = 0;
+ for(i = 0; i < 256; ++i) {
+ j = (j + this.S[i] + key[i % key.length]) & 255;
+ t = this.S[i];
+ this.S[i] = this.S[j];
+ this.S[j] = t;
+ }
+ this.i = 0;
+ this.j = 0;
+}
+
+function ARC4next() {
+ var t;
+ this.i = (this.i + 1) & 255;
+ this.j = (this.j + this.S[this.i]) & 255;
+ t = this.S[this.i];
+ this.S[this.i] = this.S[this.j];
+ this.S[this.j] = t;
+ return this.S[(t + this.S[this.i]) & 255];
+}
+
+Arcfour.prototype.init = ARC4init;
+Arcfour.prototype.next = ARC4next;
+
+// Plug in your RNG constructor here
+function prng_newstate() {
+ return new Arcfour();
+}
+
+// Pool size must be a multiple of 4 and greater than 32.
+// An array of bytes the size of the pool will be passed to init()
+var rng_psize = 256;
diff --git a/ui/js/spice/quic.js b/ui/js/spice/quic.js
new file mode 100644
index 0000000..9bb9f47
--- /dev/null
+++ b/ui/js/spice/quic.js
@@ -0,0 +1,1335 @@
+/*"use strict";*/
+/* use strict is commented out because it results in a 5x slowdone in chrome */
+/*
+ * Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+ * Copyright (C) 2012 by Aric Stewart <aric(a)codeweavers.com>
+ *
+ * This file is part of spice-html5.
+ *
+ * spice-html5 is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * spice-html5 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+var encoder;
+
+var QUIC_IMAGE_TYPE_INVALID = 0;
+var QUIC_IMAGE_TYPE_GRAY = 1;
+var QUIC_IMAGE_TYPE_RGB16 = 2;
+var QUIC_IMAGE_TYPE_RGB24 = 3;
+var QUIC_IMAGE_TYPE_RGB32 = 4;
+var QUIC_IMAGE_TYPE_RGBA = 5;
+var DEFevol = 3;
+var DEFwmimax = 6;
+var DEFwminext = 2048;
+var need_init = true;
+var DEFmaxclen = 26;
+var evol = DEFevol;
+var wmimax = DEFwmimax;
+var wminext = DEFwminext;
+var family_5bpc = { nGRcodewords:[0,0,0,0,0,0,0,0],
+ notGRcwlen:[0,0,0,0,0,0,0,0],
+ notGRprefixmask:[0,0,0,0,0,0,0,0],
+ notGRsuffixlen:[0,0,0,0,0,0,0,0],
+ xlatU2L:[0,0,0,0,0,0,0,0],
+ xlatL2U:[0,0,0,0,0,0,0,0]
+ };
+var family_8bpc = { nGRcodewords:[0,0,0,0,0,0,0,0],
+ notGRcwlen:[0,0,0,0,0,0,0,0],
+ notGRprefixmask:[0,0,0,0,0,0,0,0],
+ notGRsuffixlen:[0,0,0,0,0,0,0,0],
+ xlatU2L:[0,0,0,0,0,0,0,0],
+ xlatL2U:[0,0,0,0,0,0,0,0]
+ };
+var bppmask = [ 0x00000000,
+ 0x00000001, 0x00000003, 0x00000007, 0x0000000f,
+ 0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff,
+ 0x000001ff, 0x000003ff, 0x000007ff, 0x00000fff,
+ 0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff,
+ 0x0001ffff, 0x0003ffff, 0x0007ffff, 0x000fffff,
+ 0x001fffff, 0x003fffff, 0x007fffff, 0x00ffffff,
+ 0x01ffffff, 0x03ffffff, 0x07ffffff, 0x0fffffff,
+ 0x1fffffff, 0x3fffffff, 0x7fffffff, 0xffffffff];
+
+var zeroLUT = [];
+
+var besttrigtab = [
+ [ 550, 900, 800, 700, 500, 350, 300, 200, 180, 180, 160],
+ [ 110, 550, 900, 800, 550, 400, 350, 250, 140, 160, 140],
+ [ 100, 120, 550, 900, 700, 500, 400, 300, 220, 250, 160]];
+
+var J = [ 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 5, 5, 6, 6,
+ 7, 7, 8, 9, 10, 11, 12, 13, 14, 15];
+
+var lzeroes = [
+ 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0];
+
+var tabrand_chaos = [
+ 0x02c57542, 0x35427717, 0x2f5a2153, 0x9244f155, 0x7bd26d07, 0x354c6052,
+ 0x57329b28, 0x2993868e, 0x6cd8808c, 0x147b46e0, 0x99db66af, 0xe32b4cac,
+ 0x1b671264, 0x9d433486, 0x62a4c192, 0x06089a4b, 0x9e3dce44, 0xdaabee13,
+ 0x222425ea, 0xa46f331d, 0xcd589250, 0x8bb81d7f, 0xc8b736b9, 0x35948d33,
+ 0xd7ac7fd0, 0x5fbe2803, 0x2cfbc105, 0x013dbc4e, 0x7a37820f, 0x39f88e9e,
+ 0xedd58794, 0xc5076689, 0xfcada5a4, 0x64c2f46d, 0xb3ba3243, 0x8974b4f9,
+ 0x5a05aebd, 0x20afcd00, 0x39e2b008, 0x88a18a45, 0x600bde29, 0xf3971ace,
+ 0xf37b0a6b, 0x7041495b, 0x70b707ab, 0x06beffbb, 0x4206051f, 0xe13c4ee3,
+ 0xc1a78327, 0x91aa067c, 0x8295f72a, 0x732917a6, 0x1d871b4d, 0x4048f136,
+ 0xf1840e7e, 0x6a6048c1, 0x696cb71a, 0x7ff501c3, 0x0fc6310b, 0x57e0f83d,
+ 0x8cc26e74, 0x11a525a2, 0x946934c7, 0x7cd888f0, 0x8f9d8604, 0x4f86e73b,
+ 0x04520316, 0xdeeea20c, 0xf1def496, 0x67687288, 0xf540c5b2, 0x22401484,
+ 0x3478658a, 0xc2385746, 0x01979c2c, 0x5dad73c8, 0x0321f58b, 0xf0fedbee,
+ 0x92826ddf, 0x284bec73, 0x5b1a1975, 0x03df1e11, 0x20963e01, 0xa17cf12b,
+ 0x740d776e, 0xa7a6bf3c, 0x01b5cce4, 0x1118aa76, 0xfc6fac0a, 0xce927e9b,
+ 0x00bf2567, 0x806f216c, 0xbca69056, 0x795bd3e9, 0xc9dc4557, 0x8929b6c2,
+ 0x789d52ec, 0x3f3fbf40, 0xb9197368, 0xa38c15b5, 0xc3b44fa8, 0xca8333b0,
+ 0xb7e8d590, 0xbe807feb, 0xbf5f8360, 0xd99e2f5c, 0x372928e1, 0x7c757c4c,
+ 0x0db5b154, 0xc01ede02, 0x1fc86e78, 0x1f3985be, 0xb4805c77, 0x00c880fa,
+ 0x974c1b12, 0x35ab0214, 0xb2dc840d, 0x5b00ae37, 0xd313b026, 0xb260969d,
+ 0x7f4c8879, 0x1734c4d3, 0x49068631, 0xb9f6a021, 0x6b863e6f, 0xcee5debf,
+ 0x29f8c9fb, 0x53dd6880, 0x72b61223, 0x1f67a9fd, 0x0a0f6993, 0x13e59119,
+ 0x11cca12e, 0xfe6b6766, 0x16b6effc, 0x97918fc4, 0xc2b8a563, 0x94f2f741,
+ 0x0bfa8c9a, 0xd1537ae8, 0xc1da349c, 0x873c60ca, 0x95005b85, 0x9b5c080e,
+ 0xbc8abbd9, 0xe1eab1d2, 0x6dac9070, 0x4ea9ebf1, 0xe0cf30d4, 0x1ef5bd7b,
+ 0xd161043e, 0x5d2fa2e2, 0xff5d3cae, 0x86ed9f87, 0x2aa1daa1, 0xbd731a34,
+ 0x9e8f4b22, 0xb1c2c67a, 0xc21758c9, 0xa182215d, 0xccb01948, 0x8d168df7,
+ 0x04238cfe, 0x368c3dbc, 0x0aeadca5, 0xbad21c24, 0x0a71fee5, 0x9fc5d872,
+ 0x54c152c6, 0xfc329483, 0x6783384a, 0xeddb3e1c, 0x65f90e30, 0x884ad098,
+ 0xce81675a, 0x4b372f7d, 0x68bf9a39, 0x43445f1e, 0x40f8d8cb, 0x90d5acb6,
+ 0x4cd07282, 0x349eeb06, 0x0c9d5332, 0x520b24ef, 0x80020447, 0x67976491,
+ 0x2f931ca3, 0xfe9b0535, 0xfcd30220, 0x61a9e6cc, 0xa487d8d7, 0x3f7c5dd1,
+ 0x7d0127c5, 0x48f51d15, 0x60dea871, 0xc9a91cb7, 0x58b53bb3, 0x9d5e0b2d,
+ 0x624a78b4, 0x30dbee1b, 0x9bdf22e7, 0x1df5c299, 0x2d5643a7, 0xf4dd35ff,
+ 0x03ca8fd6, 0x53b47ed8, 0x6f2c19aa, 0xfeb0c1f4, 0x49e54438, 0x2f2577e6,
+ 0xbf876969, 0x72440ea9, 0xfa0bafb8, 0x74f5b3a0, 0x7dd357cd, 0x89ce1358,
+ 0x6ef2cdda, 0x1e7767f3, 0xa6be9fdb, 0x4f5f88f8, 0xba994a3a, 0x08ca6b65,
+ 0xe0893818, 0x9e00a16a, 0xf42bfc8f, 0x9972eedc, 0x749c8b51, 0x32c05f5e,
+ 0xd706805f, 0x6bfbb7cf, 0xd9210a10, 0x31a1db97, 0x923a9559, 0x37a7a1f6,
+ 0x059f8861, 0xca493e62, 0x65157e81, 0x8f6467dd, 0xab85ff9f, 0x9331aff2,
+ 0x8616b9f5, 0xedbd5695, 0xee7e29b1, 0x313ac44f, 0xb903112f, 0x432ef649,
+ 0xdc0a36c0, 0x61cf2bba, 0x81474925, 0xa8b6c7ad, 0xee5931de, 0xb2f8158d,
+ 0x59fb7409, 0x2e3dfaed, 0x9af25a3f, 0xe1fed4d5 ];
+
+var rgb32_pixel_pad = 3;
+var rgb32_pixel_r = 2;
+var rgb32_pixel_g = 1;
+var rgb32_pixel_b = 0;
+var rgb32_pixel_size = 4;
+
+/* Helper Functions */
+
+function ceil_log_2(val)
+{
+ if (val === 1)
+ return 0;
+
+ var result = 1;
+ val -= 1;
+ while (val = val >>> 1)
+ result++;
+
+ return result;
+}
+
+function family_init(family, bpc, limit)
+{
+ var l;
+ for (l = 0; l < bpc; l++)
+ {
+ var altprefixlen, altcodewords;
+ altprefixlen = limit - bpc;
+ if (altprefixlen > bppmask[bpc - l])
+ altprefixlen = bppmask[bpc - l];
+
+ altcodewords = bppmask[bpc] + 1 - (altprefixlen << l);
+ family.nGRcodewords[l] = (altprefixlen << l);
+ family.notGRcwlen[l] = altprefixlen + ceil_log_2(altcodewords);
+ family.notGRprefixmask[l] = bppmask[32 - altprefixlen]>>>0;
+ family.notGRsuffixlen[l] = ceil_log_2(altcodewords);
+ }
+
+ /* decorelate_init */
+ var pixelbitmask = bppmask[bpc];
+ var pixelbitmaskshr = pixelbitmask >>> 1;
+ var s;
+ for (s = 0; s <= pixelbitmask; s++) {
+ if (s <= pixelbitmaskshr) {
+ family.xlatU2L[s] = s << 1;
+ } else {
+ family.xlatU2L[s] = ((pixelbitmask - s) << 1) + 1;
+ }
+ }
+
+ /* corelate_init */
+ for (s = 0; s <= pixelbitmask; s++) {
+ if (s & 0x01) {
+ family.xlatL2U[s] = pixelbitmask - (s >>> 1);
+ } else {
+ family.xlatL2U[s] = (s >>> 1);
+ }
+ }
+}
+
+function quic_image_bpc(type)
+{
+ switch (type) {
+ case QUIC_IMAGE_TYPE_GRAY:
+ return 8;
+ case QUIC_IMAGE_TYPE_RGB16:
+ return 5;
+ case QUIC_IMAGE_TYPE_RGB24:
+ return 8;
+ case QUIC_IMAGE_TYPE_RGB32:
+ return 8;
+ case QUIC_IMAGE_TYPE_RGBA:
+ return 8;
+ case QUIC_IMAGE_TYPE_INVALID:
+ default:
+ console.log("quic: bad image type\n");
+ return 0;
+ }
+}
+
+function cnt_l_zeroes(bits)
+{
+ if (bits & 0xff800000) {
+ return lzeroes[bits >>> 24];
+ } else if (bits & 0xffff8000) {
+ return 8 + lzeroes[(bits >>> 16) & 0x000000ff];
+ } else if (bits & 0xffffff80) {
+ return 16 + lzeroes[(bits >>> 8) & 0x000000ff];
+ } else {
+ return 24 + lzeroes[bits & 0x000000ff];
+ }
+}
+
+function golomb_decoding_8bpc(l, bits)
+{
+ var rc;
+ var cwlen;
+
+ if (bits < 0 || bits > family_8bpc.notGRprefixmask[l])
+ {
+ var zeroprefix = cnt_l_zeroes(bits);
+ cwlen = zeroprefix + 1 + l;
+ rc = (zeroprefix << l) | (bits >> (32-cwlen)) & bppmask[l];
+ }
+ else
+ {
+ cwlen = family_8bpc.notGRcwlen[l];
+ rc = family_8bpc.nGRcodewords[l] + ((bits >> (32-cwlen)) & bppmask[family_8bpc.notGRsuffixlen[l]]);
+ }
+ return {'codewordlen':cwlen, 'rc':rc};
+}
+
+function golomb_code_len_8bpc(n, l)
+{
+ if (n < family_8bpc.nGRcodewords[l]) {
+ return (n >>> l) + 1 + l;
+ } else {
+ return family_8bpc.notGRcwlen[l];
+ }
+}
+
+function QuicModel(bpc)
+{
+ var bstart;
+ var bend = 0;
+
+ this.levels = 0x1 << bpc;
+ this.n_buckets_ptrs = 0;
+
+ switch (evol) {
+ case 1:
+ this.repfirst = 3;
+ this.firstsize = 1;
+ this.repnext = 2;
+ this.mulsize = 2;
+ break;
+ case 3:
+ this.repfirst = 1;
+ this.firstsize = 1;
+ this.repnext = 1;
+ this.mulsize = 2;
+ break;
+ case 5:
+ this.repfirst = 1;
+ this.firstsize = 1;
+ this.repnext = 1;
+ this.mulsize = 4;
+ break;
+ case 0:
+ case 2:
+ case 4:
+ console.log("quic: findmodelparams(): evol value obsolete!!!\n");
+ default:
+ console.log("quic: findmodelparams(): evol out of range!!!\n");
+ }
+
+ this.n_buckets = 0;
+ var repcntr = this.repfirst + 1;
+ var bsize = this.firstsize;
+
+ do {
+ if (this.n_buckets) {
+ bstart = bend + 1;
+ } else {
+ bstart = 0;
+ }
+
+ if (!--repcntr) {
+ repcntr = this.repnext;
+ bsize *= this.mulsize;
+ }
+
+ bend = bstart + bsize - 1;
+ if (bend + bsize >= this.levels) {
+ bend = this.levels - 1;
+ }
+
+ if (!this.n_buckets_ptrs) {
+ this.n_buckets_ptrs = this.levels;
+ }
+
+ (this.n_buckets)++;
+ } while (bend < this.levels - 1);
+}
+
+QuicModel.prototype = {
+ n_buckets : 0,
+ n_buckets_ptrs : 0,
+ repfirst : 0,
+ firstsize : 0,
+ repnext : 0,
+ mulsize : 0,
+ levels :0
+}
+
+function QuicBucket()
+{
+ this.counters = [0,0,0,0,0,0,0,0];
+}
+
+QuicBucket.prototype = {
+ bestcode: 0,
+
+ reste : function (bpp)
+ {
+ this.bestcode = bpp;
+ this.counters = [0,0,0,0,0,0,0,0];
+ },
+
+ update_model_8bpc : function (state, curval, bpp)
+ {
+ var i;
+
+ var bestcode = bpp - 1;
+ var bestcodelen = (this.counters[bestcode] += golomb_code_len_8bpc(curval, bestcode));
+
+ for (i = bpp - 2; i >= 0; i--) {
+ var ithcodelen = (this.counters[i] += golomb_code_len_8bpc(curval, i));
+
+ if (ithcodelen < bestcodelen) {
+ bestcode = i;
+ bestcodelen = ithcodelen;
+ }
+ }
+
+ this.bestcode = bestcode;
+
+ if (bestcodelen > state.wm_trigger) {
+ for (i = 0; i < bpp; i++) {
+ this.counters[i] = this.counters[i] >>> 1;
+ }
+ }
+ }
+}
+
+function QuicFamilyStat()
+{
+ this.buckets_ptrs = [];
+ this.buckets_buf = [];
+}
+
+QuicFamilyStat.prototype = {
+
+ fill_model_structures : function(model)
+ {
+ var bstart;
+ var bend = 0;
+ var bnumber = 0;
+
+ var repcntr = model.repfirst + 1;
+ var bsize = model.firstsize;
+
+ do {
+ if (bnumber) {
+ bstart = bend + 1;
+ } else {
+ bstart = 0;
+ }
+
+ if (!--repcntr) {
+ repcntr = model.repnext;
+ bsize *= model.mulsize;
+ }
+
+ bend = bstart + bsize - 1;
+ if (bend + bsize >= model.levels) {
+ bend = model.levels - 1;
+ }
+
+ this.buckets_buf[bnumber] = new QuicBucket;
+
+ var i;
+ for (i = bstart; i <= bend; i++) {
+ this.buckets_ptrs[i] = this.buckets_buf[bnumber];
+ }
+
+ bnumber++;
+ } while (bend < model.levels - 1);
+ return true;
+ }
+}
+
+function QuicChannel(model_8bpc, model_5bpc)
+{
+ this.state = new CommonState;
+ this.family_stat_8bpc = new QuicFamilyStat;
+ this.family_stat_5bpc = new QuicFamilyStat;
+ this.correlate_row = { zero: 0 , row:[] };
+ this.model_8bpc = model_8bpc;
+ this.model_5bpc = model_5bpc;
+ this.buckets_ptrs = [];
+
+ if (!this.family_stat_8bpc.fill_model_structures(this.model_8bpc))
+ return undefined;
+
+ if (!this.family_stat_5bpc.fill_model_structures(this.model_5bpc))
+ return undefined;
+}
+
+QuicChannel.prototype = {
+
+ reste : function (bpc)
+ {
+ var j;
+ this.correlate_row = { zero: 0 , row: []};
+
+ if (bpc == 8) {
+ for (j = 0; j < this.model_8bpc.n_buckets; j++)
+ this.family_stat_8bpc.buckets_buf[j].reste(7);
+ this.buckets_ptrs = this.family_stat_8bpc.buckets_ptrs;
+ } else if (bpc == 5) {
+ for (j = 0; j < this.model_5bpc.n_buckets; j++)
+ this.family_stat_8bpc.buckets_buf[j].reste(4);
+ this.buckets_ptrs = this.family_stat_5bpc.buckets_ptrs;
+ } else {
+ console.log("quic: %s: bad bpc %d\n", __FUNCTION__, bpc);
+ return false;
+ }
+
+ this.state.reste();
+ return true;
+ }
+}
+
+function CommonState()
+{
+}
+
+CommonState.prototype = {
+ waitcnt: 0,
+ tabrand_seed: 0xff,
+ wm_trigger: 0,
+ wmidx: 0,
+ wmileft: wminext,
+ melcstate: 0,
+ melclen: 0,
+ melcorder: 0,
+
+ set_wm_trigger : function()
+ {
+ var wm = this.wmidx;
+ if (wm > 10) {
+ wm = 10;
+ }
+
+ this.wm_trigger = besttrigtab[Math.floor(evol / 2)][wm];
+ },
+
+ reste : function()
+ {
+ this.waitcnt = 0;
+ this.tabrand_seed = 0x0ff;
+ this.wmidx = 0;
+ this.wmileft = wminext;
+
+ this.set_wm_trigger();
+
+ this.melcstate = 0;
+ this.melclen = J[0];
+ this.melcorder = 1 << this.melclen;
+ },
+
+ tabrand : function()
+ {
+ this.tabrand_seed++;
+ return tabrand_chaos[this.tabrand_seed & 0x0ff];
+ }
+}
+
+
+function QuicEncoder()
+{
+ this.rgb_state = new CommonState;
+ this.model_8bpc = new QuicModel(8);
+ this.model_5bpc = new QuicModel(5);
+ this.channels = [];
+
+ var i;
+ for (i = 0; i < 4; i++) {
+ this.channels[i] = new QuicChannel(this.model_8bpc, this.model_5bpc);
+ if (!this.channels[i])
+ {
+ console.log("quic: failed to create channel");
+ return undefined;
+ }
+ }
+}
+
+QuicEncoder.prototype = {
+ type: 0,
+ width: 0,
+ height: 0,
+ io_idx: 0,
+ io_available_bits: 0,
+ io_word: 0,
+ io_next_word: 0,
+ io_now: 0,
+ io_end: 0,
+ rows_completed: 0,
+ };
+
+QuicEncoder.prototype.reste = function(io_ptr)
+{
+ this.rgb_state.reste();
+
+ this.io_now = io_ptr;
+ this.io_end = this.io_now.length;
+ this.io_idx = 0;
+ this.rows_completed = 0;
+ return true;
+}
+
+QuicEncoder.prototype.read_io_word = function()
+{
+ if (this.io_idx >= this.io_end)
+ throw("quic: out of data");
+ this.io_next_word = this.io_now[this.io_idx++] | this.io_now[this.io_idx++]<<8 | this.io_now[this.io_idx++]<<16 | this.io_now[this.io_idx++]<<24;
+}
+
+QuicEncoder.prototype.decode_eatbits = function (len)
+{
+ this.io_word = this.io_word << len;
+
+ var delta = (this.io_available_bits - len);
+ if (delta >= 0)
+ {
+ this.io_available_bits = delta;
+ this.io_word |= this.io_next_word >>> this.io_available_bits;
+ }
+ else
+ {
+ delta = -1 * delta;
+ this.io_word |= this.io_next_word << delta;
+ this.read_io_word();
+ this.io_available_bits = 32 - delta;
+ this.io_word |= this.io_next_word >>> this.io_available_bits;
+ }
+}
+
+QuicEncoder.prototype.decode_eat32bits = function()
+{
+ this.decode_eatbits(16);
+ this.decode_eatbits(16);
+}
+
+QuicEncoder.prototype.reste_channels = function(bpc)
+{
+ var i;
+
+ for (i = 0; i < 4; i++)
+ if (!this.channels[i].reste(bpc))
+ return false;
+ return true;
+}
+
+QuicEncoder.prototype.quic_decode_begin = function(io_ptr)
+{
+ if (!this.reste(io_ptr)) {
+ return false;
+ }
+
+ this.io_idx = 0;
+ this.io_next_word = this.io_now[this.io_idx++] | this.io_now[this.io_idx++]<<8 | this.io_now[this.io_idx++]<<16 | this.io_now[this.io_idx++]<<24;
+ this.io_word = this.io_next_word;
+ this.io_available_bits = 0;
+
+ var magic = this.io_word;
+ this.decode_eat32bits();
+ if (magic != 0x43495551) /*QUIC*/ {
+ console.log("quic: bad magic "+magic.toString(16));
+ return false;
+ }
+
+ var version = this.io_word;
+ this.decode_eat32bits();
+ if (version != ((0 << 16) | (0 & 0xffff))) {
+ console.log("quic: bad version "+version.toString(16));
+ return false;
+ }
+
+ this.type = this.io_word;
+ this.decode_eat32bits();
+
+ this.width = this.io_word;
+ this.decode_eat32bits();
+
+ this.height = this.io_word;
+ this.decode_eat32bits();
+
+ var bpc = quic_image_bpc(this.type);
+
+ if (!this.reste_channels(bpc))
+ return false;
+
+ return true;
+}
+
+QuicEncoder.prototype.quic_rgb32_uncompress_row0_seg = function (i, cur_row, end,
+ waitmask, bpc, bpc_mask)
+{
+ var stopidx;
+ var n_channels = 3;
+ var c;
+ var a;
+
+ if (!i) {
+ cur_row[rgb32_pixel_pad] = 0;
+ c = 0;
+ do
+ {
+ a = golomb_decoding_8bpc(this.channels[c].buckets_ptrs[this.channels[c].correlate_row.zero].bestcode, this.io_word);
+ this.channels[c].correlate_row.row[0] = a.rc;
+ cur_row[2-c] = (family_8bpc.xlatL2U[a.rc]&0xFF);
+ this.decode_eatbits(a.codewordlen);
+ } while (++c < n_channels);
+
+ if (this.rgb_state.waitcnt) {
+ --this.rgb_state.waitcnt;
+ } else {
+ this.rgb_state.waitcnt = (this.rgb_state.tabrand() & waitmask);
+ c = 0;
+ do
+ {
+ this.channels[c].buckets_ptrs[this.channels[c].correlate_row.zero].update_model_8bpc(this.rgb_state, this.channels[c].correlate_row.row[0], bpc);
+ } while (++c < n_channels);
+ }
+ stopidx = ++i + this.rgb_state.waitcnt;
+ } else {
+ stopidx = i + this.rgb_state.waitcnt;
+ }
+
+ while (stopidx < end) {
+ for (; i <= stopidx; i++) {
+ cur_row[(i* rgb32_pixel_size)+rgb32_pixel_pad] = 0;
+ c = 0;
+ do
+ {
+ a = golomb_decoding_8bpc(this.channels[c].buckets_ptrs[this.channels[c].correlate_row.row[i - 1]].bestcode, this.io_word);
+ this.channels[c].correlate_row.row[i] = a.rc;
+ cur_row[(i* rgb32_pixel_size)+(2-c)] = (family_8bpc.xlatL2U[a.rc] + cur_row[((i-1) * rgb32_pixel_size) + (2-c)]) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ } while (++c < n_channels);
+ }
+ c = 0;
+ do
+ {
+ this.channels[c].buckets_ptrs[this.channels[c].correlate_row.row[stopidx - 1]].update_model_8bpc(this.rgb_state, this.channels[c].correlate_row.row[stopidx], bpc);
+ } while (++c < n_channels);
+ stopidx = i + (this.rgb_state.tabrand() & waitmask);
+ }
+
+ for (; i < end; i++) {
+ cur_row[(i* rgb32_pixel_size)+rgb32_pixel_pad] = 0;
+ c = 0;
+ do
+ {
+ a = golomb_decoding_8bpc(this.channels[c].buckets_ptrs[this.channels[c].correlate_row.row[i - 1]].bestcode, this.io_word);
+ this.channels[c].correlate_row.row[i] = a.rc;
+ cur_row[(i* rgb32_pixel_size)+(2-c)] = (family_8bpc.xlatL2U[a.rc] + cur_row[((i-1) * rgb32_pixel_size) + (2-c)]) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ } while (++c < n_channels);
+ }
+ this.rgb_state.waitcnt = stopidx - end;
+}
+
+QuicEncoder.prototype.quic_rgb32_uncompress_row0 = function (cur_row)
+{
+ var bpc = 8;
+ var bpc_mask = 0xff;
+ var pos = 0;
+ var width = this.width;
+
+ while ((wmimax > this.rgb_state.wmidx) && (this.rgb_state.wmileft <= width)) {
+ if (this.rgb_state.wmileft) {
+ this.quic_rgb32_uncompress_row0_seg(pos, cur_row,
+ pos + this.rgb_state.wmileft,
+ bppmask[this.rgb_state.wmidx],
+ bpc, bpc_mask);
+ pos += this.rgb_state.wmileft;
+ width -= this.rgb_state.wmileft;
+ }
+
+ this.rgb_state.wmidx++;
+ this.rgb_state.set_wm_trigger();
+ this.rgb_state.wmileft = wminext;
+ }
+
+ if (width) {
+ this.quic_rgb32_uncompress_row0_seg(pos, cur_row, pos + width,
+ bppmask[this.rgb_state.wmidx], bpc, bpc_mask);
+ if (wmimax > this.rgb_state.wmidx) {
+ this.rgb_state.wmileft -= width;
+ }
+ }
+}
+
+QuicEncoder.prototype.quic_rgb32_uncompress_row_seg = function( prev_row, cur_row, i, end, bpc, bpc_mask)
+{
+ var n_channels = 3;
+ var waitmask = bppmask[this.rgb_state.wmidx];
+
+ var a;
+ var run_index = 0;
+ var stopidx = 0;
+ var run_end = 0;
+ var c;
+
+ if (!i)
+ {
+ cur_row[rgb32_pixel_pad] = 0;
+
+ c = 0;
+ do {
+ a = golomb_decoding_8bpc(this.channels[c].buckets_ptrs[this.channels[c].correlate_row.zero].bestcode, this.io_word);
+ this.channels[c].correlate_row.row[0] = a.rc;
+ cur_row[2-c] = (family_8bpc.xlatL2U[this.channels[c].correlate_row.row[0]] + prev_row[2-c]) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ } while (++c < n_channels);
+
+ if (this.rgb_state.waitcnt) {
+ --this.rgb_state.waitcnt;
+ } else {
+ this.rgb_state.waitcnt = (this.rgb_state.tabrand() & waitmask);
+ c = 0;
+ do {
+ this.channels[c].buckets_ptrs[this.channels[c].correlate_row.zero].update_model_8bpc(this.rgb_state, this.channels[c].correlate_row.row[0], bpc);
+ } while (++c < n_channels);
+ }
+ stopidx = ++i + this.rgb_state.waitcnt;
+ } else {
+ stopidx = i + this.rgb_state.waitcnt;
+ }
+ for (;;) {
+ var rc = 0;
+ while (stopidx < end && !rc) {
+ for (; i <= stopidx && !rc; i++) {
+ var pixel = i * rgb32_pixel_size;
+ var pixelm1 = (i-1) * rgb32_pixel_size;
+ var pixelm2 = (i-2) * rgb32_pixel_size;
+
+ if ( prev_row[pixelm1+rgb32_pixel_r] == prev_row[pixel+rgb32_pixel_r] && prev_row[pixelm1+rgb32_pixel_g] == prev_row[pixel+rgb32_pixel_g] && prev_row[pixelm1 + rgb32_pixel_b] == prev_row[pixel+rgb32_pixel_b])
+ {
+ if (run_index != i && i > 2 && (cur_row[pixelm1+rgb32_pixel_r] == cur_row[pixelm2+rgb32_pixel_r] && cur_row[pixelm1+rgb32_pixel_g] == cur_row[pixelm2+rgb32_pixel_g] && cur_row[pixelm1+rgb32_pixel_b] == cur_row[pixelm2+rgb32_pixel_b]))
+ {
+ /* do run */
+ this.rgb_state.waitcnt = stopidx - i;
+ run_index = i;
+ run_end = i + this.decode_run(this.rgb_state);
+
+ for (; i < run_end; i++) {
+ var pixel = i * rgb32_pixel_size;
+ var pixelm1 = (i-1) * rgb32_pixel_size;
+ cur_row[pixel+rgb32_pixel_pad] = 0;
+ cur_row[pixel+rgb32_pixel_r] = cur_row[pixelm1+rgb32_pixel_r];
+ cur_row[pixel+rgb32_pixel_g] = cur_row[pixelm1+rgb32_pixel_g];
+ cur_row[pixel+rgb32_pixel_b] = cur_row[pixelm1+rgb32_pixel_b];
+ }
+
+ if (i == end) {
+ return;
+ }
+ else
+ {
+ stopidx = i + this.rgb_state.waitcnt;
+ rc = 1;
+ break;
+ }
+ }
+ }
+
+ c = 0;
+ cur_row[pixel+rgb32_pixel_pad] = 0;
+ do {
+ var cc = this.channels[c];
+ var cr = cc.correlate_row;
+
+ a = golomb_decoding_8bpc(cc.buckets_ptrs[cr.row[i-1]].bestcode, this.io_word);
+ cr.row[i] = a.rc;
+ cur_row[pixel+(2-c)] = (family_8bpc.xlatL2U[a.rc] + ((cur_row[pixelm1+(2-c)] + prev_row[pixel+(2-c)]) >> 1)) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ } while (++c < n_channels);
+ }
+ if (rc)
+ break;
+
+ c = 0;
+ do {
+ this.channels[c].buckets_ptrs[this.channels[c].correlate_row.row[stopidx - 1]].update_model_8bpc(this.rgb_state, this.channels[c].correlate_row.row[stopidx], bpc);
+ } while (++c < n_channels);
+
+ stopidx = i + (this.rgb_state.tabrand() & waitmask);
+ }
+
+ for (; i < end && !rc; i++) {
+ var pixel = i * rgb32_pixel_size;
+ var pixelm1 = (i-1) * rgb32_pixel_size;
+ var pixelm2 = (i-2) * rgb32_pixel_size;
+
+ if (prev_row[pixelm1+rgb32_pixel_r] == prev_row[pixel+rgb32_pixel_r] && prev_row[pixelm1+rgb32_pixel_g] == prev_row[pixel+rgb32_pixel_g] && prev_row[pixelm1+rgb32_pixel_b] == prev_row[pixel+rgb32_pixel_b])
+ {
+ if (run_index != i && i > 2 && (cur_row[pixelm1+rgb32_pixel_r] == cur_row[pixelm2+rgb32_pixel_r] && cur_row[pixelm1+rgb32_pixel_g] == cur_row[pixelm2+rgb32_pixel_g] && cur_row[pixelm1+rgb32_pixel_b] == cur_row[pixelm2+rgb32_pixel_b]))
+ {
+ /* do run */
+ this.rgb_state.waitcnt = stopidx - i;
+ run_index = i;
+ run_end = i + this.decode_run(this.rgb_state);
+
+ for (; i < run_end; i++) {
+ var pixel = i * rgb32_pixel_size;
+ var pixelm1 = (i-1) * rgb32_pixel_size;
+ cur_row[pixel+rgb32_pixel_pad] = 0;
+ cur_row[pixel+rgb32_pixel_r] = cur_row[pixelm1+rgb32_pixel_r];
+ cur_row[pixel+rgb32_pixel_g] = cur_row[pixelm1+rgb32_pixel_g];
+ cur_row[pixel+rgb32_pixel_b] = cur_row[pixelm1+rgb32_pixel_b];
+ }
+
+ if (i == end) {
+ return;
+ }
+ else
+ {
+ stopidx = i + this.rgb_state.waitcnt;
+ rc = 1;
+ break;
+ }
+ }
+ }
+
+ cur_row[pixel+rgb32_pixel_pad] = 0;
+ c = 0;
+ do
+ {
+ a = golomb_decoding_8bpc(this.channels[c].buckets_ptrs[this.channels[c].correlate_row.row[i-1]].bestcode, this.io_word);
+ this.channels[c].correlate_row.row[i] = a.rc;
+ cur_row[pixel+(2-c)] = (family_8bpc.xlatL2U[a.rc] + ((cur_row[pixelm1+(2-c)] + prev_row[pixel+(2-c)]) >> 1)) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ } while (++c < n_channels);
+ }
+
+ if (!rc)
+ {
+ this.rgb_state.waitcnt = stopidx - end;
+ return;
+ }
+ }
+}
+
+QuicEncoder.prototype.decode_run = function(state)
+{
+ var runlen = 0;
+
+ do {
+ var hits;
+ var x = (~(this.io_word >>> 24)>>>0)&0xff;
+ var temp = zeroLUT[x];
+
+ for (hits = 1; hits <= temp; hits++) {
+ runlen += state.melcorder;
+
+ if (state.melcstate < 32) {
+ state.melclen = J[++state.melcstate];
+ state.melcorder = (1 << state.melclen);
+ }
+ }
+ if (temp != 8) {
+ this.decode_eatbits(temp + 1);
+
+ break;
+ }
+ this.decode_eatbits(8);
+ } while (true);
+
+ if (state.melclen) {
+ runlen += this.io_word >>> (32 - state.melclen);
+ this.decode_eatbits(state.melclen);
+ }
+
+ if (state.melcstate) {
+ state.melclen = J[--state.melcstate];
+ state.melcorder = (1 << state.melclen);
+ }
+
+ return runlen;
+}
+
+QuicEncoder.prototype.quic_rgb32_uncompress_row = function (prev_row, cur_row)
+{
+ var bpc = 8;
+ var bpc_mask = 0xff;
+ var pos = 0;
+ var width = this.width;
+
+ while ((wmimax > this.rgb_state.wmidx) && (this.rgb_state.wmileft <= width)) {
+ if (this.rgb_state.wmileft) {
+ this.quic_rgb32_uncompress_row_seg(prev_row, cur_row, pos,
+ pos + this.rgb_state.wmileft, bpc, bpc_mask);
+ pos += this.rgb_state.wmileft;
+ width -= this.rgb_state.wmileft;
+ }
+
+ this.rgb_state.wmidx++;
+ this.rgb_state.set_wm_trigger();
+ this.rgb_state.wmileft = wminext;
+ }
+
+ if (width) {
+ this.quic_rgb32_uncompress_row_seg(prev_row, cur_row, pos,
+ pos + width, bpc, bpc_mask);
+ if (wmimax > this.rgb_state.wmidx) {
+ this.rgb_state.wmileft -= width;
+ }
+ }
+}
+
+QuicEncoder.prototype.quic_four_uncompress_row0_seg = function (channel, i,
+ correlate_row, cur_row, end, waitmask,
+ bpc, bpc_mask)
+{
+ var stopidx;
+ var a;
+
+ if (i == 0) {
+ a = golomb_decoding_8bpc(channel.buckets_ptrs[correlate_row.zero].bestcode, this.io_word);
+ correlate_row.row[0] = a.rc;
+ cur_row[rgb32_pixel_pad] = family_8bpc.xlatL2U[a.rc];
+ this.decode_eatbits(a.codewordlen);
+
+ if (channel.state.waitcnt) {
+ --channel.state.waitcnt;
+ } else {
+ channel.state.waitcnt = (channel.state.tabrand() & waitmask);
+ channel.buckets_ptrs[correlate_row.zero].update_model_8bpc(channel.state, correlate_row.row[0], bpc);
+ }
+ stopidx = ++i + channel.state.waitcnt;
+ } else {
+ stopidx = i + channel.state.waitcnt;
+ }
+
+ while (stopidx < end) {
+ var pbucket;
+
+ for (; i <= stopidx; i++) {
+ pbucket = channel.buckets_ptrs[correlate_row.row[i - 1]];
+
+ a = golomb_decoding_8bpc(pbucket.bestcode, this.io_word);
+ correlate_row.row[i] = a.rc;
+ cur_row[(i*rgb32_pixel_size)+rgb32_pixel_pad] = (family_8bpc.xlatL2U[a.rc] + cur_row[((i-1)*rgb32_pixel_size)+rgb32_pixel_pad]) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ }
+
+ pbucket.update_model_8bpc(channel.state, correlate_row.row[stopidx], bpc);
+
+ stopidx = i + (channel.state.tabrand() & waitmask);
+ }
+
+ for (; i < end; i++) {
+ a = golomb_decoding_8bpc(channel.buckets_ptrs[correlate_row.row[i-1]].bestcode, this.io_word);
+
+ correlate_row.row[i] = a.rc;
+ cur_row[(i*rgb32_pixel_size)+rgb32_pixel_pad] = (family_8bpc.xlatL2U[a.rc] + cur_row[((i-1)*rgb32_pixel_size)+rgb32_pixel_pad]) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ }
+ channel.state.waitcnt = stopidx - end;
+}
+
+QuicEncoder.prototype.quic_four_uncompress_row0 = function(channel, cur_row)
+{
+ var bpc = 8;
+ var bpc_mask = 0xff;
+ var correlate_row = channel.correlate_row;
+ var pos = 0;
+ var width = this.width;
+
+ while ((wmimax > channel.state.wmidx) && (channel.state.wmileft <= width)) {
+ if (channel.state.wmileft) {
+ this.quic_four_uncompress_row0_seg(channel, pos, correlate_row, cur_row,
+ pos + channel.state.wmileft, bppmask[channel.state.wmidx],
+ bpc, bpc_mask);
+ pos += channel.state.wmileft;
+ width -= channel.state.wmileft;
+ }
+
+ channel.state.wmidx++;
+ channel.state.set_wm_trigger();
+ channel.state.wmileft = wminext;
+ }
+
+ if (width) {
+ this.quic_four_uncompress_row0_seg(channel, pos, correlate_row, cur_row, pos + width,
+ bppmask[channel.state.wmidx], bpc, bpc_mask);
+ if (wmimax > channel.state.wmidx) {
+ channel.state.wmileft -= width;
+ }
+ }
+}
+
+QuicEncoder.prototype.quic_four_uncompress_row_seg = function (channel,
+ correlate_row, prev_row, cur_row, i,
+ end, bpc, bpc_mask)
+{
+ var waitmask = bppmask[channel.state.wmidx];
+ var stopidx;
+
+ var run_index = 0;
+ var run_end;
+
+ var a;
+
+ if (i == 0) {
+ a = golomb_decoding_8bpc(channel.buckets_ptrs[correlate_row.zero].bestcode, this.io_word);
+
+ correlate_row.row[0] = a.rc
+ cur_row[rgb32_pixel_pad] = (family_8bpc.xlatL2U[a.rc] + prev_row[rgb32_pixel_pad]) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+
+ if (channel.state.waitcnt) {
+ --channel.state.waitcnt;
+ } else {
+ channel.state.waitcnt = (channel.state.tabrand() & waitmask);
+ channel.buckets_ptrs[correlate_row.zero].update_model_8bpc(channel.state, correlate_row.row[0], bpc);
+ }
+ stopidx = ++i + channel.state.waitcnt;
+ } else {
+ stopidx = i + channel.state.waitcnt;
+ }
+ for (;;) {
+ var rc = 0;
+ while (stopidx < end && !rc) {
+ var pbucket;
+ for (; i <= stopidx && !rc; i++) {
+ var pixel = i * rgb32_pixel_size;
+ var pixelm1 = (i-1) * rgb32_pixel_size;
+ var pixelm2 = (i-2) * rgb32_pixel_size;
+
+ if (prev_row[pixelm1+rgb32_pixel_pad] == prev_row[pixel+rgb32_pixel_pad])
+ {
+ if (run_index != i && i > 2 && cur_row[pixelm1+rgb32_pixel_pad] == cur_row[pixelm2+rgb32_pixel_pad])
+ {
+ /* do run */
+ channel.state.waitcnt = stopidx - i;
+ run_index = i;
+
+ run_end = i + this.decode_run(channel.state);
+
+ for (; i < run_end; i++) {
+ var pixel = i * rgb32_pixel_size;
+ var pixelm1 = (i-1) * rgb32_pixel_size;
+ cur_row[pixel+rgb32_pixel_pad] = cur_row[pixelm1+rgb32_pixel_pad];
+ }
+
+ if (i == end) {
+ return;
+ }
+ else
+ {
+ stopidx = i + channel.state.waitcnt;
+ rc = 1;
+ break;
+ }
+ }
+ }
+
+ pbucket = channel.buckets_ptrs[correlate_row.row[i - 1]];
+ a = golomb_decoding_8bpc(pbucket.bestcode, this.io_word);
+ correlate_row.row[i] = a.rc
+ cur_row[pixel+rgb32_pixel_pad] = (family_8bpc.xlatL2U[a.rc] + ((cur_row[pixelm1+rgb32_pixel_pad] + prev_row[pixel+rgb32_pixel_pad]) >> 1)) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ }
+ if (rc)
+ break;
+
+ pbucket.update_model_8bpc(channel.state, correlate_row.row[stopidx], bpc);
+
+ stopidx = i + (channel.state.tabrand() & waitmask);
+ }
+
+ for (; i < end && !rc; i++) {
+ var pixel = i * rgb32_pixel_size;
+ var pixelm1 = (i-1) * rgb32_pixel_size;
+ var pixelm2 = (i-2) * rgb32_pixel_size;
+ if (prev_row[pixelm1+rgb32_pixel_pad] == prev_row[pixel+rgb32_pixel_pad])
+ {
+ if (run_index != i && i > 2 && cur_row[pixelm1+rgb32_pixel_pad] == cur_row[pixelm2+rgb32_pixel_pad])
+ {
+ /* do run */
+ channel.state.waitcnt = stopidx - i;
+ run_index = i;
+
+ run_end = i + this.decode_run(channel.state);
+
+ for (; i < run_end; i++) {
+ var pixel = i * rgb32_pixel_size;
+ var pixelm1 = (i-1) * rgb32_pixel_size;
+ cur_row[pixel+rgb32_pixel_pad] = cur_row[pixelm1+rgb32_pixel_pad];
+ }
+
+ if (i == end) {
+ return;
+ }
+ else
+ {
+ stopidx = i + channel.state.waitcnt;
+ rc = 1;
+ break;
+ }
+ }
+ }
+
+ a = golomb_decoding_8bpc(channel.buckets_ptrs[correlate_row.row[i-1]].bestcode, this.io_word);
+ correlate_row.row[i] = a.rc;
+ cur_row[pixel+rgb32_pixel_pad] = (family_8bpc.xlatL2U[a.rc] + ((cur_row[pixelm1+rgb32_pixel_pad] + prev_row[pixel+rgb32_pixel_pad]) >> 1)) & bpc_mask;
+ this.decode_eatbits(a.codewordlen);
+ }
+
+ if (!rc)
+ {
+ channel.state.waitcnt = stopidx - end;
+ return;
+ }
+ }
+}
+
+QuicEncoder.prototype.quic_four_uncompress_row = function(channel, prev_row,
+ cur_row)
+{
+ var bpc = 8;
+ var bpc_mask = 0xff;
+ var correlate_row = channel.correlate_row;
+ var pos = 0;
+ var width = this.width;
+
+ while ((wmimax > channel.state.wmidx) && (channel.state.wmileft <= width)) {
+ if (channel.state.wmileft) {
+ this.quic_four_uncompress_row_seg(channel, correlate_row, prev_row, cur_row, pos,
+ pos + channel.state.wmileft, bpc, bpc_mask);
+ pos += channel.state.wmileft;
+ width -= channel.state.wmileft;
+ }
+
+ channel.state.wmidx++;
+ channel.state.set_wm_trigger();
+ channel.state.wmileft = wminext;
+ }
+
+ if (width) {
+ this.quic_four_uncompress_row_seg(channel, correlate_row, prev_row, cur_row, pos,
+ pos + width, bpc, bpc_mask);
+ if (wmimax > channel.state.wmidx) {
+ channel.state.wmileft -= width;
+ }
+ }
+}
+
+/* We need to be generating rgb32 or rgba */
+QuicEncoder.prototype.quic_decode = function(buf, stride)
+{
+ var row;
+
+ switch (this.type)
+ {
+ case QUIC_IMAGE_TYPE_RGB32:
+ case QUIC_IMAGE_TYPE_RGB24:
+ this.channels[0].correlate_row.zero = 0;
+ this.channels[1].correlate_row.zero = 0;
+ this.channels[2].correlate_row.zero = 0;
+ this.quic_rgb32_uncompress_row0(buf);
+
+ this.rows_completed++;
+ for (row = 1; row < this.height; row++)
+ {
+ var prev = buf;
+ buf = prev.subarray(stride);
+ this.channels[0].correlate_row.zero = this.channels[0].correlate_row.row[0];
+ this.channels[1].correlate_row.zero = this.channels[1].correlate_row.row[0];
+ this.channels[2].correlate_row.zero = this.channels[2].correlate_row.row[0];
+ this.quic_rgb32_uncompress_row(prev, buf);
+ this.rows_completed++;
+ };
+ break;
+ case QUIC_IMAGE_TYPE_RGB16:
+ console.log("quic: unsupported output format\n");
+ return false;
+ break;
+ case QUIC_IMAGE_TYPE_RGBA:
+ this.channels[0].correlate_row.zero = 0;
+ this.channels[1].correlate_row.zero = 0;
+ this.channels[2].correlate_row.zero = 0;
+ this.quic_rgb32_uncompress_row0(buf);
+
+ this.channels[3].correlate_row.zero = 0;
+ this.quic_four_uncompress_row0(this.channels[3], buf);
+
+ this.rows_completed++;
+ for (row = 1; row < this.height; row++) {
+ var prev = buf;
+ buf = prev.subarray(stride);
+
+ this.channels[0].correlate_row.zero = this.channels[0].correlate_row.row[0];
+ this.channels[1].correlate_row.zero = this.channels[1].correlate_row.row[0];
+ this.channels[2].correlate_row.zero = this.channels[2].correlate_row.row[0];
+ this.quic_rgb32_uncompress_row(prev, buf);
+
+ this.channels[3].correlate_row.zero = this.channels[3].correlate_row.row[0];
+ this.quic_four_uncompress_row(encoder.channels[3], prev, buf);
+ this.rows_completed++;
+ }
+ break;
+
+ case QUIC_IMAGE_TYPE_GRAY:
+ console.log("quic: unsupported output format\n");
+ return false;
+ break;
+
+ case QUIC_IMAGE_TYPE_INVALID:
+ default:
+ console.log("quic: bad image type\n");
+ return false;
+ }
+ return true;
+}
+
+QuicEncoder.prototype.simple_quic_decode = function(buf)
+{
+ var stride = 4; /* FIXME - proper stride calc please */
+ if (!this.quic_decode_begin(buf))
+ return undefined;
+ if (this.type != QUIC_IMAGE_TYPE_RGB32 && this.type != QUIC_IMAGE_TYPE_RGB24
+ && this.type != QUIC_IMAGE_TYPE_RGBA)
+ return undefined;
+ var out = new Uint8Array(this.width*this.height*4);
+ out[0] = 69;
+ if (this.quic_decode( out, (this.width * stride)))
+ return out;
+ return undefined;
+}
+
+function SpiceQuic()
+{
+}
+
+SpiceQuic.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ if (!encoder)
+ throw("quic: no quic encoder");
+ this.data_size = dv.getUint32(at, true);
+ at += 4;
+ var buf = new Uint8Array(mb.slice(at));
+ this.outptr = encoder.simple_quic_decode(buf);
+ if (this.outptr)
+ {
+ this.type = encoder.type;
+ this.width = encoder.width;
+ this.height = encoder.height;
+ }
+ at += buf.length;
+ return at;
+ },
+}
+
+function convert_spice_quic_to_web(context, spice_quic)
+{
+ var ret = context.createImageData(spice_quic.width, spice_quic.height);
+ var i;
+ for (i = 0; i < (ret.width * ret.height * 4); i+=4)
+ {
+ ret.data[i + 0] = spice_quic.outptr[i + 2];
+ ret.data[i + 1] = spice_quic.outptr[i + 1];
+ ret.data[i + 2] = spice_quic.outptr[i + 0];
+ if (spice_quic.type !== QUIC_IMAGE_TYPE_RGBA)
+ ret.data[i + 3] = 255;
+ else
+ ret.data[i + 3] = 255 - spice_quic.outptr[i + 3];
+ }
+ return ret;
+}
+
+/* Module initialization */
+if (need_init)
+{
+ need_init = false;
+
+ family_init(family_8bpc, 8, DEFmaxclen);
+ family_init(family_5bpc, 5, DEFmaxclen);
+ /* init_zeroLUT */
+ var i, j, k, l;
+
+ j = k = 1;
+ l = 8;
+ for (i = 0; i < 256; ++i) {
+ zeroLUT[i] = l;
+ --k;
+ if (k == 0) {
+ k = j;
+ --l;
+ j *= 2;
+ }
+ }
+
+ encoder = new QuicEncoder;
+
+ if (!encoder)
+ throw("quic: failed to create encoder");
+}
diff --git a/ui/js/spice/rng.js b/ui/js/spice/rng.js
new file mode 100644
index 0000000..efbf382
--- /dev/null
+++ b/ui/js/spice/rng.js
@@ -0,0 +1,102 @@
+// Downloaded from http://www-cs-students.stanford.edu/~tjw/jsbn/ by Jeremy White on 6/1/2012
+
+/*
+ * Copyright (c) 2003-2005 Tom Wu
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
+ * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * In addition, the following condition applies:
+ *
+ * All redistributions must retain an intact copy of this copyright notice
+ * and disclaimer.
+ */
+
+
+// Random number generator - requires a PRNG backend, e.g. prng4.js
+
+// For best results, put code like
+// <body onClick='rng_seed_time();' onKeyPress='rng_seed_time();'>
+// in your main HTML document.
+
+var rng_state;
+var rng_pool;
+var rng_pptr;
+
+// Mix in a 32-bit integer into the pool
+function rng_seed_int(x) {
+ rng_pool[rng_pptr++] ^= x & 255;
+ rng_pool[rng_pptr++] ^= (x >> 8) & 255;
+ rng_pool[rng_pptr++] ^= (x >> 16) & 255;
+ rng_pool[rng_pptr++] ^= (x >> 24) & 255;
+ if(rng_pptr >= rng_psize) rng_pptr -= rng_psize;
+}
+
+// Mix in the current time (w/milliseconds) into the pool
+function rng_seed_time() {
+ rng_seed_int(new Date().getTime());
+}
+
+// Initialize the pool with junk if needed.
+if(rng_pool == null) {
+ rng_pool = new Array();
+ rng_pptr = 0;
+ var t;
+ if(navigator.appName == "Netscape" && navigator.appVersion < "5" && window.crypto) {
+ // Extract entropy (256 bits) from NS4 RNG if available
+ var z = window.crypto.random(32);
+ for(t = 0; t < z.length; ++t)
+ rng_pool[rng_pptr++] = z.charCodeAt(t) & 255;
+ }
+ while(rng_pptr < rng_psize) { // extract some randomness from Math.random()
+ t = Math.floor(65536 * Math.random());
+ rng_pool[rng_pptr++] = t >>> 8;
+ rng_pool[rng_pptr++] = t & 255;
+ }
+ rng_pptr = 0;
+ rng_seed_time();
+ //rng_seed_int(window.screenX);
+ //rng_seed_int(window.screenY);
+}
+
+function rng_get_byte() {
+ if(rng_state == null) {
+ rng_seed_time();
+ rng_state = prng_newstate();
+ rng_state.init(rng_pool);
+ for(rng_pptr = 0; rng_pptr < rng_pool.length; ++rng_pptr)
+ rng_pool[rng_pptr] = 0;
+ rng_pptr = 0;
+ //rng_pool = null;
+ }
+ // TODO: allow reseeding after first request
+ return rng_state.next();
+}
+
+function rng_get_bytes(ba) {
+ var i;
+ for(i = 0; i < ba.length; ++i) ba[i] = rng_get_byte();
+}
+
+function SecureRandom() {}
+
+SecureRandom.prototype.nextBytes = rng_get_bytes;
diff --git a/ui/js/spice/rsa.js b/ui/js/spice/rsa.js
new file mode 100644
index 0000000..ea0e45b
--- /dev/null
+++ b/ui/js/spice/rsa.js
@@ -0,0 +1,146 @@
+// Downloaded from http://www-cs-students.stanford.edu/~tjw/jsbn/ by Jeremy White on 6/1/2012
+
+/*
+ * Copyright (c) 2003-2005 Tom Wu
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
+ * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * In addition, the following condition applies:
+ *
+ * All redistributions must retain an intact copy of this copyright notice
+ * and disclaimer.
+ */
+
+
+// Depends on jsbn.js and rng.js
+
+// Version 1.1: support utf-8 encoding in pkcs1pad2
+
+// convert a (hex) string to a bignum object
+function parseBigInt(str,r) {
+ return new BigInteger(str,r);
+}
+
+function linebrk(s,n) {
+ var ret = "";
+ var i = 0;
+ while(i + n < s.length) {
+ ret += s.substring(i,i+n) + "\n";
+ i += n;
+ }
+ return ret + s.substring(i,s.length);
+}
+
+function byte2Hex(b) {
+ if(b < 0x10)
+ return "0" + b.toString(16);
+ else
+ return b.toString(16);
+}
+
+// PKCS#1 (type 2, random) pad input string s to n bytes, and return a bigint
+function pkcs1pad2(s,n) {
+ if(n < s.length + 11) { // TODO: fix for utf-8
+ alert("Message too long for RSA");
+ return null;
+ }
+ var ba = new Array();
+ var i = s.length - 1;
+ while(i >= 0 && n > 0) {
+ var c = s.charCodeAt(i--);
+ if(c < 128) { // encode using utf-8
+ ba[--n] = c;
+ }
+ else if((c > 127) && (c < 2048)) {
+ ba[--n] = (c & 63) | 128;
+ ba[--n] = (c >> 6) | 192;
+ }
+ else {
+ ba[--n] = (c & 63) | 128;
+ ba[--n] = ((c >> 6) & 63) | 128;
+ ba[--n] = (c >> 12) | 224;
+ }
+ }
+ ba[--n] = 0;
+ var rng = new SecureRandom();
+ var x = new Array();
+ while(n > 2) { // random non-zero pad
+ x[0] = 0;
+ while(x[0] == 0) rng.nextBytes(x);
+ ba[--n] = x[0];
+ }
+ ba[--n] = 2;
+ ba[--n] = 0;
+ return new BigInteger(ba);
+}
+
+// "empty" RSA key constructor
+function RSAKey() {
+ this.n = null;
+ this.e = 0;
+ this.d = null;
+ this.p = null;
+ this.q = null;
+ this.dmp1 = null;
+ this.dmq1 = null;
+ this.coeff = null;
+}
+
+// Set the public key fields N and e from hex strings
+function RSASetPublic(N,E) {
+ if(N != null && E != null && N.length > 0 && E.length > 0) {
+ this.n = parseBigInt(N,16);
+ this.e = parseInt(E,16);
+ }
+ else
+ alert("Invalid RSA public key");
+}
+
+// Perform raw public operation on "x": return x^e (mod n)
+function RSADoPublic(x) {
+ return x.modPowInt(this.e, this.n);
+}
+
+// Return the PKCS#1 RSA encryption of "text" as an even-length hex string
+function RSAEncrypt(text) {
+ var m = pkcs1pad2(text,(this.n.bitLength()+7)>>3);
+ if(m == null) return null;
+ var c = this.doPublic(m);
+ if(c == null) return null;
+ var h = c.toString(16);
+ if((h.length & 1) == 0) return h; else return "0" + h;
+}
+
+// Return the PKCS#1 RSA encryption of "text" as a Base64-encoded string
+//function RSAEncryptB64(text) {
+// var h = this.encrypt(text);
+// if(h) return hex2b64(h); else return null;
+//}
+
+// protected
+RSAKey.prototype.doPublic = RSADoPublic;
+
+// public
+RSAKey.prototype.setPublic = RSASetPublic;
+RSAKey.prototype.encrypt = RSAEncrypt;
+//RSAKey.prototype.encrypt_b64 = RSAEncryptB64;
diff --git a/ui/js/spice/sha1.js b/ui/js/spice/sha1.js
new file mode 100644
index 0000000..363c83d
--- /dev/null
+++ b/ui/js/spice/sha1.js
@@ -0,0 +1,346 @@
+/*
+ * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
+ * in FIPS 180-1
+ * Version 2.2 Copyright Paul Johnston 2000 - 2009.
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for details.
+ */
+
+ /* Downloaded 6/1/2012 from the above address by Jeremy White.
+ License reproduce here for completeness:
+
+Copyright (c) 1998 - 2009, Paul Johnston & Contributors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ */
+
+/*
+ * Configurable variables. You may need to tweak these to be compatible with
+ * the server-side, but the defaults work in most cases.
+ */
+var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
+var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
+
+/*
+ * These are the functions you'll usually want to call
+ * They take string arguments and return either hex or base-64 encoded strings
+ */
+function hex_sha1(s) { return rstr2hex(rstr_sha1(str2rstr_utf8(s))); }
+function b64_sha1(s) { return rstr2b64(rstr_sha1(str2rstr_utf8(s))); }
+function any_sha1(s, e) { return rstr2any(rstr_sha1(str2rstr_utf8(s)), e); }
+function hex_hmac_sha1(k, d)
+ { return rstr2hex(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d))); }
+function b64_hmac_sha1(k, d)
+ { return rstr2b64(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d))); }
+function any_hmac_sha1(k, d, e)
+ { return rstr2any(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d)), e); }
+
+/*
+ * Perform a simple self-test to see if the VM is working
+ */
+function sha1_vm_test()
+{
+ return hex_sha1("abc").toLowerCase() == "a9993e364706816aba3e25717850c26c9cd0d89d";
+}
+
+/*
+ * Calculate the SHA1 of a raw string
+ */
+function rstr_sha1(s)
+{
+ return binb2rstr(binb_sha1(rstr2binb(s), s.length * 8));
+}
+
+/*
+ * Calculate the HMAC-SHA1 of a key and some data (raw strings)
+ */
+function rstr_hmac_sha1(key, data)
+{
+ var bkey = rstr2binb(key);
+ if(bkey.length > 16) bkey = binb_sha1(bkey, key.length * 8);
+
+ var ipad = Array(16), opad = Array(16);
+ for(var i = 0; i < 16; i++)
+ {
+ ipad[i] = bkey[i] ^ 0x36363636;
+ opad[i] = bkey[i] ^ 0x5C5C5C5C;
+ }
+
+ var hash = binb_sha1(ipad.concat(rstr2binb(data)), 512 + data.length * 8);
+ return binb2rstr(binb_sha1(opad.concat(hash), 512 + 160));
+}
+
+/*
+ * Convert a raw string to a hex string
+ */
+function rstr2hex(input)
+{
+ try { hexcase } catch(e) { hexcase=0; }
+ var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+ var output = "";
+ var x;
+ for(var i = 0; i < input.length; i++)
+ {
+ x = input.charCodeAt(i);
+ output += hex_tab.charAt((x >>> 4) & 0x0F)
+ + hex_tab.charAt( x & 0x0F);
+ }
+ return output;
+}
+
+/*
+ * Convert a raw string to a base-64 string
+ */
+function rstr2b64(input)
+{
+ try { b64pad } catch(e) { b64pad=''; }
+ var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ var output = "";
+ var len = input.length;
+ for(var i = 0; i < len; i += 3)
+ {
+ var triplet = (input.charCodeAt(i) << 16)
+ | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)
+ | (i + 2 < len ? input.charCodeAt(i+2) : 0);
+ for(var j = 0; j < 4; j++)
+ {
+ if(i * 8 + j * 6 > input.length * 8) output += b64pad;
+ else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);
+ }
+ }
+ return output;
+}
+
+/*
+ * Convert a raw string to an arbitrary string encoding
+ */
+function rstr2any(input, encoding)
+{
+ var divisor = encoding.length;
+ var remainders = Array();
+ var i, q, x, quotient;
+
+ /* Convert to an array of 16-bit big-endian values, forming the dividend */
+ var dividend = Array(Math.ceil(input.length / 2));
+ for(i = 0; i < dividend.length; i++)
+ {
+ dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
+ }
+
+ /*
+ * Repeatedly perform a long division. The binary array forms the dividend,
+ * the length of the encoding is the divisor. Once computed, the quotient
+ * forms the dividend for the next step. We stop when the dividend is zero.
+ * All remainders are stored for later use.
+ */
+ while(dividend.length > 0)
+ {
+ quotient = Array();
+ x = 0;
+ for(i = 0; i < dividend.length; i++)
+ {
+ x = (x << 16) + dividend[i];
+ q = Math.floor(x / divisor);
+ x -= q * divisor;
+ if(quotient.length > 0 || q > 0)
+ quotient[quotient.length] = q;
+ }
+ remainders[remainders.length] = x;
+ dividend = quotient;
+ }
+
+ /* Convert the remainders to the output string */
+ var output = "";
+ for(i = remainders.length - 1; i >= 0; i--)
+ output += encoding.charAt(remainders[i]);
+
+ /* Append leading zero equivalents */
+ var full_length = Math.ceil(input.length * 8 /
+ (Math.log(encoding.length) / Math.log(2)))
+ for(i = output.length; i < full_length; i++)
+ output = encoding[0] + output;
+
+ return output;
+}
+
+/*
+ * Encode a string as utf-8.
+ * For efficiency, this assumes the input is valid utf-16.
+ */
+function str2rstr_utf8(input)
+{
+ var output = "";
+ var i = -1;
+ var x, y;
+
+ while(++i < input.length)
+ {
+ /* Decode utf-16 surrogate pairs */
+ x = input.charCodeAt(i);
+ y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
+ if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)
+ {
+ x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
+ i++;
+ }
+
+ /* Encode output as utf-8 */
+ if(x <= 0x7F)
+ output += String.fromCharCode(x);
+ else if(x <= 0x7FF)
+ output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),
+ 0x80 | ( x & 0x3F));
+ else if(x <= 0xFFFF)
+ output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),
+ 0x80 | ((x >>> 6 ) & 0x3F),
+ 0x80 | ( x & 0x3F));
+ else if(x <= 0x1FFFFF)
+ output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),
+ 0x80 | ((x >>> 12) & 0x3F),
+ 0x80 | ((x >>> 6 ) & 0x3F),
+ 0x80 | ( x & 0x3F));
+ }
+ return output;
+}
+
+/*
+ * Encode a string as utf-16
+ */
+function str2rstr_utf16le(input)
+{
+ var output = "";
+ for(var i = 0; i < input.length; i++)
+ output += String.fromCharCode( input.charCodeAt(i) & 0xFF,
+ (input.charCodeAt(i) >>> 8) & 0xFF);
+ return output;
+}
+
+function str2rstr_utf16be(input)
+{
+ var output = "";
+ for(var i = 0; i < input.length; i++)
+ output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF,
+ input.charCodeAt(i) & 0xFF);
+ return output;
+}
+
+/*
+ * Convert a raw string to an array of big-endian words
+ * Characters >255 have their high-byte silently ignored.
+ */
+function rstr2binb(input)
+{
+ var output = Array(input.length >> 2);
+ for(var i = 0; i < output.length; i++)
+ output[i] = 0;
+ for(var i = 0; i < input.length * 8; i += 8)
+ output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (24 - i % 32);
+ return output;
+}
+
+/*
+ * Convert an array of big-endian words to a string
+ */
+function binb2rstr(input)
+{
+ var output = "";
+ for(var i = 0; i < input.length * 32; i += 8)
+ output += String.fromCharCode((input[i>>5] >>> (24 - i % 32)) & 0xFF);
+ return output;
+}
+
+/*
+ * Calculate the SHA-1 of an array of big-endian words, and a bit length
+ */
+function binb_sha1(x, len)
+{
+ /* append padding */
+ x[len >> 5] |= 0x80 << (24 - len % 32);
+ x[((len + 64 >> 9) << 4) + 15] = len;
+
+ var w = Array(80);
+ var a = 1732584193;
+ var b = -271733879;
+ var c = -1732584194;
+ var d = 271733878;
+ var e = -1009589776;
+
+ for(var i = 0; i < x.length; i += 16)
+ {
+ var olda = a;
+ var oldb = b;
+ var oldc = c;
+ var oldd = d;
+ var olde = e;
+
+ for(var j = 0; j < 80; j++)
+ {
+ if(j < 16) w[j] = x[i + j];
+ else w[j] = bit_rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
+ var t = safe_add(safe_add(bit_rol(a, 5), sha1_ft(j, b, c, d)),
+ safe_add(safe_add(e, w[j]), sha1_kt(j)));
+ e = d;
+ d = c;
+ c = bit_rol(b, 30);
+ b = a;
+ a = t;
+ }
+
+ a = safe_add(a, olda);
+ b = safe_add(b, oldb);
+ c = safe_add(c, oldc);
+ d = safe_add(d, oldd);
+ e = safe_add(e, olde);
+ }
+ return Array(a, b, c, d, e);
+
+}
+
+/*
+ * Perform the appropriate triplet combination function for the current
+ * iteration
+ */
+function sha1_ft(t, b, c, d)
+{
+ if(t < 20) return (b & c) | ((~b) & d);
+ if(t < 40) return b ^ c ^ d;
+ if(t < 60) return (b & c) | (b & d) | (c & d);
+ return b ^ c ^ d;
+}
+
+/*
+ * Determine the appropriate additive constant for the current iteration
+ */
+function sha1_kt(t)
+{
+ return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 :
+ (t < 60) ? -1894007588 : -899497514;
+}
+
+/*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+function safe_add(x, y)
+{
+ var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+ var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+ return (msw << 16) | (lsw & 0xFFFF);
+}
+
+/*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+function bit_rol(num, cnt)
+{
+ return (num << cnt) | (num >>> (32 - cnt));
+}
diff --git a/ui/js/spice/spiceconn.js b/ui/js/spice/spiceconn.js
new file mode 100644
index 0000000..7d26913
--- /dev/null
+++ b/ui/js/spice/spiceconn.js
@@ -0,0 +1,447 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*----------------------------------------------------------------------------
+** SpiceConn
+** This is the base Javascript class for establishing and
+** managing a connection to a Spice Server.
+** It is used to provide core functionality to the Spice main,
+** display, inputs, and cursor channels. See main.js for
+** usage.
+**--------------------------------------------------------------------------*/
+function SpiceConn(o)
+{
+ if (o === undefined || o.uri === undefined || ! o.uri)
+ throw new Error("You must specify a uri");
+
+ this.ws = new WebSocket(o.uri, 'binary');
+
+ if (! this.ws.binaryType)
+ throw new Error("WebSocket doesn't support binaryType. Try a different browser.");
+
+ this.connection_id = o.connection_id !== undefined ? o.connection_id : 0;
+ this.type = o.type !== undefined ? o.type : SPICE_CHANNEL_MAIN;
+ this.chan_id = o.chan_id !== undefined ? o.chan_id : 0;
+ if (o.parent !== undefined)
+ {
+ this.parent = o.parent;
+ this.message_id = o.parent.message_id;
+ this.password = o.parent.password;
+ }
+ if (o.screen_id !== undefined)
+ this.screen_id = o.screen_id;
+ if (o.dump_id !== undefined)
+ this.dump_id = o.dump_id;
+ if (o.message_id !== undefined)
+ this.message_id = o.message_id;
+ if (o.password !== undefined)
+ this.password = o.password;
+ if (o.onerror !== undefined)
+ this.onerror = o.onerror;
+
+ this.state = "connecting";
+ this.ws.parent = this;
+ this.wire_reader = new SpiceWireReader(this, this.process_inbound);
+ this.messages_sent = 0;
+ this.warnings = [];
+
+ this.ws.addEventListener('open', function(e) {
+ DEBUG > 0 && console.log(">> WebSockets.onopen");
+ DEBUG > 0 && console.log("id " + this.parent.connection_id +"; type " + this.parent.type);
+
+ /***********************************************************************
+ ** WHERE IT ALL REALLY BEGINS
+ ***********************************************************************/
+ this.parent.send_hdr();
+ this.parent.wire_reader.request(SpiceLinkHeader.prototype.buffer_size());
+ this.parent.state = "start";
+ });
+ this.ws.addEventListener('error', function(e) {
+ this.parent.log_err(">> WebSockets.onerror" + e.toString());
+ this.parent.report_error(e);
+ });
+ this.ws.addEventListener('close', function(e) {
+ DEBUG > 0 && console.log(">> WebSockets.onclose");
+ DEBUG > 0 && console.log("id " + this.parent.connection_id +"; type " + this.parent.type);
+ DEBUG > 0 && console.log(e);
+ if (this.parent.state != "closing" && this.parent.state != "error" && this.parent.onerror !== undefined)
+ {
+ var e;
+ if (this.parent.state == "connecting")
+ e = new Error("Connection refused.");
+ else if (this.parent.state == "start" || this.parent.state == "link")
+ e = new Error("Unexpected protocol mismatch.");
+ else if (this.parent.state == "ticket")
+ e = new Error("Bad password.");
+ else
+ e = new Error("Unexpected close while " + this.parent.state);
+
+ this.parent.onerror(e);
+ this.parent.log_err(e.toString());
+ }
+ });
+
+ if (this.ws.readyState == 2 || this.ws.readyState == 3)
+ throw new Error("Unable to connect to " + o.uri);
+
+ this.timeout = window.setTimeout(spiceconn_timeout, SPICE_CONNECT_TIMEOUT, this);
+}
+
+SpiceConn.prototype =
+{
+ send_hdr : function ()
+ {
+ var hdr = new SpiceLinkHeader;
+ var msg = new SpiceLinkMess;
+
+ msg.connection_id = this.connection_id;
+ msg.channel_type = this.type;
+ // FIXME - we're not setting a channel_id...
+ msg.common_caps.push(
+ (1 << SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION) |
+ (1 << SPICE_COMMON_CAP_MINI_HEADER)
+ );
+
+ hdr.size = msg.buffer_size();
+
+ var mb = new ArrayBuffer(hdr.buffer_size() + msg.buffer_size());
+ hdr.to_buffer(mb);
+ msg.to_buffer(mb, hdr.buffer_size());
+
+ DEBUG > 1 && console.log("Sending header:");
+ DEBUG > 2 && hexdump_buffer(mb);
+ this.ws.send(mb);
+ },
+
+ send_ticket: function(ticket)
+ {
+ var hdr = new SpiceLinkAuthTicket();
+ hdr.auth_mechanism = SPICE_COMMON_CAP_AUTH_SPICE;
+ // FIXME - we need to implement RSA to make this work right
+ hdr.encrypted_data = ticket;
+ var mb = new ArrayBuffer(hdr.buffer_size());
+
+ hdr.to_buffer(mb);
+ DEBUG > 1 && console.log("Sending ticket:");
+ DEBUG > 2 && hexdump_buffer(mb);
+ this.ws.send(mb);
+ },
+
+ send_msg: function(msg)
+ {
+ var mb = new ArrayBuffer(msg.buffer_size());
+ msg.to_buffer(mb);
+ this.messages_sent++;
+ DEBUG > 0 && console.log(">> hdr " + this.channel_type() + " type " + msg.type + " size " + mb.byteLength);
+ DEBUG > 2 && hexdump_buffer(mb);
+ this.ws.send(mb);
+ },
+
+ process_inbound: function(mb, saved_header)
+ {
+ DEBUG > 2 && console.log(this.type + ": processing message of size " + mb.byteLength + "; state is " + this.state);
+ if (this.state == "ready")
+ {
+ if (saved_header == undefined)
+ {
+ var msg = new SpiceMiniData(mb);
+
+ if (msg.type > 500)
+ {
+ alert("Something has gone very wrong; we think we have message of type " + msg.type);
+ debugger;
+ }
+
+ if (msg.size == 0)
+ {
+ this.process_message(msg);
+ this.wire_reader.request(SpiceMiniData.prototype.buffer_size());
+ }
+ else
+ {
+ this.wire_reader.request(msg.size);
+ this.wire_reader.save_header(msg);
+ }
+ }
+ else
+ {
+ saved_header.data = mb;
+ this.process_message(saved_header);
+ this.wire_reader.request(SpiceMiniData.prototype.buffer_size());
+ this.wire_reader.save_header(undefined);
+ }
+ }
+
+ else if (this.state == "start")
+ {
+ this.reply_hdr = new SpiceLinkHeader(mb);
+ if (this.reply_hdr.magic != SPICE_MAGIC)
+ {
+ this.state = "error";
+ var e = new Error('Error: magic mismatch: ' + this.reply_hdr.magic);
+ this.report_error(e);
+ }
+ else
+ {
+ // FIXME - Determine major/minor version requirements
+ this.wire_reader.request(this.reply_hdr.size);
+ this.state = "link";
+ }
+ }
+
+ else if (this.state == "link")
+ {
+ this.reply_link = new SpiceLinkReply(mb);
+ // FIXME - Screen the caps - require minihdr at least, right?
+ if (this.reply_link.error)
+ {
+ this.state = "error";
+ var e = new Error('Error: reply link error ' + this.reply_link.error);
+ this.report_error(e);
+ }
+ else
+ {
+ this.send_ticket(rsa_encrypt(this.reply_link.pub_key, this.password + String.fromCharCode(0)));
+ this.state = "ticket";
+ this.wire_reader.request(SpiceLinkAuthReply.prototype.buffer_size());
+ }
+ }
+
+ else if (this.state == "ticket")
+ {
+ this.auth_reply = new SpiceLinkAuthReply(mb);
+ if (this.auth_reply.auth_code == SPICE_LINK_ERR_OK)
+ {
+ DEBUG > 0 && console.log(this.type + ': Connected');
+
+ if (this.type == SPICE_CHANNEL_DISPLAY)
+ {
+ // FIXME - pixmap and glz dictionary config info?
+ var dinit = new SpiceMsgcDisplayInit();
+ var reply = new SpiceMiniData();
+ reply.build_msg(SPICE_MSGC_DISPLAY_INIT, dinit);
+ DEBUG > 0 && console.log("Request display init");
+ this.send_msg(reply);
+ }
+ this.state = "ready";
+ this.wire_reader.request(SpiceMiniData.prototype.buffer_size());
+ if (this.timeout)
+ {
+ window.clearTimeout(this.timeout);
+ delete this.timeout;
+ }
+ }
+ else
+ {
+ this.state = "error";
+ if (this.auth_reply.auth_code == SPICE_LINK_ERR_PERMISSION_DENIED)
+ {
+ var e = new Error("Permission denied.");
+ }
+ else
+ {
+ var e = new Error("Unexpected link error " + this.auth_reply.auth_code);
+ }
+ this.report_error(e);
+ }
+ }
+ },
+
+ process_common_messages : function(msg)
+ {
+ if (msg.type == SPICE_MSG_SET_ACK)
+ {
+ var ack = new SpiceMsgSetAck(msg.data);
+ // FIXME - what to do with generation?
+ this.ack_window = ack.window;
+ DEBUG > 1 && console.log(this.type + ": set ack to " + ack.window);
+ this.msgs_until_ack = this.ack_window;
+ var ackack = new SpiceMsgcAckSync(ack);
+ var reply = new SpiceMiniData();
+ reply.build_msg(SPICE_MSGC_ACK_SYNC, ackack);
+ this.send_msg(reply);
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_PING)
+ {
+ DEBUG > 1 && console.log("ping!");
+ var pong = new SpiceMiniData;
+ pong.type = SPICE_MSGC_PONG;
+ if (msg.data)
+ {
+ pong.data = msg.data.slice(0, 12);
+ }
+ pong.size = pong.buffer_size();
+ this.send_msg(pong);
+ return true;
+ }
+
+ if (msg.type == SPICE_MSG_NOTIFY)
+ {
+ // FIXME - Visibility + what
+ var notify = new SpiceMsgNotify(msg.data);
+ if (notify.severity == SPICE_NOTIFY_SEVERITY_ERROR)
+ this.log_err(notify.message);
+ else if (notify.severity == SPICE_NOTIFY_SEVERITY_WARN )
+ this.log_warn(notify.message);
+ else
+ this.log_info(notify.message);
+ return true;
+ }
+
+ return false;
+
+ },
+
+ process_message: function(msg)
+ {
+ var rc;
+ DEBUG > 0 && console.log("<< hdr " + this.channel_type() + " type " + msg.type + " size " + (msg.data && msg.data.byteLength));
+ rc = this.process_common_messages(msg);
+ if (rc)
+ return rc;
+
+ if (this.process_channel_message)
+ rc = this.process_channel_message(msg);
+ else
+ {
+ this.log_err(this.type + ": No message handlers for this channel; message " + msg.type);
+ return false;
+ }
+
+ if (! rc)
+ this.log_warn(this.type + ": Unknown message type " + msg.type + "!");
+
+ if (this.msgs_until_ack !== undefined && this.ack_window)
+ {
+ this.msgs_until_ack--;
+ if (this.msgs_until_ack <= 0)
+ {
+ this.msgs_until_ack = this.ack_window;
+ var ack = new SpiceMiniData();
+ ack.type = SPICE_MSGC_ACK;
+ this.send_msg(ack);
+ DEBUG > 1 && console.log(this.type + ": sent ack");
+ }
+ }
+
+ return rc;
+ },
+
+ channel_type: function()
+ {
+ if (this.type == SPICE_CHANNEL_MAIN)
+ return "main";
+ else if (this.type == SPICE_CHANNEL_DISPLAY)
+ return "display";
+ else if (this.type == SPICE_CHANNEL_INPUTS)
+ return "inputs";
+ else if (this.type == SPICE_CHANNEL_CURSOR)
+ return "cursor";
+ return "unknown-" + this.type;
+
+ },
+
+ log_info: function()
+ {
+ var msg = Array.prototype.join.call(arguments, " ");
+ console.log(msg);
+ if (this.message_id)
+ {
+ var p = document.createElement("p");
+ p.appendChild(document.createTextNode(msg));
+ p.className += "spice-message-info";
+ document.getElementById(this.message_id).appendChild(p);
+ }
+ },
+
+ log_warn: function()
+ {
+ var msg = Array.prototype.join.call(arguments, " ");
+ console.log("WARNING: " + msg);
+ if (this.message_id)
+ {
+ var p = document.createElement("p");
+ p.appendChild(document.createTextNode(msg));
+ p.className += "spice-message-warning";
+ document.getElementById(this.message_id).appendChild(p);
+ }
+ },
+
+ log_err: function()
+ {
+ var msg = Array.prototype.join.call(arguments, " ");
+ console.log("ERROR: " + msg);
+ if (this.message_id)
+ {
+ var p = document.createElement("p");
+ p.appendChild(document.createTextNode(msg));
+ p.className += "spice-message-error";
+ document.getElementById(this.message_id).appendChild(p);
+ }
+ },
+
+ known_unimplemented: function(type, msg)
+ {
+ if ( (!this.warnings[type]) || DEBUG > 1)
+ {
+ var str = "";
+ if (DEBUG <= 1)
+ str = " [ further notices suppressed ]";
+ this.log_warn("Unimplemented function " + type + "(" + msg + ")" + str);
+ this.warnings[type] = true;
+ }
+ },
+
+ report_error: function(e)
+ {
+ this.log_err(e.toString());
+ if (this.onerror != undefined)
+ this.onerror(e);
+ else
+ throw(e);
+ },
+
+ cleanup: function()
+ {
+ if (this.timeout)
+ {
+ window.clearTimeout(this.timeout);
+ delete this.timeout;
+ }
+ if (this.ws)
+ {
+ this.ws.close();
+ this.ws = undefined;
+ }
+ },
+
+ handle_timeout: function()
+ {
+ var e = new Error("Connection timed out.");
+ this.report_error(e);
+ },
+}
+
+function spiceconn_timeout(sc)
+{
+ SpiceConn.prototype.handle_timeout.call(sc);
+}
diff --git a/ui/js/spice/spicedataview.js b/ui/js/spice/spicedataview.js
new file mode 100644
index 0000000..0699ed5
--- /dev/null
+++ b/ui/js/spice/spicedataview.js
@@ -0,0 +1,96 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*----------------------------------------------------------------------------
+** SpiceDataView
+** FIXME FIXME
+** This is used because Firefox does not have DataView yet.
+** We should use DataView if we have it, because it *has* to
+** be faster than this code
+**--------------------------------------------------------------------------*/
+function SpiceDataView(buffer, byteOffset, byteLength)
+{
+ if (byteOffset !== undefined)
+ {
+ if (byteLength !== undefined)
+ this.u8 = new Uint8Array(buffer, byteOffset, byteLength);
+ else
+ this.u8 = new Uint8Array(buffer, byteOffset);
+ }
+ else
+ this.u8 = new Uint8Array(buffer);
+};
+
+SpiceDataView.prototype = {
+ getUint8: function(byteOffset)
+ {
+ return this.u8[byteOffset];
+ },
+ getUint16: function(byteOffset, littleEndian)
+ {
+ var low = 1, high = 0;
+ if (littleEndian)
+ {
+ low = 0;
+ high = 1;
+ }
+
+ return (this.u8[byteOffset + high] << 8) | this.u8[byteOffset + low];
+ },
+ getUint32: function(byteOffset, littleEndian)
+ {
+ var low = 2, high = 0;
+ if (littleEndian)
+ {
+ low = 0;
+ high = 2;
+ }
+
+ return (this.getUint16(byteOffset + high, littleEndian) << 16) |
+ this.getUint16(byteOffset + low, littleEndian);
+ },
+ setUint8: function(byteOffset, b)
+ {
+ this.u8[byteOffset] = (b & 0xff);
+ },
+ setUint16: function(byteOffset, i, littleEndian)
+ {
+ var low = 1, high = 0;
+ if (littleEndian)
+ {
+ low = 0;
+ high = 1;
+ }
+ this.u8[byteOffset + high] = (i & 0xffff) >> 8;
+ this.u8[byteOffset + low] = (i & 0x00ff);
+ },
+ setUint32: function(byteOffset, w, littleEndian)
+ {
+ var low = 2, high = 0;
+ if (littleEndian)
+ {
+ low = 0;
+ high = 2;
+ }
+
+ this.setUint16(byteOffset + high, (w & 0xffffffff) >> 16, littleEndian);
+ this.setUint16(byteOffset + low, (w & 0x0000ffff), littleEndian);
+ },
+}
diff --git a/ui/js/spice/spicemsg.js b/ui/js/spice/spicemsg.js
new file mode 100644
index 0000000..dac4b30
--- /dev/null
+++ b/ui/js/spice/spicemsg.js
@@ -0,0 +1,883 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*----------------------------------------------------------------------------
+** Spice messages
+** This file contains classes for passing messages to and from
+** a spice server. This file should arguably be generated from
+** spice.proto, but it was instead put together by hand.
+**--------------------------------------------------------------------------*/
+function SpiceLinkHeader(a, at)
+{
+ this.magic = SPICE_MAGIC;
+ this.major_version = SPICE_VERSION_MAJOR;
+ this.minor_version = SPICE_VERSION_MINOR;
+ this.size = 0;
+ if (a !== undefined)
+ this.from_buffer(a, at);
+}
+
+SpiceLinkHeader.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.magic = "";
+ for (var i = 0; i < 4; i++)
+ this.magic += String.fromCharCode(dv.getUint8(at + i));
+ at += 4;
+
+ this.major_version = dv.getUint32(at, true); at += 4;
+ this.minor_version = dv.getUint32(at, true); at += 4;
+ this.size = dv.getUint32(at, true); at += 4;
+ },
+
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ for (var i = 0; i < 4; i++)
+ dv.setUint8(at + i, this.magic.charCodeAt(i));
+ at += 4;
+
+ dv.setUint32(at, this.major_version, true); at += 4;
+ dv.setUint32(at, this.minor_version, true); at += 4;
+ dv.setUint32(at, this.size, true); at += 4;
+ },
+ buffer_size: function()
+ {
+ return 16;
+ },
+}
+
+function SpiceLinkMess(a, at)
+{
+ this.connection_id = 0;
+ this.channel_type = 0;
+ this.channel_id = 0;
+ this.common_caps = [];
+ this.channel_caps = [];
+
+ if (a !== undefined)
+ this.from_buffer(a, at);
+}
+
+SpiceLinkMess.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var i;
+ var orig_at = at;
+ var dv = new SpiceDataView(a);
+ this.connection_id = dv.getUint32(at, true); at += 4;
+ this.channel_type = dv.getUint8(at, true); at++;
+ this.channel_id = dv.getUint8(at, true); at++;
+ var num_common_caps = dv.getUint32(at, true); at += 4;
+ var num_channel_caps = dv.getUint32(at, true); at += 4;
+ var caps_offset = dv.getUint32(at, true); at += 4;
+
+ at = orig_at + caps_offset;
+ this.common_caps = [];
+ for (i = 0; i < num_common_caps; i++)
+ {
+ this.common_caps.unshift(dv.getUint32(at, true)); at += 4;
+ }
+
+ this.channel_caps = [];
+ for (i = 0; i < num_channel_caps; i++)
+ {
+ this.channel_caps.unshift(dv.getUint32(at, true)); at += 4;
+ }
+ },
+
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var orig_at = at;
+ var i;
+ var dv = new SpiceDataView(a);
+ dv.setUint32(at, this.connection_id, true); at += 4;
+ dv.setUint8(at, this.channel_type, true); at++;
+ dv.setUint8(at, this.channel_id, true); at++;
+ dv.setUint32(at, this.common_caps.length, true); at += 4;
+ dv.setUint32(at, this.channel_caps.length, true); at += 4;
+ dv.setUint32(at, (at - orig_at) + 4, true); at += 4;
+
+ for (i = 0; i < this.common_caps.length; i++)
+ {
+ dv.setUint32(at, this.common_caps[i], true); at += 4;
+ }
+
+ for (i = 0; i < this.channel_caps.length; i++)
+ {
+ dv.setUint32(at, this.channel_caps[i], true); at += 4;
+ }
+ },
+ buffer_size: function()
+ {
+ return 18 + (4 * this.common_caps.length) + (4 * this.channel_caps.length);
+ }
+}
+
+function SpiceLinkReply(a, at)
+{
+ this.error = 0;
+ this.pub_key = undefined;
+ this.common_caps = [];
+ this.channel_caps = [];
+
+ if (a !== undefined)
+ this.from_buffer(a, at);
+}
+
+SpiceLinkReply.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var i;
+ var orig_at = at;
+ var dv = new SpiceDataView(a);
+ this.error = dv.getUint32(at, true); at += 4;
+
+ this.pub_key = create_rsa_from_mb(a, at);
+ at += SPICE_TICKET_PUBKEY_BYTES;
+
+ var num_common_caps = dv.getUint32(at, true); at += 4;
+ var num_channel_caps = dv.getUint32(at, true); at += 4;
+ var caps_offset = dv.getUint32(at, true); at += 4;
+
+ at = orig_at + caps_offset;
+ this.common_caps = [];
+ for (i = 0; i < num_common_caps; i++)
+ {
+ this.common_caps.unshift(dv.getUint32(at, true)); at += 4;
+ }
+
+ this.channel_caps = [];
+ for (i = 0; i < num_channel_caps; i++)
+ {
+ this.channel_caps.unshift(dv.getUint32(at, true)); at += 4;
+ }
+ },
+}
+
+function SpiceLinkAuthTicket(a, at)
+{
+ this.auth_mechanism = 0;
+ this.encrypted_data = undefined;
+}
+
+SpiceLinkAuthTicket.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var i;
+ var dv = new SpiceDataView(a);
+ dv.setUint32(at, this.auth_mechanism, true); at += 4;
+ for (i = 0; i < SPICE_TICKET_KEY_PAIR_LENGTH / 8; i++)
+ {
+ if (this.encrypted_data && i < this.encrypted_data.length)
+ dv.setUint8(at, this.encrypted_data[i], true);
+ else
+ dv.setUint8(at, 0, true);
+ at++;
+ }
+ },
+ buffer_size: function()
+ {
+ return 4 + (SPICE_TICKET_KEY_PAIR_LENGTH / 8);
+ }
+}
+
+function SpiceLinkAuthReply(a, at)
+{
+ this.auth_code = 0;
+ if (a !== undefined)
+ this.from_buffer(a, at);
+}
+
+SpiceLinkAuthReply.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.auth_code = dv.getUint32(at, true); at += 4;
+ },
+ buffer_size: function()
+ {
+ return 4;
+ }
+}
+
+function SpiceMiniData(a, at)
+{
+ this.type = 0;
+ this.size = 0;
+ this.data = undefined;
+ if (a !== undefined)
+ this.from_buffer(a, at);
+}
+
+SpiceMiniData.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var i;
+ var dv = new SpiceDataView(a);
+ this.type = dv.getUint16(at, true); at += 2;
+ this.size = dv.getUint32(at, true); at += 4;
+ if (a.byteLength > at)
+ {
+ this.data = a.slice(at);
+ at += this.data.byteLength;
+ }
+ },
+ to_buffer : function(a, at)
+ {
+ at = at || 0;
+ var i;
+ var dv = new SpiceDataView(a);
+ dv.setUint16(at, this.type, true); at += 2;
+ dv.setUint32(at, this.data ? this.data.byteLength : 0, true); at += 4;
+ if (this.data && this.data.byteLength > 0)
+ {
+ var u8arr = new Uint8Array(this.data);
+ for (i = 0; i < u8arr.length; i++, at++)
+ dv.setUint8(at, u8arr[i], true);
+ }
+ },
+ build_msg : function(in_type, extra)
+ {
+ this.type = in_type;
+ this.size = extra.buffer_size();
+ this.data = new ArrayBuffer(this.size);
+ extra.to_buffer(this.data);
+ },
+ buffer_size: function()
+ {
+ if (this.data)
+ return 6 + this.data.byteLength;
+ else
+ return 6;
+ },
+}
+
+function SpiceMsgChannels(a, at)
+{
+ this.num_of_channels = 0;
+ this.channels = [];
+ if (a !== undefined)
+ this.from_buffer(a, at);
+}
+
+SpiceMsgChannels.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var i;
+ var dv = new SpiceDataView(a);
+ this.num_of_channels = dv.getUint32(at, true); at += 4;
+ for (i = 0; i < this.num_of_channels; i++)
+ {
+ var chan = new SpiceChannelId();
+ at = chan.from_dv(dv, at, a);
+ this.channels.push(chan);
+ }
+ },
+}
+
+function SpiceMsgMainInit(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgMainInit.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.session_id = dv.getUint32(at, true); at += 4;
+ this.display_channels_hint = dv.getUint32(at, true); at += 4;
+ this.supported_mouse_modes = dv.getUint32(at, true); at += 4;
+ this.current_mouse_mode = dv.getUint32(at, true); at += 4;
+ this.agent_connected = dv.getUint32(at, true); at += 4;
+ this.agent_tokens = dv.getUint32(at, true); at += 4;
+ this.multi_media_time = dv.getUint32(at, true); at += 4;
+ this.ram_hint = dv.getUint32(at, true); at += 4;
+ },
+}
+
+function SpiceMsgMainMouseMode(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgMainMouseMode.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.supported_modes = dv.getUint16(at, true); at += 2;
+ this.current_mode = dv.getUint16(at, true); at += 2;
+ },
+}
+
+function SpiceMsgSetAck(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgSetAck.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.generation = dv.getUint32(at, true); at += 4;
+ this.window = dv.getUint32(at, true); at += 4;
+ },
+}
+
+function SpiceMsgcAckSync(ack)
+{
+ this.generation = ack.generation;
+}
+
+SpiceMsgcAckSync.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ dv.setUint32(at, this.generation, true); at += 4;
+ },
+ buffer_size: function()
+ {
+ return 4;
+ }
+}
+
+function SpiceMsgcMainMouseModeRequest(mode)
+{
+ this.mode = mode;
+}
+
+SpiceMsgcMainMouseModeRequest.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ dv.setUint16(at, this.mode, true); at += 2;
+ },
+ buffer_size: function()
+ {
+ return 2;
+ }
+}
+
+function SpiceMsgNotify(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgNotify.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var i;
+ var dv = new SpiceDataView(a);
+ this.time_stamp = [];
+ this.time_stamp[0] = dv.getUint32(at, true); at += 4;
+ this.time_stamp[1] = dv.getUint32(at, true); at += 4;
+ this.severity = dv.getUint32(at, true); at += 4;
+ this.visibility = dv.getUint32(at, true); at += 4;
+ this.what = dv.getUint32(at, true); at += 4;
+ this.message_len = dv.getUint32(at, true); at += 4;
+ this.message = "";
+ for (i = 0; i < this.message_len; i++)
+ {
+ var c = dv.getUint8(at, true); at++;
+ this.message += String.fromCharCode(c);
+ }
+ },
+}
+
+function SpiceMsgcDisplayInit()
+{
+ this.pixmap_cache_id = 1;
+ this.glz_dictionary_id = 0;
+ this.pixmap_cache_size = 10 * 1024 * 1024;
+ this.glz_dictionary_window_size = 0;
+}
+
+SpiceMsgcDisplayInit.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ dv.setUint8(at, this.pixmap_cache_id, true); at++;
+ dv.setUint32(at, 0, true); at += 4;
+ dv.setUint32(at, this.pixmap_cache_size, true); at += 4;
+ dv.setUint8(at, this.glz_dictionary_id, true); at++;
+ dv.setUint32(at, this.glz_dictionary_window_size, true); at += 4;
+ },
+ buffer_size: function()
+ {
+ return 14;
+ }
+}
+
+function SpiceMsgDisplayBase()
+{
+}
+
+SpiceMsgDisplayBase.prototype =
+{
+ from_dv : function(dv, at, mb)
+ {
+ this.surface_id = dv.getUint32(at, true); at += 4;
+ this.box = new SpiceRect;
+ at = this.box.from_dv(dv, at, mb);
+ this.clip = new SpiceClip;
+ return this.clip.from_dv(dv, at, mb);
+ },
+}
+
+function SpiceMsgDisplayDrawCopy(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgDisplayDrawCopy.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.base = new SpiceMsgDisplayBase;
+ at = this.base.from_dv(dv, at, a);
+ this.data = new SpiceCopy;
+ return this.data.from_dv(dv, at, a);
+ },
+}
+
+function SpiceMsgDisplayDrawFill(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgDisplayDrawFill.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.base = new SpiceMsgDisplayBase;
+ at = this.base.from_dv(dv, at, a);
+ this.data = new SpiceFill;
+ return this.data.from_dv(dv, at, a);
+ },
+}
+
+function SpiceMsgDisplayCopyBits(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgDisplayCopyBits.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.base = new SpiceMsgDisplayBase;
+ at = this.base.from_dv(dv, at, a);
+ this.src_pos = new SpicePoint;
+ return this.src_pos.from_dv(dv, at, a);
+ },
+}
+
+
+function SpiceMsgSurfaceCreate(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgSurfaceCreate.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.surface = new SpiceSurface;
+ return this.surface.from_dv(dv, at, a);
+ },
+}
+
+function SpiceMsgSurfaceDestroy(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgSurfaceDestroy.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.surface_id = dv.getUint32(at, true); at += 4;
+ },
+}
+
+function SpiceMsgInputsInit(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgInputsInit.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.keyboard_modifiers = dv.getUint16(at, true); at += 2;
+ return at;
+ },
+}
+
+function SpiceMsgInputsKeyModifiers(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgInputsKeyModifiers.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.keyboard_modifiers = dv.getUint16(at, true); at += 2;
+ return at;
+ },
+}
+
+function SpiceMsgCursorInit(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgCursorInit.prototype =
+{
+ from_buffer: function(a, at, mb)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.position = new SpicePoint16;
+ at = this.position.from_dv(dv, at, mb);
+ this.trail_length = dv.getUint16(at, true); at += 2;
+ this.trail_frequency = dv.getUint16(at, true); at += 2;
+ this.visible = dv.getUint8(at, true); at ++;
+ this.cursor = new SpiceCursor;
+ return this.cursor.from_dv(dv, at, a);
+ },
+}
+
+
+function SpiceMsgCursorSet(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgCursorSet.prototype =
+{
+ from_buffer: function(a, at, mb)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.position = new SpicePoint16;
+ at = this.position.from_dv(dv, at, mb);
+ this.visible = dv.getUint8(at, true); at ++;
+ this.cursor = new SpiceCursor;
+ return this.cursor.from_dv(dv, at, a);
+ },
+}
+
+
+function SpiceMsgcMousePosition(sc, e)
+{
+ // FIXME - figure out how to correctly compute display_id
+ this.display_id = 0;
+ this.buttons_state = sc.buttons_state;
+ if (e)
+ {
+ this.x = e.clientX - sc.display.surfaces[sc.display.primary_surface].canvas.offsetLeft + document.body.scrollLeft;
+ this.y = e.clientY - sc.display.surfaces[sc.display.primary_surface].canvas.offsetTop + document.body.scrollTop;
+ sc.mousex = this.x;
+ sc.mousey = this.y;
+ }
+ else
+ {
+ this.x = this.y = this.buttons_state = 0;
+ }
+}
+
+SpiceMsgcMousePosition.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ dv.setUint32(at, this.x, true); at += 4;
+ dv.setUint32(at, this.y, true); at += 4;
+ dv.setUint16(at, this.buttons_state, true); at += 2;
+ dv.setUint8(at, this.display_id, true); at += 1;
+ return at;
+ },
+ buffer_size: function()
+ {
+ return 11;
+ }
+}
+
+function SpiceMsgcMouseMotion(sc, e)
+{
+ // FIXME - figure out how to correctly compute display_id
+ this.display_id = 0;
+ this.buttons_state = sc.buttons_state;
+ if (e)
+ {
+ this.x = e.clientX - sc.display.surfaces[sc.display.primary_surface].canvas.offsetLeft;
+ this.y = e.clientY - sc.display.surfaces[sc.display.primary_surface].canvas.offsetTop;
+
+ if (sc.mousex !== undefined)
+ {
+ this.x -= sc.mousex;
+ this.y -= sc.mousey;
+ }
+ sc.mousex = e.clientX - sc.display.surfaces[sc.display.primary_surface].canvas.offsetLeft;
+ sc.mousey = e.clientY - sc.display.surfaces[sc.display.primary_surface].canvas.offsetTop;
+ }
+ else
+ {
+ this.x = this.y = this.buttons_state = 0;
+ }
+}
+
+/* Use the same functions as for MousePosition */
+SpiceMsgcMouseMotion.prototype.to_buffer = SpiceMsgcMousePosition.prototype.to_buffer;
+SpiceMsgcMouseMotion.prototype.buffer_size = SpiceMsgcMousePosition.prototype.buffer_size;
+
+function SpiceMsgcMousePress(sc, e)
+{
+ if (e)
+ {
+ this.button = e.button + 1;
+ this.buttons_state = 1 << e.button;
+ sc.buttons_state = this.buttons_state;
+ }
+ else
+ {
+ this.button = SPICE_MOUSE_BUTTON_LEFT;
+ this.buttons_state = SPICE_MOUSE_BUTTON_MASK_LEFT;
+ }
+}
+
+SpiceMsgcMousePress.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ dv.setUint8(at, this.button, true); at ++;
+ dv.setUint16(at, this.buttons_state, true); at += 2;
+ return at;
+ },
+ buffer_size: function()
+ {
+ return 3;
+ }
+}
+
+function SpiceMsgcMouseRelease(sc, e)
+{
+ if (e)
+ {
+ this.button = e.button + 1;
+ this.buttons_state = 0;
+ sc.buttons_state = this.buttons_state;
+ }
+ else
+ {
+ this.button = SPICE_MOUSE_BUTTON_LEFT;
+ this.buttons_state = 0;
+ }
+}
+
+/* Use the same functions as for MousePress */
+SpiceMsgcMouseRelease.prototype.to_buffer = SpiceMsgcMousePress.prototype.to_buffer;
+SpiceMsgcMouseRelease.prototype.buffer_size = SpiceMsgcMousePress.prototype.buffer_size;
+
+
+function SpiceMsgcKeyDown(e)
+{
+ if (e)
+ {
+ this.code = keycode_to_start_scan(e.keyCode);
+ }
+ else
+ {
+ this.code = 0;
+ }
+}
+
+SpiceMsgcKeyDown.prototype =
+{
+ to_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ dv.setUint32(at, this.code, true); at += 4;
+ return at;
+ },
+ buffer_size: function()
+ {
+ return 4;
+ }
+}
+
+function SpiceMsgcKeyUp(e)
+{
+ if (e)
+ {
+ this.code = keycode_to_end_scan(e.keyCode);
+ }
+ else
+ {
+ this.code = 0;
+ }
+}
+
+/* Use the same functions as for KeyDown */
+SpiceMsgcKeyUp.prototype.to_buffer = SpiceMsgcKeyDown.prototype.to_buffer;
+SpiceMsgcKeyUp.prototype.buffer_size = SpiceMsgcKeyDown.prototype.buffer_size;
+
+function SpiceMsgDisplayStreamCreate(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgDisplayStreamCreate.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.surface_id = dv.getUint32(at, true); at += 4;
+ this.id = dv.getUint32(at, true); at += 4;
+ this.flags = dv.getUint8(at, true); at += 1;
+ this.codec_type = dv.getUint8(at, true); at += 1;
+ /*stamp */ at += 8;
+ this.stream_width = dv.getUint32(at, true); at += 4;
+ this.stream_height = dv.getUint32(at, true); at += 4;
+ this.src_width = dv.getUint32(at, true); at += 4;
+ this.src_height = dv.getUint32(at, true); at += 4;
+
+ this.dest = new SpiceRect;
+ at = this.dest.from_dv(dv, at, a);
+ this.clip = new SpiceClip;
+ this.clip.from_dv(dv, at, a);
+ },
+}
+
+function SpiceStreamDataHeader(a, at)
+{
+}
+
+SpiceStreamDataHeader.prototype =
+{
+ from_dv : function(dv, at, mb)
+ {
+ this.id = dv.getUint32(at, true); at += 4;
+ this.multi_media_time = dv.getUint32(at, true); at += 4;
+ return at;
+ },
+}
+
+function SpiceMsgDisplayStreamData(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgDisplayStreamData.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.base = new SpiceStreamDataHeader;
+ at = this.base.from_dv(dv, at, a);
+ this.data_size = dv.getUint32(at, true); at += 4;
+ this.data = dv.u8.subarray(at, at + this.data_size);
+ },
+}
+
+function SpiceMsgDisplayStreamClip(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgDisplayStreamClip.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.id = dv.getUint32(at, true); at += 4;
+ this.clip = new SpiceClip;
+ this.clip.from_dv(dv, at, a);
+ },
+}
+
+function SpiceMsgDisplayStreamDestroy(a, at)
+{
+ this.from_buffer(a, at);
+}
+
+SpiceMsgDisplayStreamDestroy.prototype =
+{
+ from_buffer: function(a, at)
+ {
+ at = at || 0;
+ var dv = new SpiceDataView(a);
+ this.id = dv.getUint32(at, true); at += 4;
+ },
+}
diff --git a/ui/js/spice/spicetype.js b/ui/js/spice/spicetype.js
new file mode 100644
index 0000000..b88ba28
--- /dev/null
+++ b/ui/js/spice/spicetype.js
@@ -0,0 +1,480 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*----------------------------------------------------------------------------
+** Spice types
+** This file contains classes for common spice types.
+** Generally, they are used as helpers in reading and writing messages
+** to and from the server.
+**--------------------------------------------------------------------------*/
+
+function SpiceChannelId()
+{
+}
+SpiceChannelId.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.type = dv.getUint8(at, true); at ++;
+ this.id = dv.getUint8(at, true); at ++;
+ return at;
+ },
+}
+
+function SpiceRect()
+{
+}
+
+SpiceRect.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.top = dv.getUint32(at, true); at += 4;
+ this.left = dv.getUint32(at, true); at += 4;
+ this.bottom = dv.getUint32(at, true); at += 4;
+ this.right = dv.getUint32(at, true); at += 4;
+ return at;
+ },
+ is_same_size : function(r)
+ {
+ if ((this.bottom - this.top) == (r.bottom - r.top) &&
+ (this.right - this.left) == (r.right - r.left) )
+ return true;
+
+ return false;
+ },
+}
+
+function SpiceClipRects()
+{
+}
+
+SpiceClipRects.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ var i;
+ this.num_rects = dv.getUint32(at, true); at += 4;
+ if (this.num_rects > 0)
+ this.rects = [];
+ for (i = 0; i < this.num_rects; i++)
+ {
+ this.rects[i] = new SpiceRect();
+ at = this.rects[i].from_dv(dv, at, mb);
+ }
+ return at;
+ },
+}
+
+function SpiceClip()
+{
+}
+
+SpiceClip.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.type = dv.getUint8(at, true); at ++;
+ if (this.type == SPICE_CLIP_TYPE_RECTS)
+ {
+ this.rects = new SpiceClipRects();
+ at = this.rects.from_dv(dv, at, mb);
+ }
+ return at;
+ },
+}
+
+function SpiceImageDescriptor()
+{
+}
+
+SpiceImageDescriptor.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.id = dv.getUint32(at, true); at += 4;
+ this.id += (dv.getUint32(at, true) << 32); at += 4;
+ this.type = dv.getUint8(at, true); at ++;
+ this.flags = dv.getUint8(at, true); at ++;
+ this.width = dv.getUint32(at, true); at += 4;
+ this.height= dv.getUint32(at, true); at += 4;
+ return at;
+ },
+}
+
+function SpicePalette()
+{
+}
+
+SpicePalette.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ var i;
+ this.unique = [];
+ this.unique[0] = dv.getUint32(at, true); at += 4;
+ this.unique[1] = dv.getUint32(at, true); at += 4;
+ this.num_ents = dv.getUint16(at, true); at += 2;
+ this.ents = [];
+ for (i = 0; i < this.num_ents; i++)
+ {
+ this.ents[i] = dv.getUint32(at, true); at += 4;
+ }
+ return at;
+ },
+}
+
+function SpiceBitmap()
+{
+}
+
+SpiceBitmap.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.format = dv.getUint8(at, true); at++;
+ this.flags = dv.getUint8(at, true); at++;
+ this.x = dv.getUint32(at, true); at += 4;
+ this.y = dv.getUint32(at, true); at += 4;
+ this.stride = dv.getUint32(at, true); at += 4;
+ if (this.flags & SPICE_BITMAP_FLAGS_PAL_FROM_CACHE)
+ {
+ this.palette_id = [];
+ this.palette_id[0] = dv.getUint32(at, true); at += 4;
+ this.palette_id[1] = dv.getUint32(at, true); at += 4;
+ }
+ else
+ {
+ var offset = dv.getUint32(at, true); at += 4;
+ if (offset == 0)
+ this.palette = null;
+ else
+ {
+ this.palette = new SpicePalette;
+ this.palette.from_dv(dv, offset, mb);
+ }
+ }
+ // FIXME - should probably constrain this to the offset
+ // of palette, if non zero
+ this.data = mb.slice(at);
+ at += this.data.byteLength;
+ return at;
+ },
+}
+
+function SpiceImage()
+{
+}
+
+SpiceImage.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.descriptor = new SpiceImageDescriptor;
+ at = this.descriptor.from_dv(dv, at, mb);
+
+ if (this.descriptor.type == SPICE_IMAGE_TYPE_LZ_RGB)
+ {
+ this.lz_rgb = new Object();
+ this.lz_rgb.length = dv.getUint32(at, true); at += 4;
+ var initial_at = at;
+ this.lz_rgb.magic = "";
+ for (var i = 3; i >= 0; i--)
+ this.lz_rgb.magic += String.fromCharCode(dv.getUint8(at + i));
+ at += 4;
+
+ // NOTE: The endian change is *correct*
+ this.lz_rgb.version = dv.getUint32(at); at += 4;
+ this.lz_rgb.type = dv.getUint32(at); at += 4;
+ this.lz_rgb.width = dv.getUint32(at); at += 4;
+ this.lz_rgb.height = dv.getUint32(at); at += 4;
+ this.lz_rgb.stride = dv.getUint32(at); at += 4;
+ this.lz_rgb.top_down = dv.getUint32(at); at += 4;
+
+ var header_size = at - initial_at;
+
+ this.lz_rgb.data = mb.slice(at, this.lz_rgb.length + at - header_size);
+ at += this.lz_rgb.data.byteLength;
+
+ }
+
+ if (this.descriptor.type == SPICE_IMAGE_TYPE_BITMAP)
+ {
+ this.bitmap = new SpiceBitmap;
+ at = this.bitmap.from_dv(dv, at, mb);
+ }
+
+ if (this.descriptor.type == SPICE_IMAGE_TYPE_SURFACE)
+ {
+ this.surface_id = dv.getUint32(at, true); at += 4;
+ }
+
+ if (this.descriptor.type == SPICE_IMAGE_TYPE_JPEG)
+ {
+ this.jpeg = new Object;
+ this.jpeg.data_size = dv.getUint32(at, true); at += 4;
+ this.jpeg.data = mb.slice(at);
+ at += this.jpeg.data.byteLength;
+ }
+
+ if (this.descriptor.type == SPICE_IMAGE_TYPE_JPEG_ALPHA)
+ {
+ this.jpeg_alpha = new Object;
+ this.jpeg_alpha.flags = dv.getUint8(at, true); at += 1;
+ this.jpeg_alpha.jpeg_size = dv.getUint32(at, true); at += 4;
+ this.jpeg_alpha.data_size = dv.getUint32(at, true); at += 4;
+ this.jpeg_alpha.data = mb.slice(at, this.jpeg_alpha.jpeg_size + at);
+ at += this.jpeg_alpha.data.byteLength;
+ // Alpha channel is an LZ image
+ this.jpeg_alpha.alpha = new Object();
+ this.jpeg_alpha.alpha.length = this.jpeg_alpha.data_size - this.jpeg_alpha.jpeg_size;
+ var initial_at = at;
+ this.jpeg_alpha.alpha.magic = "";
+ for (var i = 3; i >= 0; i--)
+ this.jpeg_alpha.alpha.magic += String.fromCharCode(dv.getUint8(at + i));
+ at += 4;
+
+ // NOTE: The endian change is *correct*
+ this.jpeg_alpha.alpha.version = dv.getUint32(at); at += 4;
+ this.jpeg_alpha.alpha.type = dv.getUint32(at); at += 4;
+ this.jpeg_alpha.alpha.width = dv.getUint32(at); at += 4;
+ this.jpeg_alpha.alpha.height = dv.getUint32(at); at += 4;
+ this.jpeg_alpha.alpha.stride = dv.getUint32(at); at += 4;
+ this.jpeg_alpha.alpha.top_down = dv.getUint32(at); at += 4;
+
+ var header_size = at - initial_at;
+
+ this.jpeg_alpha.alpha.data = mb.slice(at, this.jpeg_alpha.alpha.length + at - header_size);
+ at += this.jpeg_alpha.alpha.data.byteLength;
+ }
+
+ if (this.descriptor.type == SPICE_IMAGE_TYPE_QUIC)
+ {
+ this.quic = new SpiceQuic;
+ at = this.quic.from_dv(dv, at, mb);
+ }
+ return at;
+ },
+}
+
+
+function SpiceQMask()
+{
+}
+
+SpiceQMask.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.flags = dv.getUint8(at, true); at++;
+ this.pos = new SpicePoint;
+ at = this.pos.from_dv(dv, at, mb);
+ var offset = dv.getUint32(at, true); at += 4;
+ if (offset == 0)
+ {
+ this.bitmap = null;
+ return at;
+ }
+
+ this.bitmap = new SpiceImage;
+ return this.bitmap.from_dv(dv, offset, mb);
+ },
+}
+
+
+function SpicePattern()
+{
+}
+
+SpicePattern.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ var offset = dv.getUint32(at, true); at += 4;
+ if (offset == 0)
+ {
+ this.pat = null;
+ }
+ else
+ {
+ this.pat = new SpiceImage;
+ this.pat.from_dv(dv, offset, mb);
+ }
+
+ this.pos = new SpicePoint;
+ return this.pos.from_dv(dv, at, mb);
+ }
+}
+
+function SpiceBrush()
+{
+}
+
+SpiceBrush.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.type = dv.getUint8(at, true); at ++;
+ if (this.type == SPICE_BRUSH_TYPE_SOLID)
+ {
+ this.color = dv.getUint32(at, true); at += 4;
+ }
+ else if (this.type == SPICE_BRUSH_TYPE_PATTERN)
+ {
+ this.pattern = new SpicePattern;
+ at = this.pattern.from_dv(dv, at, mb);
+ }
+ return at;
+ },
+}
+
+function SpiceFill()
+{
+}
+
+SpiceFill.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.brush = new SpiceBrush;
+ at = this.brush.from_dv(dv, at, mb);
+ this.rop_descriptor = dv.getUint16(at, true); at += 2;
+ this.mask = new SpiceQMask;
+ return this.mask.from_dv(dv, at, mb);
+ },
+}
+
+
+function SpiceCopy()
+{
+}
+
+SpiceCopy.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ var offset = dv.getUint32(at, true); at += 4;
+ if (offset == 0)
+ {
+ this.src_bitmap = null;
+ }
+ else
+ {
+ this.src_bitmap = new SpiceImage;
+ this.src_bitmap.from_dv(dv, offset, mb);
+ }
+ this.src_area = new SpiceRect;
+ at = this.src_area.from_dv(dv, at, mb);
+ this.rop_descriptor = dv.getUint16(at, true); at += 2;
+ this.scale_mode = dv.getUint8(at, true); at ++;
+ this.mask = new SpiceQMask;
+ return this.mask.from_dv(dv, at, mb);
+ },
+}
+
+function SpicePoint16()
+{
+}
+
+SpicePoint16.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.x = dv.getUint16(at, true); at += 2;
+ this.y = dv.getUint16(at, true); at += 2;
+ return at;
+ },
+}
+
+function SpicePoint()
+{
+}
+
+SpicePoint.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.x = dv.getUint32(at, true); at += 4;
+ this.y = dv.getUint32(at, true); at += 4;
+ return at;
+ },
+}
+
+function SpiceCursorHeader()
+{
+}
+
+SpiceCursorHeader.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.unique = [];
+ this.unique[0] = dv.getUint32(at, true); at += 4;
+ this.unique[1] = dv.getUint32(at, true); at += 4;
+ this.type = dv.getUint8(at, true); at ++;
+ this.width = dv.getUint16(at, true); at += 2;
+ this.height = dv.getUint16(at, true); at += 2;
+ this.hot_spot_x = dv.getUint16(at, true); at += 2;
+ this.hot_spot_y = dv.getUint16(at, true); at += 2;
+ return at;
+ },
+}
+
+function SpiceCursor()
+{
+}
+
+SpiceCursor.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.flags = dv.getUint16(at, true); at += 2;
+ if (this.flags & SPICE_CURSOR_FLAGS_NONE)
+ this.header = null;
+ else
+ {
+ this.header = new SpiceCursorHeader;
+ at = this.header.from_dv(dv, at, mb);
+ this.data = mb.slice(at);
+ at += this.data.byteLength;
+ }
+ return at;
+ },
+}
+
+function SpiceSurface()
+{
+}
+
+SpiceSurface.prototype =
+{
+ from_dv: function(dv, at, mb)
+ {
+ this.surface_id = dv.getUint32(at, true); at += 4;
+ this.width = dv.getUint32(at, true); at += 4;
+ this.height = dv.getUint32(at, true); at += 4;
+ this.format = dv.getUint32(at, true); at += 4;
+ this.flags = dv.getUint32(at, true); at += 4;
+ return at;
+ },
+}
+
+/* FIXME - SpiceImage types lz_plt, jpeg, zlib_glz, and jpeg_alpha are
+ completely unimplemented */
diff --git a/ui/js/spice/ticket.js b/ui/js/spice/ticket.js
new file mode 100644
index 0000000..96577a3
--- /dev/null
+++ b/ui/js/spice/ticket.js
@@ -0,0 +1,250 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+var SHA_DIGEST_LENGTH = 20;
+
+/*----------------------------------------------------------------------------
+** General ticket RSA encryption functions - just good enough to
+** support what we need to send back an encrypted ticket.
+**--------------------------------------------------------------------------*/
+
+
+/*----------------------------------------------------------------------------
+** OAEP padding functions. Inspired by the OpenSSL implementation.
+**--------------------------------------------------------------------------*/
+function MGF1(mask, seed)
+{
+ var i, j, outlen;
+ for (i = 0, outlen = 0; outlen < mask.length; i++)
+ {
+ var combo_buf = new String;
+
+ for (j = 0; j < seed.length; j++)
+ combo_buf += String.fromCharCode(seed[j]);
+ combo_buf += String.fromCharCode((i >> 24) & 255);
+ combo_buf += String.fromCharCode((i >> 16) & 255);
+ combo_buf += String.fromCharCode((i >> 8) & 255);
+ combo_buf += String.fromCharCode((i) & 255);
+
+ var combo_hash = rstr_sha1(combo_buf);
+ for (j = 0; j < combo_hash.length && outlen < mask.length; j++, outlen++)
+ {
+ mask[outlen] = combo_hash.charCodeAt(j);
+ }
+ }
+}
+
+
+function RSA_padding_add_PKCS1_OAEP(tolen, from, param)
+{
+ var seed = new Array(SHA_DIGEST_LENGTH);
+ var rand = new SecureRandom();
+ rand.nextBytes(seed);
+
+ var dblen = tolen - 1 - seed.length;
+ var db = new Array(dblen);
+ var padlen = dblen - from.length - 1;
+ var i;
+
+ if (param === undefined)
+ param = "";
+
+ if (padlen < SHA_DIGEST_LENGTH)
+ {
+ console.log("Error - data too large for key size.");
+ return null;
+ }
+
+ for (i = 0; i < padlen; i++)
+ db[i] = 0;
+
+ var param_hash = rstr_sha1(param);
+ for (i = 0; i < param_hash.length; i++)
+ db[i] = param_hash.charCodeAt(i);
+
+ db[padlen] = 1;
+ for (i = 0; i < from.length; i++)
+ db[i + padlen + 1] = from.charCodeAt(i);
+
+ var dbmask = new Array(dblen);
+ if (MGF1(dbmask, seed) < 0)
+ return null;
+
+ for (i = 0; i < dbmask.length; i++)
+ db[i] ^= dbmask[i];
+
+
+ var seedmask = Array(SHA_DIGEST_LENGTH);
+ if (MGF1(seedmask, db) < 0)
+ return null;
+
+ for (i = 0; i < seedmask.length; i++)
+ seed[i] ^= seedmask[i];
+
+ var ret = new String;
+ ret += String.fromCharCode(0);
+ for (i = 0; i < seed.length; i++)
+ ret += String.fromCharCode(seed[i]);
+ for (i = 0; i < db.length; i++)
+ ret += String.fromCharCode(db[i]);
+ return ret;
+}
+
+
+function asn_get_length(u8, at)
+{
+ var len = u8[at++];
+ if (len > 0x80)
+ {
+ if (len != 0x81)
+ {
+ console.log("Error: we lazily don't support keys bigger than 255 bytes. It'd be easy to fix.");
+ return null;
+ }
+ len = u8[at++];
+ }
+
+ return [ at, len];
+}
+
+function find_sequence(u8, at)
+{
+ var lenblock;
+ at = at || 0;
+ if (u8[at++] != 0x30)
+ {
+ console.log("Error: public key should start with a sequence flag.");
+ return null;
+ }
+
+ lenblock = asn_get_length(u8, at);
+ if (! lenblock)
+ return null;
+ return lenblock;
+}
+
+/*----------------------------------------------------------------------------
+** Extract an RSA key from a memory buffer
+**--------------------------------------------------------------------------*/
+function create_rsa_from_mb(mb, at)
+{
+ var u8 = new Uint8Array(mb);
+ var lenblock;
+ var seq;
+ var ba;
+ var i;
+ var ret;
+
+ /* We have a sequence which contains a sequence followed by a bit string */
+ seq = find_sequence(u8, at);
+ if (! seq)
+ return null;
+
+ at = seq[0];
+ seq = find_sequence(u8, at);
+ if (! seq)
+ return null;
+
+ /* Skip over the contained sequence */
+ at = seq[0] + seq[1];
+ if (u8[at++] != 0x3)
+ {
+ console.log("Error: expecting bit string next.");
+ return null;
+ }
+
+ /* Get the bit string, which is *itself* a sequence. Having fun yet? */
+ lenblock = asn_get_length(u8, at);
+ if (! lenblock)
+ return null;
+
+ at = lenblock[0];
+ if (u8[at] != 0 && u8[at + 1] != 0x30)
+ {
+ console.log("Error: unexpected values in bit string.");
+ return null;
+ }
+
+ /* Okay, now we have a sequence of two binary values, we hope. */
+ seq = find_sequence(u8, at + 1);
+ if (! seq)
+ return null;
+
+ at = seq[0];
+ if (u8[at++] != 0x02)
+ {
+ console.log("Error: expecting integer n next.");
+ return null;
+ }
+ lenblock = asn_get_length(u8, at);
+ if (! lenblock)
+ return null;
+ at = lenblock[0];
+
+ ba = new Array(lenblock[1]);
+ for (i = 0; i < lenblock[1]; i++)
+ ba[i] = u8[at + i];
+
+ ret = new RSAKey();
+ ret.n = new BigInteger(ba);
+
+ at += lenblock[1];
+
+ if (u8[at++] != 0x02)
+ {
+ console.log("Error: expecting integer e next.");
+ return null;
+ }
+ lenblock = asn_get_length(u8, at);
+ if (! lenblock)
+ return null;
+ at = lenblock[0];
+
+ ret.e = u8[at++];
+ for (i = 1; i < lenblock[1]; i++)
+ {
+ ret.e <<= 8;
+ ret.e |= u8[at++];
+ }
+
+ return ret;
+}
+
+function rsa_encrypt(rsa, str)
+{
+ var i;
+ var ret = [];
+ var oaep = RSA_padding_add_PKCS1_OAEP((rsa.n.bitLength()+7)>>3, str);
+ if (! oaep)
+ return null;
+
+ var ba = new Array(oaep.length);
+
+ for (i = 0; i < oaep.length; i++)
+ ba[i] = oaep.charCodeAt(i);
+ var bigint = new BigInteger(ba);
+ var enc = rsa.doPublic(bigint);
+ var h = enc.toString(16);
+ if ((h.length & 1) != 0)
+ h = "0" + h;
+ for (i = 0; i < h.length; i += 2)
+ ret[i / 2] = parseInt(h.substring(i, i + 2), 16);
+ return ret;
+}
diff --git a/ui/js/spice/utils.js b/ui/js/spice/utils.js
new file mode 100644
index 0000000..5ef23d6
--- /dev/null
+++ b/ui/js/spice/utils.js
@@ -0,0 +1,261 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*----------------------------------------------------------------------------
+** Utility settings and functions for Spice
+**--------------------------------------------------------------------------*/
+var DEBUG = 0;
+var DUMP_DRAWS = false;
+var DUMP_CANVASES = false;
+
+
+/*----------------------------------------------------------------------------
+** combine_array_buffers
+** Combine two array buffers.
+** FIXME - this can't be optimal. See wire.js about eliminating the need.
+**--------------------------------------------------------------------------*/
+function combine_array_buffers(a1, a2)
+{
+ var in1 = new Uint8Array(a1);
+ var in2 = new Uint8Array(a2);
+ var ret = new ArrayBuffer(a1.byteLength + a2.byteLength);
+ var out = new Uint8Array(ret);
+ var o = 0;
+ var i;
+ for (i = 0; i < in1.length; i++)
+ out[o++] = in1[i];
+ for (i = 0; i < in2.length; i++)
+ out[o++] = in2[i];
+
+ return ret;
+}
+
+/*----------------------------------------------------------------------------
+** hexdump_buffer
+**--------------------------------------------------------------------------*/
+function hexdump_buffer(a)
+{
+ var mg = new Uint8Array(a);
+ var hex = "";
+ var str = "";
+ var last_zeros = 0;
+ for (var i = 0; i < mg.length; i++)
+ {
+ var h = Number(mg[i]).toString(16);
+ if (h.length == 1)
+ hex += "0";
+ hex += h + " ";
+
+ str += String.fromCharCode(mg[i]);
+
+ if ((i % 16 == 15) || (i == (mg.length - 1)))
+ {
+ while (i % 16 != 15)
+ {
+ hex += " ";
+ i++;
+ }
+
+ if (last_zeros == 0)
+ console.log(hex + " | " + str);
+
+ if (hex == "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ")
+ {
+ if (last_zeros == 1)
+ {
+ console.log(".");
+ last_zeros++;
+ }
+ else if (last_zeros == 0)
+ last_zeros++;
+ }
+ else
+ last_zeros = 0;
+
+ hex = str = "";
+ }
+ }
+}
+
+/*----------------------------------------------------------------------------
+** Converting keycodes to AT scancodes is very hard.
+** luckly there are some resources on the web and in the Xorg driver that help
+** us figure out what browser depenend keycodes match to what scancodes.
+**
+** This will most likely not work for non US keyboard and browsers other than
+** modern Chrome and FireFox.
+**--------------------------------------------------------------------------*/
+var common_scanmap = [];
+common_scanmap['Q'.charCodeAt(0)] = KEY_Q;
+common_scanmap['W'.charCodeAt(0)] = KEY_W;
+common_scanmap['E'.charCodeAt(0)] = KEY_E;
+common_scanmap['R'.charCodeAt(0)] = KEY_R;
+common_scanmap['T'.charCodeAt(0)] = KEY_T;
+common_scanmap['Y'.charCodeAt(0)] = KEY_Y;
+common_scanmap['U'.charCodeAt(0)] = KEY_U;
+common_scanmap['I'.charCodeAt(0)] = KEY_I;
+common_scanmap['O'.charCodeAt(0)] = KEY_O;
+common_scanmap['P'.charCodeAt(0)] = KEY_P;
+common_scanmap['A'.charCodeAt(0)] = KEY_A;
+common_scanmap['S'.charCodeAt(0)] = KEY_S;
+common_scanmap['D'.charCodeAt(0)] = KEY_D;
+common_scanmap['F'.charCodeAt(0)] = KEY_F;
+common_scanmap['G'.charCodeAt(0)] = KEY_G;
+common_scanmap['H'.charCodeAt(0)] = KEY_H;
+common_scanmap['J'.charCodeAt(0)] = KEY_J;
+common_scanmap['K'.charCodeAt(0)] = KEY_K;
+common_scanmap['L'.charCodeAt(0)] = KEY_L;
+common_scanmap['Z'.charCodeAt(0)] = KEY_Z;
+common_scanmap['X'.charCodeAt(0)] = KEY_X;
+common_scanmap['C'.charCodeAt(0)] = KEY_C;
+common_scanmap['V'.charCodeAt(0)] = KEY_V;
+common_scanmap['B'.charCodeAt(0)] = KEY_B;
+common_scanmap['N'.charCodeAt(0)] = KEY_N;
+common_scanmap['M'.charCodeAt(0)] = KEY_M;
+common_scanmap[' '.charCodeAt(0)] = KEY_Space;
+common_scanmap[13] = KEY_Enter;
+common_scanmap[27] = KEY_Escape;
+common_scanmap[8] = KEY_BackSpace;
+common_scanmap[9] = KEY_Tab;
+common_scanmap[16] = KEY_ShiftL;
+common_scanmap[17] = KEY_LCtrl;
+common_scanmap[18] = KEY_Alt;
+common_scanmap[20] = KEY_CapsLock;
+common_scanmap[144] = KEY_NumLock;
+common_scanmap[112] = KEY_F1;
+common_scanmap[113] = KEY_F2;
+common_scanmap[114] = KEY_F3;
+common_scanmap[115] = KEY_F4;
+common_scanmap[116] = KEY_F5;
+common_scanmap[117] = KEY_F6;
+common_scanmap[118] = KEY_F7;
+common_scanmap[119] = KEY_F8;
+common_scanmap[120] = KEY_F9;
+common_scanmap[121] = KEY_F10;
+common_scanmap[122] = KEY_F11;
+common_scanmap[123] = KEY_F12;
+
+/* These externded scancodes do not line up with values from atKeynames */
+common_scanmap[42] = 99;
+common_scanmap[19] = 101; // Break
+common_scanmap[111] = 0xE035; // KP_Divide
+common_scanmap[106] = 0xE037; // KP_Multiply
+common_scanmap[36] = 0xE047; // Home
+common_scanmap[38] = 0xE048; // Up
+common_scanmap[33] = 0xE049; // PgUp
+common_scanmap[37] = 0xE04B; // Left
+common_scanmap[39] = 0xE04D; // Right
+common_scanmap[35] = 0xE04F; // End
+common_scanmap[40] = 0xE050; // Down
+common_scanmap[34] = 0xE051; // PgDown
+common_scanmap[45] = 0xE052; // Insert
+common_scanmap[46] = 0xE053; // Delete
+common_scanmap[44] = 0x2A37; // Print
+
+/* These are not common between ALL browsers but are between Firefox and DOM3 */
+common_scanmap['1'.charCodeAt(0)] = KEY_1;
+common_scanmap['2'.charCodeAt(0)] = KEY_2;
+common_scanmap['3'.charCodeAt(0)] = KEY_3;
+common_scanmap['4'.charCodeAt(0)] = KEY_4;
+common_scanmap['5'.charCodeAt(0)] = KEY_5;
+common_scanmap['6'.charCodeAt(0)] = KEY_6;
+common_scanmap['7'.charCodeAt(0)] = KEY_7;
+common_scanmap['8'.charCodeAt(0)] = KEY_8;
+common_scanmap['9'.charCodeAt(0)] = KEY_9;
+common_scanmap['0'.charCodeAt(0)] = KEY_0;
+common_scanmap[145] = KEY_ScrollLock;
+common_scanmap[103] = KEY_KP_7;
+common_scanmap[104] = KEY_KP_8;
+common_scanmap[105] = KEY_KP_9;
+common_scanmap[100] = KEY_KP_4;
+common_scanmap[101] = KEY_KP_5;
+common_scanmap[102] = KEY_KP_6;
+common_scanmap[107] = KEY_KP_Plus;
+common_scanmap[97] = KEY_KP_1;
+common_scanmap[98] = KEY_KP_2;
+common_scanmap[99] = KEY_KP_3;
+common_scanmap[96] = KEY_KP_0;
+common_scanmap[110] = KEY_KP_Decimal;
+common_scanmap[191] = KEY_Slash;
+common_scanmap[190] = KEY_Period;
+common_scanmap[188] = KEY_Comma;
+common_scanmap[220] = KEY_BSlash;
+common_scanmap[192] = KEY_Tilde;
+common_scanmap[222] = KEY_Quote;
+common_scanmap[219] = KEY_LBrace;
+common_scanmap[221] = KEY_RBrace;
+
+common_scanmap[91] = 0xE05B; //KEY_LMeta
+common_scanmap[92] = 0xE05C; //KEY_RMeta
+common_scanmap[93] = 0xE05D; //KEY_Menu
+
+/* Firefox/Mozilla codes */
+var firefox_scanmap = [];
+firefox_scanmap[109] = KEY_Minus;
+firefox_scanmap[61] = KEY_Equal;
+firefox_scanmap[59] = KEY_SemiColon;
+
+/* DOM3 codes */
+var DOM_scanmap = [];
+DOM_scanmap[189] = KEY_Minus;
+DOM_scanmap[187] = KEY_Equal;
+DOM_scanmap[186] = KEY_SemiColon;
+
+function get_scancode(code)
+{
+ if (common_scanmap[code] === undefined)
+ {
+ if (navigator.userAgent.indexOf("Firefox") != -1)
+ return firefox_scanmap[code];
+ else
+ return DOM_scanmap[code];
+ }
+ else
+ return common_scanmap[code];
+}
+
+function keycode_to_start_scan(code)
+{
+ var scancode = get_scancode(code);
+ if (scancode === undefined)
+ {
+ alert('no map for ' + code);
+ return 0;
+ }
+
+ if (scancode < 0x100) {
+ return scancode;
+ } else {
+ return 0xe0 | ((scancode - 0x100) << 8);
+ }
+}
+
+function keycode_to_end_scan(code)
+{
+ var scancode = get_scancode(code);
+ if (scancode === undefined)
+ return 0;
+
+ if (scancode < 0x100) {
+ return scancode | 0x80;
+ } else {
+ return 0x80e0 | ((scancode - 0x100) << 8);
+ }
+}
diff --git a/ui/js/spice/wire.js b/ui/js/spice/wire.js
new file mode 100644
index 0000000..2c7f096
--- /dev/null
+++ b/ui/js/spice/wire.js
@@ -0,0 +1,123 @@
+"use strict";
+/*
+ Copyright (C) 2012 by Jeremy P. White <jwhite(a)codeweavers.com>
+
+ This file is part of spice-html5.
+
+ spice-html5 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ spice-html5 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with spice-html5. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*--------------------------------------------------------------------------------------
+** SpiceWireReader
+** This class will receive messages from a WebSocket and relay it to a given
+** callback. It will optionally save and pass along a header, useful in processing
+** the mini message format.
+**--------------------------------------------------------------------------------------*/
+function SpiceWireReader(sc, callback)
+{
+ this.sc = sc;
+ this.callback = callback;
+ this.needed = 0;
+
+ this.buffers = [];
+
+ this.sc.ws.wire_reader = this;
+ this.sc.ws.binaryType = "arraybuffer";
+ this.sc.ws.addEventListener('message', wire_blob_catcher);
+}
+
+SpiceWireReader.prototype =
+{
+
+ /*------------------------------------------------------------------------
+ ** Process messages coming in from our WebSocket
+ **----------------------------------------------------------------------*/
+ inbound: function (mb)
+ {
+ var at;
+
+ /* Just buffer if we don't need anything yet */
+ if (this.needed == 0)
+ {
+ this.buffers.push(mb);
+ return;
+ }
+
+ /* Optimization - if we have just one inbound block, and it's
+ suitable for our needs, just use it. */
+ if (this.buffers.length == 0 && mb.byteLength >= this.needed)
+ {
+ if (mb.byteLength > this.needed)
+ {
+ this.buffers.push(mb.slice(this.needed));
+ mb = mb.slice(0, this.needed);
+ }
+ this.callback.call(this.sc, mb,
+ this.saved_msg_header || undefined);
+ }
+ else
+ {
+ this.buffers.push(mb);
+ }
+
+
+ /* If we have fragments that add up to what we need, combine them */
+ /* FIXME - it would be faster to revise the processing code to handle
+ ** multiple fragments directly. Essentially, we should be
+ ** able to do this without any slice() or combine_array_buffers() calls */
+ while (this.buffers.length > 1 && this.buffers[0].byteLength < this.needed)
+ {
+ var mb1 = this.buffers.shift();
+ var mb2 = this.buffers.shift();
+
+ this.buffers.unshift(combine_array_buffers(mb1, mb2));
+ }
+
+
+ while (this.buffers.length > 0 && this.buffers[0].byteLength >= this.needed)
+ {
+ mb = this.buffers.shift();
+ if (mb.byteLength > this.needed)
+ {
+ this.buffers.unshift(mb.slice(this.needed));
+ mb = mb.slice(0, this.needed);
+ }
+ this.callback.call(this.sc, mb,
+ this.saved_msg_header || undefined);
+ }
+
+ },
+
+ request: function(n)
+ {
+ this.needed = n;
+ },
+
+ save_header: function(h)
+ {
+ this.saved_msg_header = h;
+ },
+
+ clear_header: function()
+ {
+ this.saved_msg_header = undefined;
+ },
+}
+
+function wire_blob_catcher(e)
+{
+ DEBUG > 1 && console.log(">> WebSockets.onmessage");
+ DEBUG > 1 && console.log("id " + this.wire_reader.sc.connection_id +"; type " + this.wire_reader.sc.type);
+ SpiceWireReader.prototype.inbound.call(this.wire_reader, e.data);
+}
diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js
index fbcf4a2..a18288f 100644
--- a/ui/js/src/kimchi.api.js
+++ b/ui/js/src/kimchi.api.js
@@ -309,6 +309,27 @@ var kimchi = {
});
},
+ spiceToVM : function(vm) {
+ kimchi.requestJSON({
+ url : '/config',
+ type : 'GET',
+ dataType : 'json'
+ }).done(function(data, textStatus, xhr) {
+ http_port = data['http_port'];
+ kimchi.requestJSON({
+ url : "/vms/" + encodeURIComponent(vm) + "/connect",
+ type : "POST",
+ dataType : "json"
+ }).done(function(data, textStatus, xhr) {
+ url = 'http://' + location.hostname + ':' + http_port;
+ url += "/spice.html?port=" + data.graphics.port + "&listen=" + data.graphics.listen;
+ window.open(url);
+ });
+ }).error(function() {
+ kimchi.message.error(i18n['msg.fail.get.config']);
+ });
+ },
+
listVMs : function(suc, err) {
kimchi.requestJSON({
url : kimchi.url + 'vms',
diff --git a/ui/js/src/kimchi.guest_main.js b/ui/js/src/kimchi.guest_main.js
index a36c59c..bff5d8f 100644
--- a/ui/js/src/kimchi.guest_main.js
+++ b/ui/js/src/kimchi.guest_main.js
@@ -119,6 +119,10 @@ kimchi.initVmButtonsAction = function() {
kimchi.vncToVM($(this).data('vm'));
});
+ $(".vm-spice").on("click", function(event) {
+ kimchi.spiceToVM($(this).data('vm'));
+ });
+
kimchi.init_button_stat();
};
@@ -127,13 +131,25 @@ kimchi.init_button_stat = function() {
$('.vm-action').each(function() {
var vm_action = $(this);
var vm_vnc = vm_action.find('.vm-vnc');
- if ((vm_action.data('graphics') === 'vnc')
- && (vm_action.data('vmstate') === 'running')) {
- vm_vnc.removeAttr('disabled');
+ var vm_spice = vm_action.find('.vm-spice');
+ if (vm_action.data('graphics') === 'vnc') {
+ vm_spice.hide();
+ if (vm_action.data('vmstate') === 'running') {
+ vm_vnc.removeAttr('disabled');
+ } else {
+ vm_vnc.attr('disabled', 'disabled');
+ }
+ } else if (vm_action.data('graphics') === 'spice') {
+ vm_vnc.hide();
+ if (vm_action.data('vmstate') === 'running') {
+ vm_spice.removeAttr('disabled');
+ } else {
+ vm_spice.attr('disabled', 'disabled');
+ }
} else {
- vm_vnc.attr('disabled', 'disabled');
+ vm_vnc.hide();
+ vm_spice.hide();
}
-
var editButton = vm_action.find('.vm-edit');
editButton.prop('disabled', vm_action.data('vmstate') !== 'shutoff');
})
diff --git a/ui/pages/guest.html.tmpl b/ui/pages/guest.html.tmpl
index 6bd6853..6d83d57 100644
--- a/ui/pages/guest.html.tmpl
+++ b/ui/pages/guest.html.tmpl
@@ -58,6 +58,7 @@
<span class="text">$_("Actions")</span><span class="arrow"></span>
<div class="popover actionsheet right-side" style="width: 250px">
<button class="button-big vm-vnc" data-vm="{name}"><span class="text">VNC</span></button>
+ <button class="button-big vm-spice" data-vm="{name}"><span class="text">SPICE</span></button>
<button class="button-big vm-edit" data-vm="{name}"><span class="text">$_("Edit")</span></button>
<a class="button-big red vm-delete" data-vm="{name}">$_("Delete")</a>
</div>
--
1.7.1
2
1
From: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>
add 'networks' option for template get/create/update
ShaoHe Feng (4):
template supports networks: let template xml support more networks
template supports networks: update API
template supports networks: update controller and json schema
template supports networks: update model
docs/API.md | 4 ++++
src/kimchi/API.json | 14 ++++++++++++++
src/kimchi/controller.py | 3 ++-
src/kimchi/model.py | 6 ++++++
src/kimchi/osinfo.py | 5 +++--
src/kimchi/vmtemplate.py | 20 ++++++++++++++++----
6 files changed, 45 insertions(+), 7 deletions(-)
--
1.8.4.2
2
9
27 Dec '13
We define hard coded template for different distros. It's not
flexible to generate templates for other platforms or new distros.
This patch changes to dynamically generate vm template. With
this change, it can support new distro, like Fedora20, without any
modifications.
Signed-off-by: Mark Wu <wudxw(a)linux.vnet.ibm.com>
---
src/kimchi/osinfo.py | 174 ++++++++++-------------------------------------
src/kimchi/vmtemplate.py | 2 +-
tests/test_osinfo.py | 28 ++++++--
3 files changed, 60 insertions(+), 144 deletions(-)
diff --git a/src/kimchi/osinfo.py b/src/kimchi/osinfo.py
index 20a93a6..b141d9e 100644
--- a/src/kimchi/osinfo.py
+++ b/src/kimchi/osinfo.py
@@ -22,136 +22,23 @@
import copy
import os
+from distutils.version import LooseVersion
+
+
+common_spec = {'cpus': 1, 'cpu_cores': 1, 'cpu_threads': 1, 'memory': 1024,
+ 'disks': [{'index': 0, 'size': 10}], 'cdrom_bus': 'ide',
+ 'cdrom_index': 2}
+
+
+modern_spec = dict(common_spec, disk_bus='virtio', nic_model='virtio')
+
+
+old_spec = dict(common_spec, disk_bus='ide', nic_model='e1000')
+
+
+modern_version_bases = {'debian': '6.0', 'ubuntu': '7.10', 'opensuse': '10.3',
+ 'centos': '5.3', 'rhel': '6.0', 'fedora': '16'}
-osinfo = [
- # Entries are searched in order and the first match will be returned
- ('debian', {
- 'version': lambda d,v: bool(d == 'debian' and v in ('6.0', '7.0')),
- 'icon': 'images/icon-debian.png',
- 'cpus': 1, 'cpu_cores': 1, 'cpu_threads': 1,
- 'memory': 1024,
- 'disks': [{'index': 0, 'size': 10}],
- 'disk_bus': 'virtio', 'nic_model': 'virtio',
- 'cdrom_bus': 'ide', 'cdrom_index': 2,
- }),
- ('debian-old', {
- 'version': lambda d,v: bool(d == 'debian'),
- 'icon': 'images/icon-debian.png',
- 'cpus': 1, 'cpu_cores': 1, 'cpu_threads': 1,
- 'memory': 1024,
- 'disks': [{'index': 0, 'size': 10}],
- 'disk_bus': 'ide', 'nic_model': 'e1000',
- 'cdrom_bus': 'ide', 'cdrom_index': 2,
- }),
- ('ubuntu', {
- 'version': lambda d,v: bool(d == 'ubuntu' and v in
- ('7.10', '8.04', '8.10', '9.04', '9.10', '10.04', '10.10',
- '11.04', '11.10', '12.04', '12.10', '13.04', '13.10')),
- 'icon': 'images/icon-ubuntu.png',
- 'cpus': 1, 'cpu_cores': 1, 'cpu_threads': 1,
- 'memory': 1024,
- 'disks': [{'index': 0, 'size': 10}],
- 'disk_bus': 'virtio', 'nic_model': 'virtio',
- 'cdrom_bus': 'ide', 'cdrom_index': 2,
- }),
- ('ubuntu-old', {
- 'version': lambda d,v: bool(d == 'ubuntu'),
- 'icon': 'images/icon-ubuntu.png',
- 'cpus': 1, 'cpu_cores': 1, 'cpu_threads': 1,
- 'memory': 1024,
- 'disks': [{'index': 0, 'size': 10}],
- 'disk_bus': 'ide', 'nic_model': 'e1000',
- 'cdrom_bus': 'ide', 'cdrom_index': 2,
- }),
- ('opensuse', {
- 'version': lambda d,v: bool(d == 'opensuse' and v in
- ('10.3', '11.0', '11.1', '11.2', '11.3', '11.4', '12.1', '12.2',
- '12.3',)),
- 'icon': 'images/icon-opensuse.png',
- 'cpus': 1, 'cpu_cores': 1, 'cpu_threads': 1,
- 'memory': 1024,
- 'disks': [{'index': 0, 'size': 10}],
- 'disk_bus': 'virtio', 'nic_model': 'virtio',
- 'cdrom_bus': 'ide', 'cdrom_index': 2,
- }),
- ('opensuse-old', {
- 'version': lambda d,v: bool(d == 'opensuse'),
- 'icon': 'images/icon-opensuse.png',
- 'cpus': 1, 'cpu_cores': 1, 'cpu_threads': 1,
- 'memory': 1024,
- 'disks': [{'index': 0, 'size': 10}],
- 'disk_bus': 'ide', 'nic_model': 'e1000',
- 'cdrom_bus': 'ide', 'cdrom_index': 2,
- }),
- ('fedora', {
- 'version': lambda d,v: bool(d == 'fedora' and v in
- ('16', '17', '18', '19')),
- 'icon': 'images/icon-fedora.png',
- 'cpus': 1, 'cpu_cores': 1, 'cpu_threads': 1,
- 'memory': 1024,
- 'disks': [{'index': 0, 'size': 10}],
- 'disk_bus': 'virtio', 'nic_model': 'virtio',
- 'cdrom_bus': 'ide', 'cdrom_index': 2,
- }),
- ('fedora-old', {
- 'version': lambda d,v: bool(d == 'fedora'),
- 'icon': 'images/icon-fedora.png',
- 'cpus': 1, 'cpu_cores': 1, 'cpu_threads': 1,
- 'memory': 1024,
- 'disks': [{'index': 0, 'size': 10}],
- 'disk_bus': 'ide', 'nic_model': 'e1000',
- 'cdrom_bus': 'ide', 'cdrom_index': 2,
- }),
- ('rhel', {
- 'version': lambda d,v: bool(d == 'rhel' and
- v.startswith('6.')),
- 'icon': 'images/icon-vm.png',
- 'cpus': 1, 'cpu_cores': 1, 'cpu_threads': 1,
- 'memory': 1024,
- 'disks': [{'index': 0, 'size': 10}],
- 'disk_bus': 'virtio', 'nic_model': 'virtio',
- 'cdrom_bus': 'ide', 'cdrom_index': 2,
- }),
- ('rhel-old', {
- 'version': lambda d,v: bool(d == 'rhel'),
- 'icon': 'images/icon-vm.png',
- 'cpus': 1, 'cpu_cores': 1, 'cpu_threads': 1,
- 'memory': 1024,
- 'disks': [{'index': 0, 'size': 10}],
- 'disk_bus': 'ide', 'nic_model': 'e1000',
- 'cdrom_bus': 'ide', 'cdrom_index': 2,
- }),
- ('centos', {
- 'version': lambda d,v: bool(d == 'centos' and
- (v in ('5.3', '5.4', '5.5') or v.startswith('6.'))),
- 'icon': 'images/icon-centos.png',
- 'cpus': 1, 'cpu_cores': 1, 'cpu_threads': 1,
- 'memory': 1024,
- 'disks': [{'index': 0, 'size': 10}],
- 'disk_bus': 'virtio', 'nic_model': 'virtio',
- 'cdrom_bus': 'ide', 'cdrom_index': 2,
- }),
- ('centos-old', {
- 'version': lambda d,v: bool(d == 'centos'),
- 'icon': 'images/icon-centos.png',
- 'cpus': 1, 'cpu_cores': 1, 'cpu_threads': 1,
- 'memory': 1024,
- 'disks': [{'index': 0, 'size': 10}],
- 'disk_bus': 'ide', 'nic_model': 'e1000',
- 'cdrom_bus': 'ide', 'cdrom_index': 2,
- }),
- ('unknown', {
- 'version': lambda d,v: True,
- 'icon': 'images/icon-vm.png',
- 'os_distro': 'unknown', 'os_version': 'unknown',
- 'cpus': 1, 'cpu_cores': 1, 'cpu_threads': 1,
- 'memory': 1024,
- 'cdrom': '',
- 'disks': [{'index': 0, 'size': 10}],
- 'disk_bus': 'ide', 'nic_model': 'e1000',
- 'cdrom_bus': 'ide', 'cdrom_index': 2,
- }),
-]
isolinks = {
'debian': {
@@ -181,13 +68,22 @@ def lookup(distro, version):
'defaults' and merging the parameters given for the identified OS. If
known, a link to a remote install CD is added.
"""
- for name, entry in osinfo:
- # Test if this entry is a valid match
- if entry['version'](distro, version):
- params = copy.copy(defaults)
- params['os_distro'] = distro
- params['os_version'] = version
- params.update(entry)
- params['cdrom'] = isolinks.get(distro, {}).get(version, '')
- del params['version'] # Don't pass around the version function
- return (name, params)
+ params = copy.copy(defaults)
+ params['os_distro'] = distro
+ params['os_version'] = version
+ params['cdrom'] = isolinks.get(distro, {}).get(version, '')
+
+ for name, base_version in modern_version_bases.iteritems():
+ if name == distro:
+ params['icon'] = 'images/icon-%s.png' % distro
+ if LooseVersion(version) >= LooseVersion(base_version):
+ params.update(modern_spec)
+ else:
+ params.update(old_spec)
+ break
+ else:
+ params['icon'] = 'images/icon-vm.png'
+ params['os_distro'] = params['os_version'] = "unknown"
+ params.update(old_spec)
+
+ return params
diff --git a/src/kimchi/vmtemplate.py b/src/kimchi/vmtemplate.py
index dd43faa..f92af49 100644
--- a/src/kimchi/vmtemplate.py
+++ b/src/kimchi/vmtemplate.py
@@ -70,7 +70,7 @@ class VMTemplate(object):
# Fetch defaults based on the os distro and version
os_distro = args.get('os_distro', iso_distro)
os_version = args.get('os_version', iso_version)
- name, entry = osinfo.lookup(os_distro, os_version)
+ entry = osinfo.lookup(os_distro, os_version)
self.info.update(entry)
# Override with the passed in parameters
diff --git a/tests/test_osinfo.py b/tests/test_osinfo.py
index f92567d..da3e1d2 100644
--- a/tests/test_osinfo.py
+++ b/tests/test_osinfo.py
@@ -25,16 +25,36 @@ from kimchi.osinfo import *
class OSInfoTests(unittest.TestCase):
def test_default_lookup(self):
- name, entry = lookup(None, None)
- self.assertEquals(name, 'unknown')
+ entry = lookup(None, None)
self.assertEquals('unknown', entry['os_distro'])
self.assertEquals('unknown', entry['os_version'])
self.assertEquals('default', entry['network'])
def test_fedora_lookup(self):
cd = 'http://fedora.mirrors.tds.net/pub/fedora/releases/17/Live/x86_64/Fedora-17-…'
- name, entry = lookup('fedora', '17')
- self.assertEquals(name, 'fedora')
+ entry = lookup('fedora', '17')
self.assertEquals(10, entry['disks'][0]['size'])
self.assertEquals(cd, entry['cdrom'])
self.assertEquals('/storagepools/default', entry['storagepool'])
+
+ def test_old_distros(self):
+ old_versions = {'debian': '5.0', 'ubuntu': '7.04', 'opensuse': '10.1',
+ 'centos': '5.1', 'rhel': '5.1', 'fedora': '15'}
+ for distro, version in old_versions.iteritems():
+ entry = lookup(distro, version)
+ self.assertEquals(entry['disk_bus'], 'ide')
+ self.assertEquals(entry['nic_model'], 'e1000')
+
+ def test_modern_bases(self):
+ for distro, version in modern_version_bases.iteritems():
+ entry = lookup(distro, version)
+ self.assertEquals(entry['disk_bus'], 'virtio')
+ self.assertEquals(entry['nic_model'], 'virtio')
+
+ def test_modern_distros(self):
+ old_versions = {'debian': '7.0', 'ubuntu': '12.04', 'opensuse': '12.3',
+ 'centos': '6.4', 'rhel': '6.3', 'fedora': '18'}
+ for distro, version in old_versions.iteritems():
+ entry = lookup(distro, version)
+ self.assertEquals(entry['disk_bus'], 'virtio')
+ self.assertEquals(entry['nic_model'], 'virtio')
--
1.8.3.1
4
7
v4-v3:
1. Remove not used module "copy" in vmtemplate.py(Thanks Aline)
2. Fix alignment problem in model.py(Thanks Aline)
3. Use type property of jsonschema to validate both ipv4 and ipv6(Thanks Aline)
REF: http://stackoverflow.com/questions/14550235/json-schema-away-to-specify-an-…
4. Use "$ref" to simply API.json, remove dumplicate content(Thanks Rodrigo)
v3-v2:
1. Rebase to upstream
2. Improve/simplify some code according to reviewers' advice(Thanks Royce, Markwu and Aline).
3. Reimplement parameters validation using jsonschema.
4. Remove unnecessary test code in model.py.
5. Split patch to several smaller commits according to logic(Thanks Royce).
v2-v1: Rebased to upstream and resend
v1: Add spice backend support for kimchi
apporc (5):
Add spice backend support for kimchi
Update mockmodel for spice support
Validate graphics parameters input by users
Update test case for graphics support
Add graphics parameters description in API.md
docs/API.md | 43 ++++++++++++++++++++++-
src/kimchi/API.json | 30 +++++++++++++++--
src/kimchi/controller.py | 14 +++++---
src/kimchi/mockmodel.py | 49 ++++++++++++++++++---------
src/kimchi/model.py | 43 +++++++++++++----------
src/kimchi/osinfo.py | 5 +--
src/kimchi/vmtemplate.py | 31 +++++++++++++++--
tests/test_mockmodel.py | 2 ++
tests/test_model.py | 27 ++++++++++++++-
tests/test_rest.py | 88 +++++++++++++++++++++++++++++++++++++++++++++---
tests/test_vmtemplate.py | 42 ++++++++++++++++++++---
11 files changed, 319 insertions(+), 55 deletions(-)
--
1.8.1.2
2
10
Hi,
This patch series is about creating iSCSI storage pool. I firstly
refactored a little bit to make the storage pool creation API nicer,
then add unit test for pool XML generation. The front-end is also
changed to suit the new back-end API. Then I implement iSCSI pool XML
generation and also add a test to actually create a iSCSI pool and
delete it.
v1 -> v2
I take Royce's advice and arrange pool source information under a
"source" key. A pool definition is now like the following.
{'type': 'netfs', 'name': 'XXX,
'source': {'host': 'X.X.X.X', 'path': '/export/path'}}
or
{'type': 'logical', 'name': 'XXX,
'source': {'devices': ['/dev/sdX', '/dev/sdY']}}
Sheldon thinks the pool XML generation code should be put into a
separated file. I also see Bing Bu's email on refactoring.
Ming says we can make the hard coded path constants configurable.
I think in future we may also add code for validating the pool
source, for example, try to mount an NFS export or login iSCSI
target. All these worth an overall refactor so they are not the
scope of this patch series. I think we can do this after the iSCSI
support is ready.
As Pradeep suggested, I add test to create and delete iSCSI storage
pool in test_model.py. It requires an existing iSCSI target on
localhost. You can use targetcli to create one, or it just skips the
test if it detects no target to use.
Thanks for all your review! I want to hear your comments on the new
API schema, so I can make it stable and save time for front-end work.
Zhou Zheng Sheng (5):
storagepool: dispatch _get_pool_xml() to respective
_get_XXX_storagepool_xml() function
storagepool: rename and consolidate arguments of creating (back end)
storagepool: rename and consolidate arguments of creating (front end)
storagepool: Support Creating iSCSI storagepool in model.py
test_model: test creating iSCSI storage pool
Makefile.am | 2 +
docs/API.md | 24 +++--
src/kimchi/Makefile.am | 1 +
src/kimchi/iscsi.py | 57 +++++++++++
src/kimchi/model.py | 160 +++++++++++++++++++++----------
tests/Makefile.am | 1 +
tests/test_model.py | 91 ++++++++++--------
tests/test_storagepool.py | 111 +++++++++++++++++++++
ui/js/src/kimchi.storagepool_add_main.js | 13 ++-
9 files changed, 361 insertions(+), 99 deletions(-)
create mode 100644 src/kimchi/iscsi.py
create mode 100644 tests/test_storagepool.py
--
1.7.11.7
2
17
[PATCH v3 1/2] logical pool fixes: only list leaf devices, and read file instead of run "cat"
by Zhou Zheng Sheng 27 Dec '13
by Zhou Zheng Sheng 27 Dec '13
27 Dec '13
Some devices are parent of other devices. We should just list the leaf
devices but not parent devices. For example, if a disk contains
partitions, we should only list the partitions. If a disk is held by
multipath device, we should not list the disk.
Currently we strip the last charactter from "vda1" to get "vda" and
ignore the parent "vda" device. This may fails on disk contains lots of
logical partitions, for example "vda10"'s parent is not "vda1". Another
problem is this method can not find the parent-child relation ship
between a multipath device and its slaves.
The most accurate information is on sysfs, and lsblk lists all children
device of the requested device based on sysfs. So when "lsblk -P
devices" prints only one line, it's the device itself, when it prints
more lines, they are the children devices. This patch use this method to
detect if a device a leaf one.
This patch also avoids start new "cat" process to read uevent file, it
opens the uevent file and read it directly.
v2:
Assign string literal to constants thus avoid breaking the line.
v3:
Save N lsblk invocations when list all devices by collecting major:min
and device node path in one run. N is number of block devices.
Use short-circuiting operator "and" instead of all to save extra lsblk
invocation.
Signed-off-by: Zhou Zheng Sheng <zhshzhou(a)linux.vnet.ibm.com>
---
src/kimchi/disks.py | 83 ++++++++++++++++++++++++++++++++---------------------
1 file changed, 50 insertions(+), 33 deletions(-)
diff --git a/src/kimchi/disks.py b/src/kimchi/disks.py
index a054961..34b9e01 100644
--- a/src/kimchi/disks.py
+++ b/src/kimchi/disks.py
@@ -27,27 +27,13 @@ from kimchi.exception import OperationFailed
from kimchi.utils import kimchi_log
-def _get_partition_path(name):
- """ Returns device path given a partition name """
- dev_path = None
- maj_min = None
-
- keys = ["NAME", "MAJ:MIN"]
- dev_list = _get_lsblk_devs(keys)
-
- for dev in dev_list:
- if dev['name'] == name:
- maj_min = dev['maj:min']
- break
+def _get_dev_node_path(maj_min):
+ """ Returns device node path given the device number 'major:min' """
+ uevent = "/sys/dev/block/%s/uevent" % maj_min
+ with open(uevent) as ueventf:
+ content = ueventf.read()
- uevent_cmd = "cat /sys/dev/block/%s/uevent" % maj_min
- uevent = subprocess.Popen(uevent_cmd, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE, shell=True)
- out, err = uevent.communicate()
- if uevent.returncode != 0:
- raise OperationFailed("Error getting partition path for %s", name)
-
- data = dict(re.findall(r'(\S+)=(".*?"|\S+)', out.replace("\n", " ")))
+ data = dict(re.findall(r'(\S+)=(".*?"|\S+)', content.replace("\n", " ")))
return "/dev/%s" % data["DEVNAME"]
@@ -63,6 +49,39 @@ def _get_lsblk_devs(keys, devs=[]):
return _parse_lsblk_output(out, keys)
+def _get_dev_major_min(name):
+ maj_min = None
+
+ keys = ["NAME", "MAJ:MIN"]
+ dev_list = _get_lsblk_devs(keys)
+
+ for dev in dev_list:
+ if dev['name'] == name:
+ maj_min = dev['maj:min']
+ break
+ else:
+ msg = "Failed to find major and minor number for %s" % name
+ raise OperationFailed(msg)
+
+ return maj_min
+
+
+def _is_dev_leaf(devNodePath):
+ try:
+ # By default, lsblk prints a device information followed by children
+ # device information
+ childrenCount = len(
+ _get_lsblk_devs(["NAME"], [devNodePath])) - 1
+ except OperationFailed as e:
+ # lsblk is known to fail on multipath devices
+ # Assume these devices contain children
+ kimchi_log.error(
+ "Error getting device info for %s: %s", devNodePath, e)
+ return False
+ else:
+ return childrenCount == 0
+
+
def _parse_lsblk_output(output, keys):
# output is on format key="value",
# where key can be NAME, TYPE, FSTYPE, SIZE, MOUNTPOINT, etc
@@ -82,32 +101,30 @@ def _parse_lsblk_output(output, keys):
def get_partitions_names():
names = []
- ignore_names = []
- keys = ["NAME", "TYPE", "FSTYPE", "MOUNTPOINT"]
+ keys = ["NAME", "TYPE", "FSTYPE", "MOUNTPOINT", "MAJ:MIN"]
# output is on format key="value",
# where key can be NAME, TYPE, FSTYPE, MOUNTPOINT
for dev in _get_lsblk_devs(keys):
# split()[0] to avoid the second part of the name, after the
# whiteline
name = dev['name'].split()[0]
- # Only list unmounted and unformated partition or disk.
- if not all([dev['type'] in ['part', 'disk'],
- dev['fstype'] == "",
- dev['mountpoint'] == ""]):
-
- # the whole disk must be ignored in it has at least one
- # mounted/formatted partition
- if dev['type'] == 'part':
- ignore_names.append(name[:-1])
+ devNodePath = _get_dev_node_path(dev['maj:min'])
+ # Only list unmounted and unformated and leaf and (partition or disk)
+ # leaf means a partition, a disk has no partition, or a disk not held
+ # by any multipath device.
+ if not (dev['type'] in ['part', 'disk'] and
+ dev['fstype'] == "" and
+ dev['mountpoint'] == "" and
+ _is_dev_leaf(devNodePath)):
continue
names.append(name)
- return list(set(names) - set(ignore_names))
+ return names
def get_partition_details(name):
- dev_path = _get_partition_path(name)
+ dev_path = _get_dev_node_path(_get_dev_major_min(name))
keys = ["TYPE", "FSTYPE", "SIZE", "MOUNTPOINT"]
try:
--
1.7.11.7
3
9
In order to show server target I did a select box in UI, but sometimes
maybe the export path number is large. So I made a filter-select
widget to make user easier to choose the export path he want.
This is the first jquery widget. And let us make all common widget to
jquery widget from now on.
Signed-off-by: zhoumeina <zhoumein(a)linux.vnet.ibm.com>
---
ui/js/widgets/filter-select.js | 110 ++++++++++++++++++++++++++++++++++++++++
1 files changed, 110 insertions(+), 0 deletions(-)
create mode 100644 ui/js/widgets/filter-select.js
diff --git a/ui/js/widgets/filter-select.js b/ui/js/widgets/filter-select.js
new file mode 100644
index 0000000..f58b208
--- /dev/null
+++ b/ui/js/widgets/filter-select.js
@@ -0,0 +1,110 @@
+/*
+ * Project Kimchi
+ *
+ * Copyright IBM, Corp. 2013
+ *
+ * Authors:
+ * zhoumeina <zhoumein(a)linux.vnet.ibm.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function($) {
+ $.widget("kimchi.filterselect", {
+ options: {
+ key: 0,
+ value: 0
+ },
+
+ _create : function() {
+ this.listControl = this.element;
+ this.element.html("");
+ var targetId = this.listControl.data('target');
+ var labelId = this.listControl.data('label');
+ this.target = $('#' + targetId);
+ this.label = $('#' + labelId);
+ this.selectDiv = this.listControl.parent().parent();
+ },
+
+ _setOption : function(key,value) {},
+
+ setData : function (options) {
+ var that = this;
+ var value = this.target.val();
+ var selectedClass = 'active';
+ var itemTag = 'li';
+
+ that.listControl.on('click', itemTag, function() {
+ that.listControl.children().removeClass(selectedClass);
+ that.listControl.addClass(selectedClass);
+ that.target.text($(this).text());
+ var oldValue = that.target.val();
+ var newValue = $(this).data('value');
+ that.target.val(newValue);
+ if(oldValue !== newValue) {
+ that.target.change();
+ }
+ });
+
+ that.selectDiv.click( function(e) {
+ that.listControl.html("");
+ var items = that._dataList(options);
+ $.each(items, function(index,item){
+ that.listControl.append(item);
+ })
+ var isOpen = that.selectDiv.hasClass('open');
+ that.selectDiv.removeClass('open');
+ if (!isOpen) {
+ that.selectDiv.addClass('open');
+ }
+ e.preventDefault();
+ e.stopPropagation();
+ });
+
+ that.target.keyup( function() {
+ that.listControl.html("");
+ var items = that._dataList(options);
+ $.each(items, function(index,item){
+ if (item.html().indexOf(that.target.val()) != -1) {
+ that.listControl.append(item);
+ }
+ });
+ if (that.listControl.html() === ''){
+ that.listControl.html(i18n['msg.storagepool.unvalid.path']);
+ }
+ });
+ },
+
+ _dataList : function (options) {
+ var item;
+ var itemTag = 'li';
+ var selectedClass = 'active';
+ var items = [];
+ var that = this;
+ $.each(options, function(index, option) {
+ item = $('<' + itemTag + '></' + itemTag + '>');
+ item.text(option.label);
+ item.data('value', option.value);
+ if(option.value === that.target.val()) {
+ item.addClass(selectedClass);
+ }
+ items.push(item);
+ });
+ return items;
+ },
+
+ destroy : function() {
+ // call the base destroy function
+ $.Widget.prototype.destroy.call(this);
+ }
+ });
+}(jQuery));
\ No newline at end of file
--
1.7.1
1
0
27 Dec '13
This patch is working adding a select box in create nfs pool,
when user don't want to input the server ip or name, he can
simply select "used the server I have used before" and choose
a server in a select box, this is more convinent.
Signed-off-by: zhoumeina <zhoumein(a)linux.vnet.ibm.com>
---
ui/css/theme-default/button.css | 11 +++----
ui/css/theme-default/form.css | 6 ++++
ui/css/theme-default/storage.css | 4 +-
ui/js/Makefile.am | 4 +-
ui/js/src/kimchi.api.js | 26 +++++++++++++++++-
ui/js/src/kimchi.storagepool_add_main.js | 43 ++++++++++++++++++++++++++++++
ui/pages/i18n.html.tmpl | 4 ++-
ui/pages/storagepool-add.html.tmpl | 39 +++++++++++++++++++++------
8 files changed, 116 insertions(+), 21 deletions(-)
diff --git a/ui/css/theme-default/button.css b/ui/css/theme-default/button.css
index c7ed3f6..f99679a 100644
--- a/ui/css/theme-default/button.css
+++ b/ui/css/theme-default/button.css
@@ -247,17 +247,16 @@
.btn-select {
display: inline-block;
position: relative;
- height: 38px;
+ height: 30px;
padding-right: 20px;
margin: 5px;
vertical-align: top;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
- border-radius: 5px;
- background: #eee;
+ background: #fff;
box-shadow: -1px -1px 1px #666, 1px 1px 1px #fff, 2px 2px 2px rgba(0, 0, 0, .15) inset;
font-size: 13px;
- line-height: 38px;
+ line-height: 30px;
text-align: left;
cursor: pointer;
}
@@ -269,8 +268,8 @@
.btn-select .arrow {
position: absolute;
width: 15px;
- height: 38px;
- line-height: 38px;
+ height: 30px;
+ line-height: 30px;
top: 0;
right: 5px;
background: url(../images/theme-default/arrow-down-black.png) no-repeat center center;
diff --git a/ui/css/theme-default/form.css b/ui/css/theme-default/form.css
index c24b277..9db4bba 100644
--- a/ui/css/theme-default/form.css
+++ b/ui/css/theme-default/form.css
@@ -45,3 +45,9 @@
line-height: 30px;
padding: 0 5px;
}
+
+.text-help {
+ font-size: 12px;
+ color: #333;
+ margin: 0 0 5px 5px;
+}
diff --git a/ui/css/theme-default/storage.css b/ui/css/theme-default/storage.css
index d81dc75..ae89f1b 100644
--- a/ui/css/theme-default/storage.css
+++ b/ui/css/theme-default/storage.css
@@ -529,7 +529,7 @@
width: 300px;
display: inline-block;
vertical-align: top;
- padding: 5px 5px 5px 20px;
+ padding: 5px 5px 5px 22px;
}
.storage-type-wrapper-controls input[type="text"] {
@@ -548,7 +548,7 @@
.storage-type-wrapper-controls > .dropdown {
margin: 5px 0 0 1px;
- width: 250px;
+ width: 150px;
}
.storage-type-wrapper-controls input[type="text"][disabled] {
diff --git a/ui/js/Makefile.am b/ui/js/Makefile.am
index 337e369..8dfd4d6 100644
--- a/ui/js/Makefile.am
+++ b/ui/js/Makefile.am
@@ -20,7 +20,7 @@
SUBDIRS = novnc
-EXTRA_DIST = src
+EXTRA_DIST = src widgets
jsdir = $(datadir)/kimchi/ui/js
@@ -32,7 +32,7 @@ dist_js_DATA = \
modernizr.custom.2.6.2.min.js \
$(NULL)
-kimchi.min.js: src/*.js
+kimchi.min.js: widgets/*.js src/*.js
cat $(sort $^) > $@
CLEANFILES = kimchi.min.js
diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js
index fbcf4a2..46072cd 100644
--- a/ui/js/src/kimchi.api.js
+++ b/ui/js/src/kimchi.api.js
@@ -681,5 +681,29 @@ var kimchi = {
success : suc,
error : err
});
+ },
+
+ getStorageServers: function(type, suc, err) {
+ var url = kimchi.url + 'storageservers?target_type=' + type;
+ kimchi.requestJSON({
+ url : url,
+ type : 'GET',
+ contentType : 'application/json',
+ dataType : 'json',
+ success : suc,
+ error : err
+ });
+ },
+
+ getStorageTargets: function(server,type, suc, err) {
+ var url = kimchi.url + 'storageservers/' + server + '/storagetargets?type=' + type;
+ kimchi.requestJSON({
+ url : url,
+ type : 'GET',
+ contentType : 'application/json',
+ dataType : 'json',
+ success : suc,
+ error : err
+ });
}
-};
+};
\ No newline at end of file
diff --git a/ui/js/src/kimchi.storagepool_add_main.js b/ui/js/src/kimchi.storagepool_add_main.js
index b31610a..fb6ec89 100644
--- a/ui/js/src/kimchi.storagepool_add_main.js
+++ b/ui/js/src/kimchi.storagepool_add_main.js
@@ -52,6 +52,22 @@ kimchi.initStorageAddPage = function() {
$('.host-partition').html(listHtml);
}
kimchi.select('storagePool-list', options);
+ kimchi.getStorageServers('netfs', function(data) {
+ var serverContent = [];
+ serverContent.push({
+ label : i18n['select_default'],
+ value : ''
+ });
+ if (data.length > 0) {
+ $.each(data, function(index, value) {
+ serverContent.push({
+ label : value,
+ value : value
+ });
+ });
+ }
+ kimchi.select('nfs-server-used', serverContent);
+ });
$('#poolType').change(function() {
if ($(this).val() === 'dir') {
$('.path-section').removeClass('tmpl-html');
@@ -67,6 +83,33 @@ kimchi.initStorageAddPage = function() {
$('.nfs-section').addClass('tmpl-html');
}
});
+ $('input[name=nfsServerType]').change(function() {
+ if ($(this).val() === 'input') {
+ $('#nfsServerInputDiv').removeClass('tmpl-html');
+ $('#nfsServerChooseDiv').addClass('tmpl-html');
+ } else {
+ $('#nfsServerInputDiv').addClass('tmpl-html');
+ $('#nfsServerChooseDiv').removeClass('tmpl-html');
+ }
+ });
+ $('#nfsServerSelect').change(function() {
+ $('#nfsserverId').val($(this).val());
+ });
+ $('#nfsserverId').blur(function() {
+ kimchi.getStorageTargets($(this).val(),'netfs', function(data) {
+ var serverContent = [];
+ if (data.length > 0) {
+ $.each(data, function(index, value) {
+ serverContent.push({
+ label : value,
+ value : value
+ });
+ });
+ }
+ $('#nfs-server-target').filterselect();
+ $('#nfs-server-target').filterselect("setData",options);
+ });
+ });
});
};
diff --git a/ui/pages/i18n.html.tmpl b/ui/pages/i18n.html.tmpl
index c1fc3d1..222459e 100644
--- a/ui/pages/i18n.html.tmpl
+++ b/ui/pages/i18n.html.tmpl
@@ -121,7 +121,9 @@ var i18n = {
'action_create': "$_("Create")",
'msg_warning': "$_("Warning")",
'msg.logicalpool.confirm.delete': "$_("It will format your disk and you will loose any data in"
- " there, are you sure to continue? ")"
+ " there, are you sure to continue? ")",
+ 'select_default': "$_("Please choose")",
+ 'msg.storagepool.unvalid.path': "$_("This is not a valid path")"
};
</script>
</body>
diff --git a/ui/pages/storagepool-add.html.tmpl b/ui/pages/storagepool-add.html.tmpl
index 5a2dd45..2b522ff 100644
--- a/ui/pages/storagepool-add.html.tmpl
+++ b/ui/pages/storagepool-add.html.tmpl
@@ -27,7 +27,7 @@
<!DOCTYPE html>
<html>
<body>
- <div class="window" style="width: 600px; height: 600px;">
+ <div class="window" style="width: 800px; height: 600px;">
<header>
<h1 class="title">$_("Define a New Storage Pool")</h1>
<div class="close">X</div>
@@ -47,7 +47,7 @@
<section class="form-section">
<h2>2. $_("Storage Pool Type")</h2>
<div class="storage-type-wrapper-controls">
- <div class="btn dropdown popable">
+ <div class="btn-select dropdown popable">
<input id="poolType" name="type" type="hidden" value="dir"/>
<span class="text" id="pool-type-label"></span><span class="arrow"></span>
<div class="popover" style="width: 100%">
@@ -74,22 +74,43 @@
<section class="form-section">
<h2>3. $_("NFS server IP")</h2>
<div class="field">
+ <input type="radio" id="nfsServerInput" value="input" name="nfsServerType" checked="true">
+ <label>$_("I want to input the server myself.")</label>
+ <input type="radio" id="nfsServerChoose" value="choose" name="nfsServerType">
+ <label>$_("I want to choose a server I used before.")</label>
+ </div>
+ <div id="nfsServerInputDiv" class="field">
<p class="text-help">
$_("NFS server IP or hostname. It should not be empty.")</p>
<input id="nfsserverId" type="text" class="text"
style="width: 300px">
</div>
+ <div id="nfsServerChooseDiv" class="field tmpl-html" style="overflow:visible">
+ <p class="text-help">
+ $_("Please choose the nfs server you want to create storage pool.")</p>
+ <div class="btn-select dropdown" style="width: 285px">
+ <input id="nfsServerSelect" type="hidden"/>
+ <span class="text" id="nfs-server-label"></span><span class="arrow"></span>
+ <div class="popover" style="width: 100%">
+ <ul class="select-list" id="nfs-server-used" data-target="nfsServerSelect" data-label="nfs-server-label">
+ </ul>
+ </div>
+ </div>
+ </div>
</section>
<section class="form-section">
<h2>4. $_("NFS Path")</h2>
- <div class="field">
+ <div class="field" style="overflow:visible">
<p class="text-help">$_("The nfs exported path on nfs server")</p>
- <input id="nfspathId" type="text" class="text"
- style="width: 300px">
- <input type="hidden" id="localpathId" class="text"
- value="none">
- </div>
- <div class="clear"></div>
+ <div class="btn-select dropdown popable" style="width: 285px">
+ <input id="nfspathId" class="text" style="width:293px;"/>
+ <span class="text" id="nfs-target-label"></span><span class="arrow"></span>
+ <div class="popover" style="width: 100%">
+ <ul class="select-list" id="nfs-server-target" data-target="nfspathId" data-label="nfs-target-label">
+ </ul>
+ </div>
+ </div>
+ </div>
</section>
</div>
<div class="logical-section tmpl-html">
--
1.7.1
1
0
[PATCH V1 0/2] Add the nfs pool server and target select UI(new jquery widget)
by zhoumeina 27 Dec '13
by zhoumeina 27 Dec '13
27 Dec '13
This patch is working adding a select box in create nfs pool,
when user don't want to input the server ip or name, he can
simply select "used the server I have used before" and choose
a server in a select box, this is more convinent.
zhoumeina (2):
Add nfs server and target UI in create storage pool
Add a new jquery filter-select widget
ui/css/theme-default/button.css | 11 ++--
ui/css/theme-default/form.css | 6 ++
ui/css/theme-default/storage.css | 4 +-
ui/js/Makefile.am | 4 +-
ui/js/src/kimchi.api.js | 26 +++++++-
ui/js/src/kimchi.storagepool_add_main.js | 43 ++++++++++++
ui/js/widgets/filter-select.js | 110 ++++++++++++++++++++++++++++++
ui/pages/i18n.html.tmpl | 4 +-
ui/pages/storagepool-add.html.tmpl | 39 ++++++++---
9 files changed, 226 insertions(+), 21 deletions(-)
create mode 100644 ui/js/widgets/filter-select.js
1
0
From: Aline Manera <alinefm(a)br.ibm.com>
The current implemenation put all resources implementation into controller.py
file. That way this file tends to increase to infinity as more resources are
added to Kimchi.
This patch splits controller module (controller.py) into small modules.
So each resource will have its own controller implementation under /control
It will make maintenance easier and anyone can easily identify where any resource
is implemented.
This patch set does not change any logic - just move classes from controller
to a new module.
Aline Manera (16):
Move generate_action_handler() function to Resource() class
Move common functions for Resource and Collection to control/utils.py
Move login() and logout() functions from controller.py to root.py
Move basic controller resources to control/base.py
Move all resources related to vms to control/vms.py
Move all resources related to templates to control/templates.py
Move all resources related to debug reports to
control/debugreports.py
Move all resources related to storage pools to
control/storagepools.py
Move all resources related to storage volume to
control/storagevolumes.py
Move all resources related to interfaces to control/interfaces.py
Move all resources related to networks to control/networks.py
Move all resources related to config to control/config.py
Move all resources related to host to control/host.py
Move all resources related to plugins to control/plugins.py
Move all resources related to tasks to control/tasks.py
Use new control modules in root.py
Makefile.am | 13 +
src/kimchi/control/__init__.py | 21 +
src/kimchi/control/base.py | 290 +++++++++++++
src/kimchi/control/config.py | 65 +++
src/kimchi/control/debugreports.py | 52 +++
src/kimchi/control/host.py | 61 +++
src/kimchi/control/interfaces.py | 44 ++
src/kimchi/control/networks.py | 48 +++
src/kimchi/control/plugins.py | 44 ++
src/kimchi/control/storagepools.py | 125 ++++++
src/kimchi/control/storagevolumes.py | 79 ++++
src/kimchi/control/tasks.py | 41 ++
src/kimchi/control/templates.py | 51 +++
src/kimchi/control/utils.py | 103 +++++
src/kimchi/control/vms.py | 64 +++
src/kimchi/controller.py | 755 ----------------------------------
src/kimchi/root.py | 61 ++-
17 files changed, 1147 insertions(+), 770 deletions(-)
create mode 100644 src/kimchi/control/__init__.py
create mode 100644 src/kimchi/control/base.py
create mode 100644 src/kimchi/control/config.py
create mode 100644 src/kimchi/control/debugreports.py
create mode 100644 src/kimchi/control/host.py
create mode 100644 src/kimchi/control/interfaces.py
create mode 100644 src/kimchi/control/networks.py
create mode 100644 src/kimchi/control/plugins.py
create mode 100644 src/kimchi/control/storagepools.py
create mode 100644 src/kimchi/control/storagevolumes.py
create mode 100644 src/kimchi/control/tasks.py
create mode 100644 src/kimchi/control/templates.py
create mode 100644 src/kimchi/control/utils.py
create mode 100644 src/kimchi/control/vms.py
delete mode 100644 src/kimchi/controller.py
--
1.7.10.4
6
73
[PATCH V3 0/3] check and set search permission for a directory
by shaohef@linux.vnet.ibm.com 26 Dec '13
by shaohef@linux.vnet.ibm.com 26 Dec '13
26 Dec '13
From: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>
V2 -> V3
read the quem process pid from pid file instead of from iterating the
process list.
V1 -> V2
resend: qemu user tests
rebase: add a method to fix search permissions
ShaoHe Feng (3):
move RollbackContext from tests/utils to src/kimchi/utils
qemu user tests: probe the username of qemu process started by libvirt
add a method to fix search permissions
src/kimchi/kvmusertests.py | 66 ++++++++++++++++++
src/kimchi/utils.py | 162 ++++++++++++++++++++++++++++++++++++++++++++-
tests/utils.py | 47 +------------
3 files changed, 229 insertions(+), 46 deletions(-)
create mode 100644 src/kimchi/kvmusertests.py
--
1.8.4.2
2
6
26 Dec '13
As we know that as the development of kimchi UI, more and more new
feature merged. And we need more UI widgets to support our feature. Just
like charts, tooltips, filter select
and so on. and also will be more complex.
And our way to solve this problem is trying to make a widget by css, do
we want to continue this way?
or trying to find some common jquery third-part widget?
Appreciate anycomments.
<http://fanyi.baidu.com/###>
Appreciate anycomments
2
1
Hi, All
I reviewed current kimchi UI content and found generic UI components
below that should be jquery plugins or widgets
Button/Menu/Dialog/AlertBox/Accordion/Chart/DataGrid...
These widgets fall into 2 categories:
1. basic form control component like Button/Menu and container widgets
like Dialog/Accordion
2. functional widget like Chart/DataGrid
_For the 1st category_, jquery ui contains most of them, refer to link below
http://jqueryui.com/
If any widget we need that does not exist in jquery-ui, we should create
a jquery widget which is common enough for reuse.
_For the 2nd category_, as jquery ui does not contain any of them, we
need to discuss to use jquery plugin from 3rd party.
jquery has a widget registry, we can search the needed plugin/widget
there. http://plugins.jquery.com/
for chart: http://plugins.jquery.com/?s=chart
for datagrid: http://plugins.jquery.com/?s=datagrid
As they are from 3rd party, not created by jquery team, we need to
evaluate its features, quality, maintenance, license.
Team, this is definitely strategic which impact long-term development,
please feel free to popup your idea.
1
0
Add the nfs server messages in po files
Signed-off-by: zhoumeina <zhoumein(a)linux.vnet.ibm.com>
---
po/en_US.po | 16 ++++++++++++++--
po/kimchi.pot | 15 +++++++++++++--
po/pt_BR.po | 18 +++++++++++++++---
po/zh_CN.po | 16 ++++++++++++++--
4 files changed, 56 insertions(+), 9 deletions(-)
diff --git a/po/en_US.po b/po/en_US.po
index f2810db..79b095b 100644
--- a/po/en_US.po
+++ b/po/en_US.po
@@ -6,14 +6,14 @@ msgid ""
msgstr ""
"Project-Id-Version: kimchi 0.1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-12-18 16:04-0200\n"
+"POT-Creation-Date: 2013-12-24 10:17+0800\n"
"PO-Revision-Date: 2013-07-11 17:32-0400\n"
"Last-Translator: Crístian Viana <vianac(a)linux.vnet.ibm.com>\n"
"Language-Team: English\n"
-"Language: en_US\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"Language: en_US\n"
"Generated-By: pygettext.py 1.5\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -407,6 +407,9 @@ msgstr ""
"It will format your disk and you will loose any data in there, are you sure "
"to continue? "
+msgid "Please choose"
+msgstr "Please choose"
+
msgid "Log out"
msgstr "Log out"
@@ -459,9 +462,18 @@ msgstr ""
msgid "NFS server IP"
msgstr "NFS server IP"
+msgid "I want to input the server myself."
+msgstr "I want to input the server myself."
+
+msgid "I want to choose a server I used before."
+msgstr "I want to choose a server I used before."
+
msgid "NFS server IP or hostname. It should not be empty."
msgstr "NFS server IP or hostname. It should not be empty."
+msgid "Please choose the nfs server you want to create storage pool."
+msgstr "Please choose the nfs server you want to create storage pool."
+
msgid "NFS Path"
msgstr "NFS Path"
diff --git a/po/kimchi.pot b/po/kimchi.pot
index 762f4e4..78fd77b 100644
--- a/po/kimchi.pot
+++ b/po/kimchi.pot
@@ -8,11 +8,10 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-12-18 16:04-0200\n"
+"POT-Creation-Date: 2013-12-24 10:17+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL(a)li.org>\n"
-"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -392,6 +391,9 @@ msgid ""
"to continue? "
msgstr ""
+msgid "Please choose"
+msgstr ""
+
msgid "Log out"
msgstr ""
@@ -439,9 +441,18 @@ msgstr ""
msgid "NFS server IP"
msgstr ""
+msgid "I want to input the server myself."
+msgstr ""
+
+msgid "I want to choose a server I used before."
+msgstr ""
+
msgid "NFS server IP or hostname. It should not be empty."
msgstr ""
+msgid "Please choose the nfs server you want to create storage pool."
+msgstr ""
+
msgid "NFS Path"
msgstr ""
diff --git a/po/pt_BR.po b/po/pt_BR.po
index 7d59503..f790154 100644
--- a/po/pt_BR.po
+++ b/po/pt_BR.po
@@ -20,14 +20,14 @@ msgid ""
msgstr ""
"Project-Id-Version: kimchi 1.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-12-18 16:04-0200\n"
+"POT-Creation-Date: 2013-12-24 10:17+0800\n"
"PO-Revision-Date: 2013-06-27 10:48+0000\n"
"Last-Translator: Alexandre Hirata <hirata(a)linux.vnet.ibm.com>\n"
"Language-Team: Aline Manera <alinefm(a)br.ibm.com>\n"
-"Language: pt_BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"Language: pt_BR\n"
"Generated-By: pygettext.py 1.5\n"
"X-Poedit-Country: Brazil\n"
"X-Poedit-Language: Portuguese\n"
@@ -424,6 +424,9 @@ msgstr ""
"Isso formatará seu disco e você perderá toda informação, você tem certeza "
"que quer continuar?"
+msgid "Please choose"
+msgstr ""
+
msgid "Log out"
msgstr "Sair"
@@ -475,8 +478,17 @@ msgstr ""
msgid "NFS server IP"
msgstr "Endereço IP do servidor NFS"
+msgid "I want to input the server myself."
+msgstr ""
+
+msgid "I want to choose a server I used before."
+msgstr ""
+
msgid "NFS server IP or hostname. It should not be empty."
-msgstr "Endereço IP ou nome do servidor NFS. Não deve ser vazio. "
+msgstr ""
+
+msgid "Please choose the nfs server you want to create storage pool."
+msgstr ""
msgid "NFS Path"
msgstr "Caminho do NFS"
diff --git a/po/zh_CN.po b/po/zh_CN.po
index de759ef..890bfb6 100644
--- a/po/zh_CN.po
+++ b/po/zh_CN.po
@@ -20,14 +20,14 @@ msgid ""
msgstr ""
"Project-Id-Version: kimchi 0.1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-12-18 16:04-0200\n"
+"POT-Creation-Date: 2013-12-24 10:17+0800\n"
"PO-Revision-Date: 2013-06-27 10:48+0000\n"
"Last-Translator: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>\n"
"Language-Team: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>\n"
-"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"Language: zh_CN\n"
"Generated-By: pygettext.py 1.5\n"
"X-Poedit-Country: CHINA\n"
"X-Poedit-Language: Chinese\n"
@@ -408,6 +408,9 @@ msgid ""
"to continue? "
msgstr "你的磁盘将会格式化,磁盘上的数据会丢失,你确定要继续吗?"
+msgid "Please choose"
+msgstr "请选择"
+
msgid "Log out"
msgstr "登出"
@@ -457,9 +460,18 @@ msgstr "存储池的路径.每个存储池的路径是唯一的。"
msgid "NFS server IP"
msgstr "NFS服务器IP"
+msgid "I want to input the server myself."
+msgstr "我想自己输入服务器"
+
+msgid "I want to choose a server I used before."
+msgstr "我想选择一个我曾经用过的服务器"
+
msgid "NFS server IP or hostname. It should not be empty."
msgstr "NFS服务器IP或者主机名, 不能为空。"
+msgid "Please choose the nfs server you want to create storage pool."
+msgstr "请选择想要创建存储池的服务器"
+
msgid "NFS Path"
msgstr "NFS 路径"
--
1.7.1
1
0
This patch is working adding a select box in create nfs pool,
when user don't want to input the server ip or name, he can
simply select "used the server I have used before" and choose
a server in a select box, this is more convinent.
zhoumeina (2):
Add nfs server and target UI in create storage pool
Add the translate messages
po/en_US.po | 16 ++++++++++-
po/kimchi.pot | 15 +++++++++-
po/pt_BR.po | 18 ++++++++++--
po/zh_CN.po | 16 ++++++++++-
ui/css/theme-default/button.css | 11 +++----
ui/css/theme-default/form.css | 6 ++++
ui/css/theme-default/storage.css | 4 +-
ui/js/src/kimchi.api.js | 26 +++++++++++++++++-
ui/js/src/kimchi.storagepool_add_main.js | 42 ++++++++++++++++++++++++++++++
ui/pages/i18n.html.tmpl | 3 +-
ui/pages/storagepool-add.html.tmpl | 39 +++++++++++++++++++++------
11 files changed, 168 insertions(+), 28 deletions(-)
1
0
26 Dec '13
From: Eli Qiao <taget(a)linux.vnet.ibm.com>
V3 - V2 changes:
1.Rename kimchid.xml to firewalld.xml (Mark)
2.Remove firewalld from serivce require (Mark)
3.Fix typo
V2 - V1 changes:
1.Add firewalld sevice configure file kimchid.xml to help open iptables port (Mark)
2.Add Ubuntu iptables rule (Royce)
Add iptable rules to open 8000 and 8001 port.
1. For fedora, ubuntu and RHEL7, add a firewalld.xml to use firewalld daemon to open port
8000 and 8001.
2. For suse and RHEL6.x, add iptables static rules to open port 8000 and 8001.
Signed-off-by: Eli Qiao <taget(a)linux.vnet.ibm.com>
---
contrib/DEBIAN/control.in | 3 ++-
contrib/DEBIAN/postinst | 2 ++
contrib/DEBIAN/postrm | 2 ++
contrib/kimchi.spec.fedora.in | 19 +++++++++++++++++++
contrib/kimchi.spec.suse.in | 10 ++++++++--
src/Makefile.am | 1 +
src/firewalld.xml | 7 +++++++
7 files changed, 41 insertions(+), 3 deletions(-)
create mode 100644 src/firewalld.xml
diff --git a/contrib/DEBIAN/control.in b/contrib/DEBIAN/control.in
index 380584c..c0ea1f1 100644
--- a/contrib/DEBIAN/control.in
+++ b/contrib/DEBIAN/control.in
@@ -17,7 +17,8 @@ Depends: python-cherrypy3 (>= 3.2.0),
python-psutil (>= 0.6.0),
python-ethtool,
sosreport,
- python-ipaddr
+ python-ipaddr,
+ firewalld
Build-Depends:
Maintainer: Aline Manera <alinefm(a)br.ibm.com>
Description: Kimchi web server
diff --git a/contrib/DEBIAN/postinst b/contrib/DEBIAN/postinst
index c1fc22e..b27205c 100755
--- a/contrib/DEBIAN/postinst
+++ b/contrib/DEBIAN/postinst
@@ -19,3 +19,5 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
service kimchid start
+/usr/bin/firewall-cmd --reload
+/usr/bin/firewall-cmd --add-service kimchid
diff --git a/contrib/DEBIAN/postrm b/contrib/DEBIAN/postrm
index ef90b49..3c70584 100755
--- a/contrib/DEBIAN/postrm
+++ b/contrib/DEBIAN/postrm
@@ -26,3 +26,5 @@ case "$1" in
rm -rf /var/log/kimchi /var/run/kimchi.pid /usr/share/kimchi/
;;
esac
+
+/usr/bin/firewall-cmd --remove-service kimchid
diff --git a/contrib/kimchi.spec.fedora.in b/contrib/kimchi.spec.fedora.in
index 14ec359..57baead 100644
--- a/contrib/kimchi.spec.fedora.in
+++ b/contrib/kimchi.spec.fedora.in
@@ -34,6 +34,7 @@ BuildRequires: python-unittest2
%if 0%{?with_systemd}
Requires: systemd
+Requires: firewalld
Requires(post): systemd
Requires(preun): systemd
Requires(postun): systemd
@@ -63,6 +64,7 @@ make DESTDIR=%{buildroot} install
%if 0%{?with_systemd}
# Install the systemd scripts
install -Dm 0644 contrib/kimchid.service.fedora %{buildroot}%{_unitdir}/kimchid.service
+install -Dm 0640 src/firewalld.xml %{buildroot}%{_prefix}/lib/firewalld/services/kimchid.xml
%endif
%if 0%{?rhel} == 6
@@ -83,16 +85,32 @@ fi
%if 0%{?rhel} == 6
start kimchid
+# Add defult iptable rules to open 8000 and 8001 port
+iptables -I INPUT -p tcp --dport 8000 -j ACCEPT
+iptables -I INPUT -p tcp --dport 8001 -j ACCEPT
+service iptables save
%else
service kimchid start
+# Add firewalld rules to open 8000 and 8001 port
+/usr/bin/firewall-cmd --reload
+/usr/bin/firewall-cmd --add-service kimchid
%endif
%preun
+%if 0%{?rhel} == 6
+iptables -D INPUT -p tcp --dport 8000 -j ACCEPT
+iptables -D INPUT -p tcp --dport 8001 -j ACCEPT
+service iptables save
+%else
+/usr/bin/firewall-cmd --remove-service kimchid
+%endif
+
if [ $1 -eq 0 ] ; then
# Package removal, not upgrade
/bin/systemctl --no-reload disable kimchid.service > /dev/null 2>&1 || :
/bin/systemctl stop kimchid.service > /dev/null 2>&1 || :
fi
+
exit 0
@@ -153,6 +171,7 @@ rm -rf $RPM_BUILD_ROOT
%if 0%{?with_systemd}
%{_unitdir}/kimchid.service
+%{_prefix}/lib/firewalld/services/kimchid.xml
%endif
%if 0%{?rhel} == 6
/etc/init/kimchid.conf
diff --git a/contrib/kimchi.spec.suse.in b/contrib/kimchi.spec.suse.in
index 9051284..dde9dae 100644
--- a/contrib/kimchi.spec.suse.in
+++ b/contrib/kimchi.spec.suse.in
@@ -46,10 +46,16 @@ install -Dm 0755 contrib/kimchid.sysvinit %{buildroot}%{_initrddir}/kimchid
%post
service kimchid start
chkconfig kimchid on
-
+# Add iptables rules to open 8000 and 8001 port
+iptables -I INPUT -p tcp --dport 8000 -j ACCEPT
+iptables -I INPUT -p tcp --dport 8001 -j ACCEPT
+service iptables save
%preun
service kimchid stop
-
+# Remove iptables rules to open 8000 and 8001 port
+iptables -D INPUT -p tcp --dport 8000 -j ACCEPT
+iptables -D INPUT -p tcp --dport 8001 -j ACCEPT
+service iptables save
%clean
rm -rf $RPM_BUILD_ROOT
diff --git a/src/Makefile.am b/src/Makefile.am
index 7d29e28..7514870 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -24,6 +24,7 @@ SUBDIRS = kimchi distros.d
EXTRA_DIST = kimchid.in \
kimchi.conf.in \
+ firewalld.xml \
$(NULL)
bin_SCRIPTS = kimchid
diff --git a/src/firewalld.xml b/src/firewalld.xml
new file mode 100644
index 0000000..dee4599
--- /dev/null
+++ b/src/firewalld.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<service>
+ <short>kimchid</short>
+ <description>Kimchid is a daemon service for kimchi whichi is a HTML5 based management tool for KVM. It is designed to make it as easy as possible to get started with KVM and create your first guest.</description>
+ <port protocol="tcp" port="8000"/>
+ <port protocol="tcp" port="8001"/>
+</service>
--
1.7.1
4
3
Hi,
As we moved to new mail list, resend the patch to the new list. Sorry to
disturb you!
2
5
v3-v2:
1. Rebase to upstream
2. Improve/simplify some code according to reviewers' advice.
3. Reimplement parameters validation using jsonschema.
4. Remove unnecessary test code in model.py.
5. Split patch to several smaller commits according to logic.
v2-v1: Rebased to upstream and resend
v1: Add spice backend support for kimchi
apporc (5):
Add spice backend support for kimchi
Update mockmodel for spice support
Validate graphics parameters input by users
Update test case for graphics support
Add graphics parameters description in API.md
docs/API.md | 43 ++++++++++++++++++++++-
src/kimchi/API.json | 33 ++++++++++++++++++
src/kimchi/controller.py | 14 +++++---
src/kimchi/mockmodel.py | 49 ++++++++++++++++++---------
src/kimchi/model.py | 41 +++++++++++++---------
src/kimchi/osinfo.py | 5 +--
src/kimchi/vmtemplate.py | 32 ++++++++++++++++--
tests/test_mockmodel.py | 2 ++
tests/test_model.py | 27 ++++++++++++++-
tests/test_rest.py | 88 +++++++++++++++++++++++++++++++++++++++++++++---
tests/test_vmtemplate.py | 42 ++++++++++++++++++++---
11 files changed, 325 insertions(+), 51 deletions(-)
--
1.8.1.2
3
8
Identify images of Gentoo Linux inside storage pools and local files,
and add a remote template for the latest version of Gentoo.
According to http://www.gentoo.org/main/en/name-logo.xml, some sentences
were added to Kimchi's terms of services regarding Gentoo trademark and
media ownership.
Signed-off-by: Crístian Viana <vianac(a)linux.vnet.ibm.com>
---
COPYING | 5 +++++
contrib/kimchi.spec.fedora.in | 1 +
contrib/kimchi.spec.suse.in | 1 +
src/distros.d/gentoo.json | 8 ++++++++
src/kimchi/isoinfo.py | 1 +
src/kimchi/osinfo.py | 9 +++++++++
ui/css/theme-default/template_add.css | 4 ++++
ui/images/icon-gentoo.png | Bin 0 -> 15307 bytes
8 files changed, 29 insertions(+)
create mode 100644 src/distros.d/gentoo.json
create mode 100644 ui/images/icon-gentoo.png
diff --git a/COPYING b/COPYING
index 3e59c4d..6129b48 100644
--- a/COPYING
+++ b/COPYING
@@ -4,3 +4,8 @@ the Apache License version 2.0. The rest of this distribution is
governed by the GNU Lesser General Public License version 2.1.
See COPYING.LGPL and COPYING.ASL2.
+
+The name "Gentoo" and the "g" logo are trademarks of Gentoo Foundation, Inc.
+The content, project, site, product or any other type of item with which the
+"Gentoo" name is associated is not part of the Gentoo project and is not
+directed or managed by Gentoo Foundation, Inc.
diff --git a/contrib/kimchi.spec.fedora.in b/contrib/kimchi.spec.fedora.in
index 14ec359..577516c 100644
--- a/contrib/kimchi.spec.fedora.in
+++ b/contrib/kimchi.spec.fedora.in
@@ -150,6 +150,7 @@ rm -rf $RPM_BUILD_ROOT
%{_sysconfdir}/kimchi/distros.d/fedora.json
%{_sysconfdir}/kimchi/distros.d/opensuse.json
%{_sysconfdir}/kimchi/distros.d/ubuntu.json
+%{_sysconfdir}/kimchi/distros.d/gentoo.json
%if 0%{?with_systemd}
%{_unitdir}/kimchid.service
diff --git a/contrib/kimchi.spec.suse.in b/contrib/kimchi.spec.suse.in
index 9051284..12d02ec 100644
--- a/contrib/kimchi.spec.suse.in
+++ b/contrib/kimchi.spec.suse.in
@@ -97,6 +97,7 @@ rm -rf $RPM_BUILD_ROOT
%{_sysconfdir}/kimchi/distros.d/fedora.json
%{_sysconfdir}/kimchi/distros.d/opensuse.json
%{_sysconfdir}/kimchi/distros.d/ubuntu.json
+%{_sysconfdir}/kimchi/distros.d/gentoo.json
%{_initrddir}/kimchid
%changelog
diff --git a/src/distros.d/gentoo.json b/src/distros.d/gentoo.json
new file mode 100644
index 0000000..dffeadf
--- /dev/null
+++ b/src/distros.d/gentoo.json
@@ -0,0 +1,8 @@
+[
+ {
+ "name": "gentoo-20131010",
+ "os_distro": "gentoo",
+ "os_version": "20131010",
+ "path": "http://distfiles.gentoo.org/releases/amd64/autobuilds/current-iso/install-a…"
+ }
+]
diff --git a/src/kimchi/isoinfo.py b/src/kimchi/isoinfo.py
index 219fda0..f76fd90 100644
--- a/src/kimchi/isoinfo.py
+++ b/src/kimchi/isoinfo.py
@@ -113,6 +113,7 @@ iso_dir = [
('ubuntu', lambda m: m.group(2), '[Uu]buntu(-Server)? (\d+\.\d+)'),
('fedora', lambda m: m.group(1), 'Fedora[ -](\d+)'),
('fedora', lambda m: m.group(1), 'Fedora.*-(\d+)-'),
+ ('gentoo', lambda m: m.group(1), 'Gentoo Linux \w+ (\d+)'),
]
diff --git a/src/kimchi/osinfo.py b/src/kimchi/osinfo.py
index 20a93a6..da70b30 100644
--- a/src/kimchi/osinfo.py
+++ b/src/kimchi/osinfo.py
@@ -140,6 +140,15 @@ osinfo = [
'disk_bus': 'ide', 'nic_model': 'e1000',
'cdrom_bus': 'ide', 'cdrom_index': 2,
}),
+ ('gentoo', {
+ 'version': lambda d,v: bool(d == 'gentoo'),
+ 'icon': 'images/icon-gentoo.png',
+ 'cpus': 1, 'cpu_cores': 1, 'cpu_threads': 1,
+ 'memory': 1024,
+ 'disks': [{'index': 0, 'size': 10}],
+ 'disk_bus': 'virtio', 'nic_model': 'virtio',
+ 'cdrom_bus': 'ide', 'cdrom_index': 2,
+ }),
('unknown', {
'version': lambda d,v: True,
'icon': 'images/icon-vm.png',
diff --git a/ui/css/theme-default/template_add.css b/ui/css/theme-default/template_add.css
index 38fa375..2167ff1 100644
--- a/ui/css/theme-default/template_add.css
+++ b/ui/css/theme-default/template_add.css
@@ -194,6 +194,10 @@
background-image: url(../../images/icon-ubuntu.png);
}
+.iso-icon.gentoo {
+ background-image: url(../../images/icon-gentoo.png);
+}
+
.list-iso {
overflow: hidden;
margin: 5px;
diff --git a/ui/images/icon-gentoo.png b/ui/images/icon-gentoo.png
new file mode 100644
index 0000000000000000000000000000000000000000..50d928fbfa30560fe873cf3de438a8f0c10a8f96
GIT binary patch
literal 15307
zcmV;+J2b?JP)<h;3K|Lk000e1NJLTq004jh004jp1^@s6!#-il00006VoOIv0RI60
z0RN!9r;`8x010qNS#tmY4#NNd4#NS*Z>VGd000McNliru-3uHIHU<90ip~H4AOJ~3
zK~#9!?Y(!HWLJ6T|2gNz%ALEXyQgPHqfrJ4fh3R+AhPZC+K<Do?Z^IMOmYC5j7{*8
z!5F+?ytYXO2X?VY0)d1C5?}*X0%eUfGa60qo}P}CZanAw{;2An>guYV9!V1bo#(lI
zL*1&by6^YA`F)Qt#t5DmKexmFss9BA5Fmsw>$?33z^@qMr|^LoAq2)4zJKHPV!fvG
z8io+^QmuKC)@(Ie3;-p8Fh+=~6ymUuJS?TSUnzZyZCgitUsRs=;;ogRcToJ)0ATh=
znfvZK<iv@(FsP?*ier8=iX}=}Xd^K+HV|M0AVIA7r3gR@gC)VX44!AOEIy-2KGfe=
zc;w2fHl$!Z0f5uy%Qe~h_1E0_`dUrDGYqXvYHV9+8`C7Zv}~kwK!^tM5eRW~FN8!|
z&HF3~fQlpHC_w8JDZsWgj%5<xH#3%_|M-`Gb=x(@7+49RCjf9V3IFYH-tnUHg85hw
z*;|v;v6ZngSYRZ=vXRopvTTH~5W+%Q65qFQ9f4&D3<9kUNur4&O&lwX(M_cRgk|A4
zHqy38qlhT1Auu?W!L?P8$#9>W(XV*%FK)g2XIVs?P5>}M2)_BHTmQ0Bldp^uXDrbU
z#yB8sq~%~)F4A%-7F-6097;tC#}QbzKnl>hZ8Kn+$4Wtx8sgXx)KeB06RNcsgFy<g
zYzJvc(j+2{L!{8Sj=^{Jo=jH!!!P{urVsrr>I<g@0E7_c+ArSn)_P#QEr^_~G9KU{
zEC<W-a2%iEVV{u=4!u1R*M(GpRx63E^-+Rr4a%%MU)qdmY$}CnEu~UPD3?RDHb_fi
zSr*1<(j)|}aBNM+H<6#=V<QvJJD+;dhTvxf2&WVP4Z{D|Z-$}xw_4!33Lk`vB|U7{
zr&P)@G2yXkv&69>O*<1)H3_d;uj`}Nbr%pUAJf<`EW}hQ3H5pm7%baHU`W#ljKY?R
zY}SzR#e4floWH*8(vfnKXkpvp2>`5j#8HChyXLyD{BT<sitDO1=gd_57~x~t9<G;T
zpg+gi=Xh+~2uX63Sgk}`p5+>hI?4f-LD0NrRD%<A1VJ13(7N#*!%#Cj7ZTKCwAM(=
zLI_Z41V-anid@FvXT@uWHu#@->Qe_orL>SzJOO}Xji20pk38^5<=yqV{njw?(PWU8
zhv(-hl?puNVwVjYFc`!Vt8OR!(yVV40j;yW{WWclo~tfgpj~&Mxu2d62!aHqQiPNU
z2`Y(^LStK+Le7xM$zR*NIe+c8vkU480IV^-`lXu+Ci3@H1FxiX1|u`LUY0_k#05{v
zuyre@i?>g#B3A3HS1Yfpc7nIu+YW?oAZX<}#=!J+NDxFQt+0duDNt#I6b8@M6be)d
zz0O6KUq0~2Pm@SE;Q-KDV_BB@`qkfkU9IN*O%(fRkwMxSa`_%MY$$Ts(}l6u<CJa!
zVFkhUN)RlGge4HHxURc!4%0=UY*j3q|95saAPgI#LCA(j5}jaK2G3EHO5)xZ|I+A_
ze_E8p6AFOYBMS@+^_pw`-}in{t9e(%I*S$=JU35I?*LD`#9?>@bKD<n5bcCt0Rq)R
zaMeL1w7;*Lve+hmOjBWuW6kVrog`Hltq{^e>jWuGLs9f~+|z5l<GC*w`B1f*id@e6
z`2)bdM-GX5?zyiRSN*$dfj^O&EXWMGd@rM;Jv{9aX?*`NIu3LL2<;$f1wyMBP`ZO?
z=%7e;wHTUar7?};AV`^?570(8olIa%iY*MTqbTIrIWXk@;$_bm*mb&cgcBAGy!Xf3
z&y7O=mRgX_DUm^1If^|4oOM>7^DlT5v%V90CB|xcewAu9FOQxJv5I?ibnq%b>Z32W
zZkXn=ZiE<&24NtRvGLpji{*%VJwh9e5Dv;H!dQ|n>TOXJ@BGg7Q|}W(y#2KFx;~y?
z${2$X!hGXP-?=0V+}rA57A3OSPJzC@A<lo2$L7tC5AnO{2CZ8=S6Jo*s<ZCUNm=a1
z1(xokDJG&=vrrCDu|gTu2vHk_B@M2vDHhpZ>~*es<}>>rKAlX)#|;3+7^IZuYgd2!
z;y84FT#s@nlfm&y3=R(Rl#5-4hED+T+eE?&J)n)~t=CL*uT&S)vCY@8%>MOiAS_u5
z7+5Gr1VN0^8bd=gSORRR$YnK!qV=-py>RHCYPD2kv)0cJ0E7_c>sNp0$x-0k9mF|O
zGPrJ$p^*_Txzwho=fo1flU|@y!`-Xe6-3otKWNnlRydWGh)<g%$sAP<k6H_c`T06Z
zYqU}rG*TF(G<a4@Z?C*#cr0`Ilb>8je`Wx1-B)kAAgnp})WR%^EVf%<c*6!RyVRyw
zIyuBYYI`D<h~5f-t{`Z$9+pKxo29Vq7w8ahY6Za3d8HB&h6&nev{ncVmNeLwqL4GD
zXTW*NGoRUa,}e5@Z(DOb7fYv0`%2i86H$VZdKvU3a%k8sH)r-1m4Uu7%_mets#
z1(pQaVH54wMDw~h`g+svgA|S9<xRGow{Ac_@6l7rB89}VY(SD~L85FHE0&l(l-%{*
zn`S;IgfK~>fBHCpQX1Q~%-6nf^FpPTDJfF`nPF&ToJ%itO<(^h=5Gd(rfoC6Im?%>
z>ki^!`C11TvT2JQn6?OGyKX>Jl$0w0X`+_7fs|kgjb|&0MIIhI%Q=5TZ$9oHaDI9?
zfNfjm+N*EAvsQCU+GIg^^bd@1;f1FG@c{&8i3==kOEZ3%JIIwYe#Pu>;Q}i-0G&Ak
zOQvJXebD%fQZYj|>mh{1k~Ufkl$L~v&HSR+y7z&2=DvFuE?aMM>uCXi>%V&QKL&OC
zB4x4|;ZrE}bLO@z8%9qfJGUtgWDDV2iM{65B&6ghAi5H}g9vB`LdJI}6|zX#%sD`!
zj3iEN7AsbMao+sbcWylV>XQkAM+E>Ogt_sX-+ywwDqa<<42BGzUu5G%FK3@~3S|DV
zzOU8f1T7%ws1JzMx<RX~Z`U1`a!^GlPSASZvbP;cPbrIS*+|()XVXTIYMV;kVsSxy
z{Ko4KfA(a7;J9^wG*$fQ`?qB(b78Gq&5)Wr(k?JIKE`Fwu*qajKL9N01FbrMN*h^4
zY1-XVXx9tY+B!LkTCIcD(6*tmuht@xIBA5RjY0^pq{g*VN<HQg$4}3{>N%qUConnn
zxH*91*e0k&pQ;BA+IR@zQR?Yu+qTn__`2a25ZZvWFpi7yGniZfQ|iI=4`52Y;JQaS
zL#qz3N*@sG-=thA3YL7!ONaTaM<(kdg+NFfV;Xuvl-SJA%Pn!;t>5tN14D4)p6hXO
z0HrkFz3~T^))v$)^)QbjkLUHW?VJ%Vy7=@Vev%++HAoY%ZE(HDj0*yY!p5=VKqd>>
zEZFwZ@17)x>BA7m>+=tEOffS@Q_Wg~N3EOyjvlAVP^s3NRzt(TETzGg38ex^KY8+V
zpEG=)<H%FX0c_hcX{f#!B`z8dAwBx~``CKs=|OmvA`Tx$OihCCL;nD#rx%mWVsbf*
z$U`Whs$r%M`={WcU9fW}V)`&bD}>`<#>O!dn=qc&BKwzgfi8+4on$^O=d9KfmRNyh
z35reSP>6<tp|v2@4vQ5*T66BX`Fm5paY{Kr9L0R^+dq0)P%)pXM|m`P{OkZ1TrkMl
z=bif5A4iD&2f%Y-Y#h@X1-#>K#E<Vh%#rCC3VDZO-owviDHc5jhYPqFflwd~^b}yj
z2-t4pHw6Lg-E%Y}Nv($qw0H|wae)^9z$(2!L$w~FlxifJ7=x6$kz<h2Wb^7RFL>d|
zhqTs02yt=&AcQd2efg%TTGbtnbrB=;jE-*PvS&DCbEg&n#=t`lA+&;X&u7ICx_dj~
z>aW~KrBcSVHJOadV!2Eqo1q>Al!`^hh6fni*vHUF54HoAg5d!eY9^~_4SOCzgyAtj
zU{(i<+8soll))8%0BW^}G)<ciRX5Ao6ggj$DX4e9@P)(wD~^?TJVe5}@&RhqI^VqJ
z+b>t47}j`zjqCah4V`-80~|Vtn4N*GTUV;f`zPT){&PRo>H_(!<h*UeNU5k+Ep%#O
z$Z+sTg`eDWKfCw!ko6qSJZlpZTLw`_Y|3TC<})z14QFh{?Ae2;R+^$fbr1!nQI@!D
z`-`IhU2)FQJcDHri=)?aIgfe(Nun@DqKyF!wGe>6yy07irR!Snh@w=uu65#dfP5im
zY+=16O>K;^K{^x*J&cW?7Fs@2hoM@7$-_reyS0W-er~5wNu4vsi)`E6gI0CosEj2O
zq^KZ>I6QTTfdP*&nnw^ZH8sf>ufB=<cOGQ14i9ce#8KljH*dydvmIgrZ4Mw>0nlmz
zw21!ptdkY@1KEs+>)6Y6j@n3+wyB2}Va<B`4c|Ka0nf9IQu@RL;M0Hq&v}({rZx^n
zG)lBeJy{B+Q$PE)Zpi5{gaiBG2R}gU+mDzn8zDtNJ}>b-O&l$vk_t(*ND?ie)FQT3
z;o|dq@kE*Px0OhAh4TCYy*a@hw>-cj`;QPN@Zf`um;%6-Eg05e0<z>PX0>Oq)f8;S
zhF~S9$Yxwz$7w2u0%HWJu^J$#+kbxJ4Tt}?ZCmDq17O334WBexg0MkYIF3tCPs6iz
zn%MfJsUnUw^Yd{3eelqIf-|;kK&2sZP(>#}qY7BY=(LPcRnlkyla?u!B)fO)VB>HG
zDa*_)Ea1BVKlt7c33Wmc!mb@n+DJI#jE0ig&GWZJ>=jl(3$<k@4$#3*V1R7K!LjXS
z>%bUEsx9iFB&^x5z2Sz#fBRVc1ji)d?LWF*danD6##jj9AT1l$^C=cNEsCd?%rs*R
zN@<cr6UUl*4bIv;NEpQk5faqqsMjW`*QPLP4wEjBMl+=G3=Rtv3W_6Bd)Uw~fEuo?
zIcwVxxBcKJ2nS|o6OJ5i{Nil(XiR})iGnt<Aev8o6$cQj#lsQ^G8qS_MI0E8f*`8f
zuld#ulkYw;05A|G%0kGzv>j~Q!j`h(xjIeK8!fBh=n+DYDov^sNur3Nl(43`{PL$_
z+bKe%O(%=Os05=UAVv}sh4bY6gs?tO5|=q^OP0gaQ#`z54}16TWBc}pFr@5#L}40z
zCfKyO$pu&g2&}$N8^>q`$qJyf>ITg~l5JTHPJn1~0-GR|sL+4Mb>BMpKORl|Kprcc
zTA!cWBrL~rTo2o|acqgvDYe>(bWk4O7#Rk~wJ^q@wI)q9ahwtcF(xsDp~i6}X`&Fc
zsM{D2reS7@1dD(l-?I~)ED{C_<T9F}fdX5$odG7LyjW*`wke<6l|fds^iE2l=%ygH
zDTAv30c5i-mL-=#pp78WHuX>-)9e?%dHsRo4kycF*#1;&)hkjY!m;pt2iLVQMlm;M
zh@#V@gU0wcrch|;BuXjLrYHzPMQt%6NhOx!5Jhq0iMRS=G0kh5dYp2>)*^?eXUX`A
zAXsE_axa(&twNHd&g>D>8fsoG`#Z?}_Rw%^h`5s|Xcq&m;vtuHu`IEI6C}!_T9-H`
zd&~7V9^AGtZyrA#AgtGOcieIhF*Z)t!_7Llj$vWG!v4KYu6l}xl(2Cea(NFh7-LAa
zA&L!)i!tY&dl3r@bsWb*iUu1MLeS_V(g3>B3eOj$N}$3TLWkH^%s|Ouadr}w1gJEj
zTuD|deWEiEIuUS{;@8FvR@~pgL<GoXJY*9D#s~}+sghJ`5?g2PxbcS%WR||?V|D}u
zLHOY0)I8Rm_jB15S0S7f&yqwz!h!t}8Q&A56DwV>r(&FWCT4CP5k(&LdWbQm5dew|
zLS)&p<wEYi@8)WyJom8Ym<7+by<EoWvn)Ao+cGPKz;i6-=NCwYjYZ1h;v!D$kn>WK
zI;5#X9IF)yXeaY;iI~!WZaRn%O$U%^QSoUx7A==di^E8?IgXmoIpetmVchgWHbR|g
z3bh)=f#T?Yl#1@t*5l>1*LJZQ@wrcY_J@L$1N-+7-t=vxo#O>Bd_I<?h=PEJAC_q2
zi17)GIK^h+MHfO8+DuM)1VMn-rtyFRjqi@}yyw5--e+8yd7kfxp!I#f^V@ISIy{j5
zaDT6VRk=D_3bl*SHobYxS?6uQBoR@d2$RN)Tk`I~Y9oHxgj;G<rdx>E^b{^joi>Gg
zX{NUld<g_Bc^R8~+ZOnqO&FzU-T1Gm0g*sZ=c(VhdFFFMh~GaJ2#y&GxcXneVBLHF
zBa70?WUKW8sjxX?%NbnujLR^wA_^47IE;^ZY}tA=h;ph%5WpRGAf~5ND%EO3RvLvA
z;MkgcR*)~*e^M+tpSt+sOxhmrc*P(5#UIzB=<kF<^cQ@7`B#65@|;JIWI*J&@S+LM
zf3h%91dr@)B$>2#Cs`37VrAm!T8Sd9A!5@4fK~gJ;~+{&6Wyp*7GMd5V=D?dO={IH
z{KXfI-TRn>L2rHIyT4xx^p%d?!$L(eIzB*}W_i&szZl<kN$N34Vki|eY(4|}22L#q
z(iHBz6EQVqm|v(Y^I5GEEWmLzzDJtNTR+lL{ppKeICOXWXZ-GOzvlK!o_fX8#x`6)
zJ+jcoqo;R>=RQA2p@7-BqfvX%UQE(4Lb#qRp?1QrgZN9w<yxbSO5;9-V<|jGQ!Ei>
zOR9Lu#XU*4QnYme;BVglk+DNl)%{88GB7wyoLG!*9OHrQlbnCy#cbO)LEg_1)nbIO
z7#VSx*aY4w%n&jL4je%2*a7nk3H5r===mcc2?GoYOM+t?EU9DLGG*H)5a={X5-&}3
zU!3A6X~P4W&-e1wOY&^l0+WZJR_l-`(y0ZF>9B8Fpt=J9mc>gO2$pwk#D~V`RBLhb
zGa4ae$5LcGO`+F(_u0?td2Tm4uL}S|2=gbe`t$vspBt~#HNM}=hT&nN1S&O&kv?0u
zO>p_spG;Uzi6f2gIgE`7Mn=JQP8|^97<TVQ?B5TSYDy5q2q~}}8>19S9HNpKV-z&H
z3pO8W<4Ie#$KX&eXP@OTz7ZDY8^mudM(vs_*ji5BDKm(+<4z!G;{vTfXbmU^p(ak!
z<ri(Ka2-uPr}6UY;w!J{z3VaM1HAS1fB6qnGxb-+8idG`q&B&Ho=m1dF5g2GSqR7D
z>Cd>3GdGVCRTU~RWV06I<AQ-9u<TO@geZckDa3&TP%dkNK#?fjGz1$@+h~JI8|>7v
zE%F7I;bE5z8z7T`FgV(&tUY<OQ?m)v-fg5qjzGu6kWL_2OAIv6D>a2u4Io>>VA~4c
zH57Zz{Ii}_?CV79bpznfU;n{Oy}nRes79Q7&IE-*fx|P3!$%@4D?_nRL<-4#EoF3U
zf@eJAB6^EC;+i5&p^%e|jS2b(FqXq99n~sgZVt+2s8kzyf@#{2p|%YfUr;DOq0k__
zU?t((%gj}W-bJRpccJN=8PqXDs6`K0#roT3ek%~-*bqjI>LW>`1+HsraygBgO<wt|
zEBijxPVeK&2ztY-|KjsAbG6^uG%-RpE6HWEI9{HGs>{Kt8o6vgy`?<$Bn9CyGCayz
z=WU^w$q)w`l|Vix86AcGL5zKhfzbN%lBAIyu64u1x71#yy|k<o@s~=?+u8rBV$xP6
zusuVlP5En|{jHA6b{(MABCXamsctk(kWyjWnv8467Si&wpIs`o(|g^PyVhD{yq-VF
zXFX=-YD`a8IdE`}IIL65M4W$SmXRUN)a(={Rd_;E4;Ogoz8&0g$NeZL#V<;N#IR$R
z=B_&thxQ|q__S~*8^^);ej`0xwr29Q*!ipC`g9KXESHZR<Mp+ohil%uMh=4ON-Vk5
zFk6C=Xe~iG#T#y(e8r;{0ZR|+ZEt+{uT4#tzdSNHfJ&<Dn~r$e#iRIM5h+~|4o0}_
zJ`j@277*IT&u8c@^#eBNJZUQ#&qYOsG%`4jWMl+JHh|}!;_y<lm{jYdEPqvTX%{hI
z%xW%R*61%p7Zb3X^4Codpe-h+6$Bwozd*wxaBV}z*Vvipj%Q!lb4e@Bj|%`xT|oZ$
z<!^b1u>E)Ed<$V>GWiViwV2Ht25{^gwwFN&7o`Q0vnkTfVhIN+Yzp}zem2LJGdI%P
zmq!JVMg~g?`UhZa6taa=1cYYwkW!7RA=L(guDt}-vI5p=26CJZAl3ju7`7^s4FLGQ
z!po*S^NO5!)BtG42FPYJ<`uvHroTbhuX9{YZy`%hDMK}gNVP#~hiooSZ~q9=a!3+b
ztZ8Nz6Z(7lK?|gCDD{;XpBQCq;{a&@Ndyu@Z;xPf42nG`83-)^=(@Fb7i&3Rv*D!a
zP#{7VMeuk5&;bN#dQ^NV1-1pgr}1-!#qxodz32s-KKrNv&<cWAzU(cponNf}?}33L
zLp?cqOF1f)8c~v<jbw45PH$fy{evUq^L?Zm7Ai5*a}kAnFTUfE#L&}MWOU;Owrm|m
z#}J1gwV_xNjEzE1Z*wt_Qw)H0bAWd3->kL-+HL=10ic`x-wpzROs-KMk;}v6!G<ry
z_rcFW7#z9z%4ZHdf5|iWm>NU~A<QdY`sQC)tku6UJk&4y3NE><N4*xHQ$;qPVScg3
z!hDrtsh7TiVS0N9u^o>ib9Ls*DVF7t_X}t($>g$ZI%AxPO`}L5Na~Gr=u#61rQSwb
z+=&LjvJS8$3Oe-^I*vw@-O9r{)&{f#z^wFcE(L^E0isDo--A*K`UhZW5VLo0qtwkv
zuxv=<#a)*_qqJ>VKYgq{f({=k^YM@TUC-Wwv)}ey`>B0JmqIo}oWvxFCZF@k<$Mkw
zS)f{tv2B<Bff0K82gzh|lxr!64p(vP3|YT`C2btnXUmxrjE)cC*fvS6@ymOAV0;1!
z#S;Vqv$D(B3fsRG2xfKT$rXhntsTX>5q`Zffc1!gmiLa0!{8wF^)+sroJ1U)1YFRm
zAt=ure9p6n#+T^+xHO46bnq}^<0Iw|Ui#+OXrunB;LD6HG=)+gZ6Hkr#hgn%pJ8sH
z#==4!gH1kPWO!sFekMnv1iSXklF63H`Z+9NlgSm>Fh0WA#z9=^5Z4U?!Pc!XyrDBE
zJkbC+o>sFRBY|xSAnkF0E*`*k=TC=2dVArFGoa9-L{HBm4jl$<L2aha%;e64zxJCK
zJ-z^F7Yi?Y>07>+&x&VN7G}iI&>-1D2CW74dO}a$BVWi6Mw*$~GEoGcmtk~#Gnq^d
zBWxbpeS}hR2-~)?Etj62e)7c}ThH2vNG<AR#WSw3@ck1DfQ~vqn-ZuytT|wgLjgP<
z?SC!VFC|>`6v*dS-m-rhF*64<(=loF2$lH*2mj~qK7APgk9(`w<_BN(rq{l^=;a3Q
zs0H;igRsuvU_XVTO%Nw6P6p&M4jV^%NHrXpsj_?L{dj(c(Xq{(b4DKq#jd?`6pQ^#
zA2~=67pa%4_;!Yko5t~d>y(&^t8)H3<n}F<hne;y53@1@NF0lWwaR<3GFxbw_RatG
zl&3(htxQiFDA%AiZ<w1|U~zUDPn6ALaReRL*!kci{N2Ytw^^z5mSRp1S(YH1%MztY
zvonXrvnBogy(ndvnX9t65R%L18Q*vYo}VL1G``b|@0Ktk%iz!`&wo*NeeLBRgB8$G
z_P6-@mFnIfU|q$bU0lCwg?&wzu=jcA9hLd5V{!qp>wd-lLkrw{?_E^ON4WIK#r^-^
z%YJEMiS8%VxY~Bk7C!cW-nHldeB%AXmB4*crDp9>sm3;fjBDX}g47r$r)Qa)Utq95
z%a$$u7#;G!eRrbM8e;=vWV{|U8F~kXdEN_75b=*G+E%83wb%Qtn6+)LUDn~wmF;~8
zTbfjf)_#U9Jqt0gX;Ww7M=4_WgNj|d5ApCL2biCq!*vqswQ$9~C*@H#(RRNOV#}*u
z{<cdCdHbWiMf({U$3j_}R4bBHbNI*tj%_hCnB%Oo&Ze*DOlqNvHa`7>!#w{51>?9U
zFQKHX3}_Pv%Q=4?DumlBgS)YL(}nG~R}n9X6Jua>6gF<`tQOq=0PNa*gq@E}GIMx8
zmIxW>vso-xLeui&PU>7T#)!Rp58l3c^Z4a2`Moz?JUU!>S1x1!x18^gq$!C~#A!-4
zKSFQMIn+W2WqgK)hI#Jui^lU$JkgC=hfEfoaj_!Nca3{n3AL;e98F7T#BfZLJ)4FN
zzar{qU|;~YoY7f@*s%+7$L;&L^WOXLT#NJ09$?3g9ptmUR2PDql+wa#7IvO=X1Zz9
z7{(ZJ<jCBeLqq+){5!w-+Og5G(%<!!ykE_@HWTCLQpj(k-X#9W=m=L`l{c<;QeDCw
zxj>8lwjvvN1qWzX_*fmXUGaG>C=g<0*6<20(DL3=sgVZX>8n4o4{`I&ySe-R2PqbO
zCN{V{v~w3i*Gba?N}11EEmggz&BBrAZF`>e`q%yG;bLK9f0%d}<1w~z1JAzdB*}b(
zXkB8atGTC-y~vbqi~8wq?%qdX?qzdu?zx?bf8Y?}x^L~}CwD(UU$4)m4OtFNPxHvG
z?VPc31Hujtyz^bJ+Sp2So6}$z6N&4ux%ak8S@wsCj}ac5w~TY;v)z+gaALa5)b-kj
z%cftG3rMqi2AfWz;~3`N(v6u6oYRH)lhcTA-+X{O?|p!gp)7+v4m);DGCjSA4gE!I
z&m|1Y|NN*kf==Y@|K^wPm?>B6{zSPTGn{qy1eZQtp4@7qHYZRgkKgeoe$ohK9MkF3
z&CAz1as@cE%d&b?(}?T7wU0aQdVnn(dr5;ucI`Pttv<_!{tP2S15C|KS3dgD*A(07
zeJVMC`Pwx<=>Pf`@7TXsb~1@_v79^?J!O>hpCrx6=R(KO{yPYB(Wb$-%l}Tgz$)#3
zN#?g{`K_a;2PP&u6aVlu;+n7T=H3T(aKTx_?A@PH^~nGL8%#+=K~%GsAPK3}ry1$b
zGT4`6c5ab0uK(_1N(z0vXZ>|wyY;zoXn&_xcSNFHT(8J8E+1j@7IE@Dd~NFyu37EV
zVL|FOJArkP`R!oq=HwX~g5fSp?Hrs$eC2BovupQm&OLLG2OoSGFAG`+q~Rh1J$dT2
z8gXi_{_x+t`hRw!wLH1A%ruFA+Z&>~eN(mOibQ#2bNxK`c^gg*@!L$mV{rn}%|XQa
za`$9=E7`Vo^s=S5(AXHJ8}aw-M_m2oUF_Sxk8PX#xc`Bj6nkwZHuhqhkWJ&m#9^I8
z8HW13mrfo!(s82OCzJS6N^{*cw|}Nywf-Os9a21c`-gbWb4n*a`=^y|ZS4YDO|w?n
z-OczHq75|d9l@50JY`#az_ciPEgr_Dux_c;`3pa|(}*u$^8j&NX2Vd9?GHW7=va=S
zfgDE;A7bO!0CUqvh@yy0#y;y!Z+&^an_hd;M8FrW`Kl<1jki|I*3-ho#R#9_kzt<m
z+&rFl3X?&X)+JWm3p`h``Y!7WubcI+q3j6(;~OEH?O+KSxa*!&eCexqQtEL@qbgI=
zhZx(CqrW#pr96vcL1kf)xw$fyE8hIU554xU*D`8O1^~=;-?+6`r1pVw*~-SLi;+H?
zHjnbGXZxqTuR!ZEw5=d8W_<uyI$t7??xgK^5i9K!ma@@{u#?{-Rq&%*Lh<!)+`*aK
zMmVr<KXJUk&_I@+qDvIalO!?qN`=Z|Kq}L(eE1`8`1ND8l|9iSz+8XrZJS8!Jqs0!
zRQU+Y=bUrLx#Uv&)OQ$aI)7Ho*ki>4Sjnna9k=`NMcaE?DBnsJ+lI|sI`f4nMttX{
zGB<tyE-t+A3?6u32N^G9sJ}?YH$?RrqA((gB7$l}V$}CP@{u?E`eSYo@mR<8*WUJQ
z6<Ob_RBaONW7!!le(E^qo-a?2_EM&Ec5ZvE{(3>M%E{j$P|ztbl*z(|4V{U<P)2<1
z>yzB~;BL-6XPgHg+(|wYFf>rW5)rk^G;y4O5`@)&Sfx8Z^3m5nXT6;LgaAMYVQ%=w
zZLf~%*2n9CL#jO-FVE$dkFx0$YWnT7pDwa{6)BDZf+etY<FulSeUE{G!A66tZf4%q
zU*5stnHhR}JoX<vKwnAG*Hc93h}oF~s5D06Q>#^pm0B3tlsPX9<8=$x#}xotYb?t$
zH+=K9zpgFHH#S1K9+_;3XFh9~4Wp;eq7vQGvX@=Lrn_Ihb>=TSbvl8d&G#$Xh(9vc
zSd*aB*!~dWi(k2$`NhNJvssJ@819o~Gae=hIC6MDQC!EdvIJ2LrB&=?%=qj6>{aS`
zp2On?fG~*2WPS6kYj6L@>VkY#5H*&R$rbx})|CSc4xdKL$uyficFM^=mUE_){lB7@
zPX|ALdpzH^V0fglkku+>FNGi8S{K(|cNbwaM<HLNr`MvC6G%dIROiU_LBhC(#3zXp
zluoNy@!-4P^Tz0SAI0MWfNCY6P|TWdef_piR~N<0!q`C>k7CaNS6<mi|KRCGe4{&8
z=XR3)W*y&Lh?VADtFk9sroI9E3=DR89+fKM`WvVD-hbYMlp)1pp0N?1tPgRxK$6s%
zn>kFXLeMTCNOg4N;rRm_KL3diJ*tL%_Tw@03&pIt{%g1WOLbBHUJ$z&e0qBOdFGXU
zr@UWKH+O!gx;)xgKkFp^GVAZA{57wYicl<e59MBS?Ze!^{b6h;C7<&+W1@sDBdV1d
zOd7H{H-{#{wX>+ykg90^dp`JQ6Z3P6kJ^}iOaRbYH`)GeU#c#MUk?)pgHN${fM;IW
zd-@SyH=I7J8a2>a6dWTt=%_bObf*2Xejs3IxHHRF3hsSCi?3dL7t_--*tTL~yqErx
zi_ul8<#}`xQeIrd2t^@ZA`TS^)&1}Kz@JVWIWi}DdW)PA09ck~Zv5tL*Ouq`Z;d1l
zpF*jRD=t5MiLbj<<#tWb=r-$H+5H-Mf40+|<<|rF(XEmA#<y-qsSvHJJmtc3uuPpO
zEE7i+(x^_YT1QAI6?;g+lrXODc;|axb9PV<g`atBsZx*06ZoC$Z~szdo?oj+9tNLm
zzL%#zeUQ<Or!Vo_OSM;I?C8!FJDqCxkEJ6R1H}?1m+Soc{zHgw-FSdI@7{@!DSf>b
zTQ?7(lL}hJ)a&!a)e2#h0E%KMi%wy&JoCffc*%di^89UE9v_qa(EvaQVQ#$s*8f$T
z=biP)LmLl2)5}vX9_EZQPkqPGc0WJ5nV8X`PJh*=yO@Y{-**}V0~o85OIIWAx=ZoZ
zYwu)!p+XSO^R%aICFdKGxPm1V)ygajb92oVO=Eg{vsl7reqrX5@BYB+U-fvp?~fY*
zLI`u?b+=p<R=K%WcS*I6;}$vZ{87$5Pn_~jq3y))R-B>N=+(DM7g#6ZjcJr&m%2nY
z=NAz-+&IY(ZrzDeF|L<z;dvV|X$?byG%=O(4AWCHC<Sp`rLQ-K=Xq4B<=4LF18?})
z;~l#>E*+p!4!G%t|2i|Ob8`?lDD5Jx44XC$vw8DrLj0zOZ<&?rmb9#6wP<Pg0hnWZ
z|5`_>r_pC<l>{>I@FR#XedS*E95{kj0h>4VFflqp999rQg9@3MndHdy0vJK9Hb-x7
z79j=Y%Hn^2?+4#_?THB7ts4Mg5b?uXe`JX$cWbR8NwovAk+Z*PvrVCR8qNA9;g>8r
z)1f-I{d|Ro(5%l2Tmph^H;6Agh`wqKam#HruD$-p)aoI=uejhz8}Xf#II18>&`FKS
zLkF2(2(YA}TAQI*@Nr!ys@JPeegB8va{q~n+OAt#XR^Mr;_R1#3Vn%oFu3INJ&bS6
z(AU>Yg4E3x{N^Nf{#yEfx+SdJVG+mn(rn$0)wlZq%u0f{|FCQhdiz!rUz<jb{ujS|
zkbl4Chg7Ny3=cb8`jiP=E5N7-qv|Bl9FIJ*m-)pA&$Wr81&Re9-}m;_YxUCm{_4#S
zoXC*<x;enL*L?T4lZao5m4h)Zj+0@-Xdi=vAgx9mU6`Y3Pl(3qEKOVZaWCU;b-|ir
zHC|k+CCjn;@pf<k2pX~bzP`>-uSbZx@78?#yZ5kX-z1Kea>->|@kBxzSHZ+2X_X|Z
zv1jirMjP^3mnd8yoAt0AdB?lo|N2WRmD&lPNb3TC5W?K>^&ftsT9qi{f^;eM^wHZZ
z$YdMcv|0-r&FTe}1zqgquHfj#Mb>Dq-ifelDt5=ynso&?TwRL2^!`2j5qI6S$ajBq
z7nQ|DHjeeMb;|%rR6$#y(h8W6YPrn5{bd{*iUpTAu3=jeZPLGe?+4!eXD2IDYh6~r
zb=Q9XS`)gzRF6DTmBsN2Ol%rs>zS~$!gjaB$BxyqodPOdT(%s;1Y65aH(j!G*42-%
z!?~0VcHilHR|CX-_Zt4=hxf2+?_qQjaLLmevtE;;bx0aju<e8c2j-cXsglh)WPHFR
zD3uTf_2+*0qi_4JG3Iew9QrW<K&6TsZ@N|5v54wb%L>yB8lRrt5hf-IjE%RIHnYaE
z>TLixp5M2M=<P)7)@5U@>ua(7HYIS$snaQRYe4LL81B1&jyvw&#i2t}Y}-1-*l+=r
z)Dc3FBsI_hQp7y4eTtwSQ7m~_BEfMi;;0^{X|nkvAA9@c$xhQ>697U8^OdW=bxp>~
z|4Kc~kf;ononvr#jI+<SaNVP<U#@fQyjDN@v4GIcEL$=Ap>t#9E;)I}(|OaF28YWR
zI&1hd^N1ZgQht2bZtj0@ACiP8UoeUzQiM>AS)bIgWX$4Xne7iwVo5>P7g$z`@419g
z__ch&|Mhph=XIxK`K_)xK$<GP@XufAEfq_@QZEZIR%6*%*T?rQtft~8m;B+f%Nn`e
zW`A2sb$ebNqC=~+#_Dc}hLwkCmR>DKV)W7Tc0#wa{8gapVB!@^7`H<|f+$7o+-cac
zbAj7`vYo@zb8Oo>z}Rq}I1WIhq-h<aB5WD6W9NSM?wh5M^KdNH{CtO6z4p3~{oOl0
zTCWGEEH$<(0363QSAYKNmzxxfmKb3pu&^DMY_>^!K*^&kWOZcyodD1lDO)kk#Txp+
z(#qh?KSp#>;PlZ%jP<r!Z~aGpc6GC*rS~10LOk?f%>57U=AQd@QY`vhaoJ`}8W2TQ
z1QA-r2on)TW$yUNLsTm<eWg6E8)G{HgPgC|t1teCPrmoIQ(c?6rWN3M&WE%~5yC?V
zi4-=Lw8`b0?NM7-#p==xdrdB2IyhF$+M=MNOSThh*Xx+=Z6anZht6UR@!)pNp1rf&
ze#gDk!hk1Tu#rMm5JeSS+n~}AL<*+P<m4p(b;|?fvv~#w3OIISmCYoPcl`Z7UU126
zw{DSt|B3gU`qKPv2z<{Q-j;d#^Iud$Wr#zMIQ22YCztQ%yeH)vHh(+YmtEG(T|)sp
zUS-#GAY9j4JF~`nR<nED4zBD-A$ILFJn~45oe%G4@7{g%_2<~Uv6ou4jOR&I8X$$i
zsDL!7@{@ZW;=ukR3=RyC&)Y~5k*2BETE6*{pMLL0>h(ZmGCrrn=*j`kKL4C^69ulY
z8mnxIqvfzmuGJ;S53+e|)}a7)QUqb$1^H_MU_CRv{WM?bT!o_y?4LwD{9wX?>3OCn
zr-_pqPq}b2Nn9n4>eyBS8Z1&&S|tdp+;;nR(nPa);|5&cpjAMU$g1aMMnC$uZ?Ash
zQ|~!Vmfc<h0CAiUM9F3mrg%Aluq0B0MxwP|u8NVOLt~^)BA^QgXea!!fY2>KW4aO9
ztmWEncGMKB&AO>M*tK18U}}+>>3JO2P{@Jn_{32eDHDXz7*f)>4yMlBLXDlf_L9qF
z$PZ?)tdum3sRiOoPkq|Czx9WI^jn(KaM+z?ziZy}_7879Z)6*mZDFMXtp!>cRGN~e
zUF7~cW&bL|i|&Dz^%B)|Jz1$cxGwZ`@ZB3=wu*T8A$WNA5e^-i!E+75#E8@&Ed@ej
zTPZ4y3BwAG)zJ3m7V1n*P1E07YOJnKLZuQDN6A&6{-^hR?{7aI2u{<{RRn0Qhwr=V
zL7w%TXJR`NtprLNj8>G(ibBC!a~YIlIe^yV3lO3Uk&iR7U;w9~;Nf(T^K$`W*A94S
z`y_kzPGT9=SRP&pEXUw@AcZ1LV#1(;(IKv#5CjqBYMpYqLQkoHM4)xT{6g(UrPXhK
z@-y!#pAJ2k9u)wLFlv6TjBq?GW3aVE8Bkg?KU*iCFDxfSwmO1zOF+o9hzL#d8VzMp
zo1^FLzJXOhkSur6FfG7YPs+9>8`-#-+-fE<y@=SgD`w~RX?E@2k4Y6hMGMCfc&^0r
z!IGLJj;IGUqOgMRX{3w^f|yDzBnSdL$H%0aauBdsslNPk|NQ>X96mfNKJm$SpVq`*
z69ASa7K5P9p4|u7vSkyN0$YJj3}Kj{RKqvEWV>szl8Cwk7g!R4LN(4+TWx(;9Y7x+
z2Qa4b-Eui76j!b>pn-jd5ZfON*uQ6reFqMaB#OaO79$Lft;iQ_90!s(q`FWcs4rq!
zid;^RBz2NRQ>z68VS<Vq*McOz-Lb;wfA(|lul&<z-zUUrPx>_gP%c-*M?U!Rot`VX
z^+&fbv1Kc^Coo2%j7BNNfqiAhCyJ~5|I6&&bg%|Guzj%(CGdD3X2x%rbxup)fz%*&
z><Re6_veY~Rc7X9m|rY2v0)G?3~^GYShC1vUE(lgZm~>ItDw_>QprWim?(^i6Gg2S
zQm-k3T7njYQBr^1KYjN7|5&Xy!nZ#y#9z}7@V2+S|DrHRf07u7@$s|RFus{HCdLWt
zhA0B99Y#irWV6n?W&fiAz?u)Ur3_n}#xg0zQX@$N(U1^9g4neu;M!{*#1;~zQw~i}
zv1NP^DJ>QjD+~{1$z=se6tcK5Pf)L+Rfy{-N+p*#uA?=CK}@xpP_4v7u_7_?j;EbB
za^5Rm_lMfHBtMO#D*z7d`hobshi_E9y#+Fbab{-gT=A@DGSoXjSXV@mKx@g^cn_X$
zt=9fK5dL@pAjBF(r`gacZIoN(@{rGg<3g;U62kT!Rla%6UG$W4_?ZmVT8*BfL#4b(
zwQd+0$xz7BnDMhS&1GX#lDNV^zmGN%N-5IBP_Ly_$`O@D{e5a%@pC@$>G%DJpYqWa
z073}!C$D_#jgI5IAP93vKhLI(V?6tL&n2lvM0HIRgE1B(8%p>YtGfx<Wxu9Rpo=Il
zrm@tkv>NN<WU`G0iqeKe!)zIT_@ha7Y~M>?PYy5R02Z#3aAbOh=_8A5+1yX5U=!Bs
zEY8mXAW;NCnS5U2dzv&&NK-`=X%^=LDwUXetdLgxuNyZFzwDjw{L_@5fw5*d_~kEs
z%SC;~+)r#T%i(E5Z-0r=(Jeglsw+t9ny?lV#Rj7#`Fw_fA-@r=6I}?uPE)Y`eyt%0
zno5h^VD)~cQFUWvBT#wo{T1%N?=aO`jjSgzIws?LC^WOvlPoM&nV9IOuO~yLyhy!L
z!?FaHG{j*QAwmiT8*NgOxIz3{C1!4}PPv+3EOo$fdH!cU^Ztj*<*FzYa{SB#-~;dZ
zn7#Ai192%^B9|{RznHS^?5)f!7@qZ<D=6l2MD>IyXe<ydq)oAup;UBMu<B!SfhAL~
z2?VVmiXlmw;a}Ns;J9vs#Y-EoAOUW_{UEz{%@RccM#uCNGc1-D@w^;Tq-Y(`Q_7Ib
z*euM=6GsuYC6Q8-CUsO=Czlgg7Nki+lEkDbEG~vjO_vGd6x$Uq`PYB_;J=*iHvQuc
z03n2V<;&jm8<y>U(Xu@Hdxps6^UN<ML}`|Zty{S4l8ew$LL4gMNHrEvGLlTj#q%sY
z&%(9^mL-r@ORT?TrLERgIg+$-k7YMzyW=$WmfP5rfXS&EJ9kX+$ivfsMhJ!L7;3dT
z3ky~H`$`NCdE{~qLMv+3T9fc#bV8B@U?MU;xSl|zszG?ANfVfvtulFJ5o64!FS~5;
zk6-utHz#STg!NOZ=sjuxBuUD*Z~VUa@)vKYDx))%s$qO=9N*7@%ux#r1A{|ce#NEq
z_Vkd1F)CIhu|g@+QiLH92n@2RAj)Q=#OB_TSW+UbCb65F?SgcmT8lU`TV?O=BkbEd
zO+5(E7^F1x_hr%Auy6k~eWffLhO+eadBkBtwNgQ=6e$f#g{UM160%u~j4#nfktQjr
zQfSjye*M6qd1mIzKQ0yA|MBV1d|>y_;;dgc0G7rdzw#~b2*c!EqoX6tEhLmH38m5i
zBSQmt`63dJTsF`77hizqW)MhpYS5`drHwQlt&et75(tD8SW<!|ux){~8?4<J(^xn{
zB^;hw<o<geA*{z}t*FHjT3f_P6-R0`hB!^>FSwkwbqLE6)GBp?dL6A|j7fkLbOI(J
zla*w%4j9wW?$e~%6w+d@9I|Wo)IllqZ+-srfBl20sTnam+|SSYSQh|<5at!X|Hg&Q
zn+Hl+-{ATMs)5J;$&kL@er(HQelcNaXn<lq!^HR~nS7p1wt(%qARF03rY-$P8%?S-
z(^Io7&PU81S)^R9P_0+-{VY-#G_Y8%vRJ8+q$!p#IF4Y;ra>k~i>NfBUJHoA0G*~F
z6k0_X-7x8lPVijF7yO2Pp&O1YV+;mCq72)29S$nx>K|SG?;rW+AP9x;d;GkNbr*-e
z<`3WcsxVIfp^*33GSP=+c_3Vj@Ti6s`zC8t%L$oG4%_!}q=Qn1f&M;*hx@Q4gkemY
zN@_twrBZ7Y^fXg<)6`(wHbGR!aU5#(ki}}1Ovb0TkfFC^G0>OCmXIV7^;!+fk{F#J
z1Xz|p7==<1Vc00KwxlAL%djN7jWNp`LUP}ZL(I)rU-89%|H!9*)>-{y27nO4y!@qa
zx=9<JTP$SQG+HE0B?t#US43J4J07X9?@+*#&mW_Z^@&4GkV>Xz7Kmew=lWzaS%O*>
zV+^kEA*6*cid^2Mkk1oE31JYB^=*24d|XFjv|3I(H%4JhiV(V?Hwc4P32{;<3Tnhr
z4JpC%Y%Hsh%+l&$Fu?r}O;M@VUzW?~|Lv2X_{%s+QeoQ`PXJ(zF*`fYXa4?QiaT~6
zTu{a^F`OriQfl=&xoiR3^Krc_`9d$Y?c=*H2%AJ{gn&#YkB~NC5JE1ml98sGG)<SI
zI2w&(Tgc_cN*as;6vk+@N}Gj<4IrpABu(Om2+%1~N*vp6<S|&*GU=5uJoNB1<x22-
zzHfi&Q=fWYcsiwKo%R4&;sSs4ia)<NOw5ndB;w3Xee{%a2n(ciNtM8HJcP7SN}{#E
zc6=<$LRvOLN{-foiH0@M?5}1xn$~MjU<}6S<qfSds0Pa?0j){Xm^h9boidxia$N`4
zaj<L)jG-Px>^rn@@bFCKEmu5k%m4e+H@;f`yt4bp41gsN{K4<P`AW~v{6`^YGuZ23
zHBC7otY-gv7qBqG0xYC-uxz_A^P7&>qpZ1+Zz$x^YEsLRU^J<UQE7tKG3W$iR0AMd
zH<p=8QdHV-95}Xv=XqGRL;!OOHD>2)|GDSD{9k?bD<Au2bFI852>)0Cu&fWf<SoAx
zDfP9p&KR}|8Pm)N7Np96&{)Dj8H12EmUPevY}>(gJZ#57V1W@$7H`nHQC@6NO+S3t
zXy~`h6-3i~Ylw_gDYVj9mWAs&IF5r-nsOzm9-LadcK>AM_22x)$ETi{?I!>LOXDBk
z_hEU{kN57r_@d1d#jI}3`sLLQk1AbD8hGoB2Me>*&SOO^pyj^S`tqjsu9RwQ+F)B2
z(y~yg(T8U$d-fe%eA~ZY{Wo8DVz!?o04((e{@ves!@Hh*-sszNnN(t&Mgos$N#hZs
zF~f~%h>2xtw<gNA)`+!g?#=fCN-gOG074y^tyT_B)js%d|M#!|3LuUX;W+jaR6SV$
zSen^F2tN3s&x`2;+dsBxEca^1@tb{lbX&8`l_uHIbTG4m;DSc(VVbICr;U`7AXH2q
zsqU>*<F{RL*^|C7apoE37k}YO^8^`BApn-Sfl^quZC?4xcjS8u@`7UC`=>(QJy%)=
zV?;~!&e0@5(>nW`38zY>D5cTb5JZY{HQ8Pdl25y?^X1ucJoAOmf2h`oQzXK+pP22Z
z4FK91gAjt3yyP#6TPKPer8MViZLYE{xlKyWGKO)}6zZumsnTYz*5;8kH4iIoZWBU0
zRIaJ%eN)lFZ(j3PNs~)FA=7_~0I)pLG{te8<6_fFDIukNf;vA70C-}Y-s6dB_5=W)
d7(dVB{|A}{iGK_`5CH%H002ovPDHLkV1nH%iLU?v
literal 0
HcmV?d00001
--
1.8.3.1
4
8
Hi all,
Specially this week and the next one, the scrum meeting will be on
Thursday instead of on Wednesday because of the Christmas and New Year
holidays.
So, we will have scrum meeting on Dec 26th (instead of Dec 25th) and Jan
2nd (instead of Jan 1st).
Merry Christmas and Happy New Year!
Aline Manera
2
1
24 Dec '13
According to PEP 8 [1], continuation lines should align wrapped elements
vertically.
[1] http://www.python.org/dev/peps/pep-0008/#indentation
Signed-off-by: Zhou Zheng Sheng <zhshzhou(a)linux.vnet.ibm.com>
---
src/kimchi/auth.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/kimchi/auth.py b/src/kimchi/auth.py
index f38d3d6..242fdcf 100644
--- a/src/kimchi/auth.py
+++ b/src/kimchi/auth.py
@@ -52,7 +52,7 @@ def authenticate(username, password, service="passwd"):
resp.append((password, 0))
elif qtype == PAM.PAM_PROMPT_ERROR_MSG:
cherrypy.log.error_log.error("PAM authenticate prompt error "
- "message: %s" % query)
+ "message: %s" % query)
resp.append(('', 0))
elif qtype == PAM.PAM_PROMPT_TEXT_INFO:
resp.append(('', 0))
--
1.7.11.7
4
3
[PATCH V3 0/3] check and set search permission for a directory
by shaohef@linux.vnet.ibm.com 24 Dec '13
by shaohef@linux.vnet.ibm.com 24 Dec '13
24 Dec '13
From: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>
V2 -> V3
read the quem process pid from pid file instead of from iterating the
process list.
V1 -> V2
resend: qemu user tests
rebase: add a method to fix search permissions
ShaoHe Feng (3):
move RollbackContext from tests/utils to src/kimchi/utils
qemu user tests: probe the username of qemu process started by libvirt
add a method to fix search permissions
src/kimchi/kvmusertests.py | 66 ++++++++++++++++++
src/kimchi/utils.py | 162 ++++++++++++++++++++++++++++++++++++++++++++-
tests/utils.py | 47 +------------
3 files changed, 229 insertions(+), 46 deletions(-)
create mode 100644 src/kimchi/kvmusertests.py
--
1.8.4.2
2
6
From: Eli Qiao <taget(a)linux.vnet.ibm.com>
V2 - V1 changes:
1.Add firewalld sevice configure file kimchid.xml to help open iptables port (Mark)
2.Add Ubuntu iptables rule (Royce)
Signed-off-by: Eli Qiao <taget(a)linux.vnet.ibm.com>
---
contrib/DEBIAN/control.in | 3 ++-
contrib/DEBIAN/postinst | 2 ++
contrib/DEBIAN/postrm | 2 ++
contrib/kimchi.spec.fedora.in | 19 +++++++++++++++++++
contrib/kimchi.spec.suse.in | 10 ++++++++--
contrib/kimchid.service.fedora | 1 +
src/Makefile.am | 1 +
src/kimchid.xml | 7 +++++++
8 files changed, 42 insertions(+), 3 deletions(-)
create mode 100644 src/kimchid.xml
diff --git a/contrib/DEBIAN/control.in b/contrib/DEBIAN/control.in
index 380584c..c0ea1f1 100644
--- a/contrib/DEBIAN/control.in
+++ b/contrib/DEBIAN/control.in
@@ -17,7 +17,8 @@ Depends: python-cherrypy3 (>= 3.2.0),
python-psutil (>= 0.6.0),
python-ethtool,
sosreport,
- python-ipaddr
+ python-ipaddr,
+ firewalld
Build-Depends:
Maintainer: Aline Manera <alinefm(a)br.ibm.com>
Description: Kimchi web server
diff --git a/contrib/DEBIAN/postinst b/contrib/DEBIAN/postinst
index c1fc22e..b27205c 100755
--- a/contrib/DEBIAN/postinst
+++ b/contrib/DEBIAN/postinst
@@ -19,3 +19,5 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
service kimchid start
+/usr/bin/firewall-cmd --reload
+/usr/bin/firewall-cmd --add-service kimchid
diff --git a/contrib/DEBIAN/postrm b/contrib/DEBIAN/postrm
index ef90b49..3c70584 100755
--- a/contrib/DEBIAN/postrm
+++ b/contrib/DEBIAN/postrm
@@ -26,3 +26,5 @@ case "$1" in
rm -rf /var/log/kimchi /var/run/kimchi.pid /usr/share/kimchi/
;;
esac
+
+/usr/bin/firewall-cmd --remove-service kimchid
diff --git a/contrib/kimchi.spec.fedora.in b/contrib/kimchi.spec.fedora.in
index 14ec359..3a3ca4c 100644
--- a/contrib/kimchi.spec.fedora.in
+++ b/contrib/kimchi.spec.fedora.in
@@ -34,6 +34,7 @@ BuildRequires: python-unittest2
%if 0%{?with_systemd}
Requires: systemd
+Requires: firewalld
Requires(post): systemd
Requires(preun): systemd
Requires(postun): systemd
@@ -63,6 +64,7 @@ make DESTDIR=%{buildroot} install
%if 0%{?with_systemd}
# Install the systemd scripts
install -Dm 0644 contrib/kimchid.service.fedora %{buildroot}%{_unitdir}/kimchid.service
+install -Dm 0640 src/kimchid.xml %{buildroot}%{_prefix}/lib/firewalld/services/kimchid.xml
%endif
%if 0%{?rhel} == 6
@@ -83,16 +85,32 @@ fi
%if 0%{?rhel} == 6
start kimchid
+# Add defult iptable rules to open 8000 and 8001 port
+iptables -I INPUT -p tcp --dport 8000 -j ACCEPT
+iptables -I INPUT -p tcp --dport 8001 -j ACCEPT
+service iptables save
%else
service kimchid start
+# Add firewalld rull to open 8000 and 8001 port
+/usr/bin/firewall-cmd --reload
+/usr/bin/firewall-cmd --add-service kimchid
%endif
%preun
+%if 0%{?rhel} == 6
+iptables -D INPUT -p tcp --dport 8000 -j ACCEPT
+iptables -D INPUT -p tcp --dport 8001 -j ACCEPT
+service iptables save
+%else
+/usr/bin/firewall-cmd --remove-service kimchid
+%endif
+
if [ $1 -eq 0 ] ; then
# Package removal, not upgrade
/bin/systemctl --no-reload disable kimchid.service > /dev/null 2>&1 || :
/bin/systemctl stop kimchid.service > /dev/null 2>&1 || :
fi
+
exit 0
@@ -153,6 +171,7 @@ rm -rf $RPM_BUILD_ROOT
%if 0%{?with_systemd}
%{_unitdir}/kimchid.service
+%{_prefix}/lib/firewalld/services/kimchid.xml
%endif
%if 0%{?rhel} == 6
/etc/init/kimchid.conf
diff --git a/contrib/kimchi.spec.suse.in b/contrib/kimchi.spec.suse.in
index 9051284..dde9dae 100644
--- a/contrib/kimchi.spec.suse.in
+++ b/contrib/kimchi.spec.suse.in
@@ -46,10 +46,16 @@ install -Dm 0755 contrib/kimchid.sysvinit %{buildroot}%{_initrddir}/kimchid
%post
service kimchid start
chkconfig kimchid on
-
+# Add iptables rules to open 8000 and 8001 port
+iptables -I INPUT -p tcp --dport 8000 -j ACCEPT
+iptables -I INPUT -p tcp --dport 8001 -j ACCEPT
+service iptables save
%preun
service kimchid stop
-
+# Remove iptables rules to open 8000 and 8001 port
+iptables -D INPUT -p tcp --dport 8000 -j ACCEPT
+iptables -D INPUT -p tcp --dport 8001 -j ACCEPT
+service iptables save
%clean
rm -rf $RPM_BUILD_ROOT
diff --git a/contrib/kimchid.service.fedora b/contrib/kimchid.service.fedora
index 7abe49b..e39f86b 100644
--- a/contrib/kimchid.service.fedora
+++ b/contrib/kimchid.service.fedora
@@ -1,6 +1,7 @@
[Unit]
Description=Kimchi server
Requires=libvirtd.service
+Requires=firewalld.service
After=libvirtd.service
[Service]
diff --git a/src/Makefile.am b/src/Makefile.am
index 7d29e28..e3938a7 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -24,6 +24,7 @@ SUBDIRS = kimchi distros.d
EXTRA_DIST = kimchid.in \
kimchi.conf.in \
+ kimchid.xml \
$(NULL)
bin_SCRIPTS = kimchid
diff --git a/src/kimchid.xml b/src/kimchid.xml
new file mode 100644
index 0000000..dee4599
--- /dev/null
+++ b/src/kimchid.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<service>
+ <short>kimchid</short>
+ <description>Kimchid is a daemon service for kimchi whichi is a HTML5 based management tool for KVM. It is designed to make it as easy as possible to get started with KVM and create your first guest.</description>
+ <port protocol="tcp" port="8000"/>
+ <port protocol="tcp" port="8001"/>
+</service>
--
1.7.1
6
7
[PATCH V2 0/2] check and set search permission for a directory
by shaohef@linux.vnet.ibm.com 24 Dec '13
by shaohef@linux.vnet.ibm.com 24 Dec '13
24 Dec '13
From: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>
V1 -> V2
resend: qemu user tests
rebase: add a method to fix search permissions
ShaoHe Feng (2):
qemu user tests: probe the username of qemu process started by libvirt
add a method to fix search permissions
src/kimchi/kvmusertests.py | 63 ++++++++++++++++++++++++
src/kimchi/utils.py | 116 ++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 178 insertions(+), 1 deletion(-)
create mode 100644 src/kimchi/kvmusertests.py
--
1.8.4.2
3
4
Include create storage pool UI and translation files.
zhoumeina (2):
Add UI support of iscsi
Add the ISCSI translation po files
po/en_US.po | 31 +++++++++++++----
po/kimchi.pot | 30 ++++++++++++----
po/pt_BR.po | 31 +++++++++++++----
po/zh_CN.po | 31 +++++++++++++----
ui/js/src/kimchi.storagepool_add_main.js | 54 ++++++++++++++++++++++++-----
ui/pages/i18n.html.tmpl | 7 ++--
ui/pages/storagepool-add.html.tmpl | 19 ++++++++++
7 files changed, 158 insertions(+), 45 deletions(-)
mode change 100644 => 100755 po/kimchi.pot
3
5
Add the nfs server messages in po files
Signed-off-by: zhoumeina <zhoumein(a)linux.vnet.ibm.com>
---
po/en_US.po | 16 ++++++++++++++--
po/kimchi.pot | 15 +++++++++++++--
po/pt_BR.po | 18 +++++++++++++++---
po/zh_CN.po | 16 ++++++++++++++--
4 files changed, 56 insertions(+), 9 deletions(-)
diff --git a/po/en_US.po b/po/en_US.po
index f2810db..79b095b 100644
--- a/po/en_US.po
+++ b/po/en_US.po
@@ -6,14 +6,14 @@ msgid ""
msgstr ""
"Project-Id-Version: kimchi 0.1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-12-18 16:04-0200\n"
+"POT-Creation-Date: 2013-12-24 10:17+0800\n"
"PO-Revision-Date: 2013-07-11 17:32-0400\n"
"Last-Translator: Crístian Viana <vianac(a)linux.vnet.ibm.com>\n"
"Language-Team: English\n"
-"Language: en_US\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"Language: en_US\n"
"Generated-By: pygettext.py 1.5\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -407,6 +407,9 @@ msgstr ""
"It will format your disk and you will loose any data in there, are you sure "
"to continue? "
+msgid "Please choose"
+msgstr "Please choose"
+
msgid "Log out"
msgstr "Log out"
@@ -459,9 +462,18 @@ msgstr ""
msgid "NFS server IP"
msgstr "NFS server IP"
+msgid "I want to input the server myself."
+msgstr "I want to input the server myself."
+
+msgid "I want to choose a server I used before."
+msgstr "I want to choose a server I used before."
+
msgid "NFS server IP or hostname. It should not be empty."
msgstr "NFS server IP or hostname. It should not be empty."
+msgid "Please choose the nfs server you want to create storage pool."
+msgstr "Please choose the nfs server you want to create storage pool."
+
msgid "NFS Path"
msgstr "NFS Path"
diff --git a/po/kimchi.pot b/po/kimchi.pot
index 762f4e4..78fd77b 100644
--- a/po/kimchi.pot
+++ b/po/kimchi.pot
@@ -8,11 +8,10 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-12-18 16:04-0200\n"
+"POT-Creation-Date: 2013-12-24 10:17+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL(a)li.org>\n"
-"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -392,6 +391,9 @@ msgid ""
"to continue? "
msgstr ""
+msgid "Please choose"
+msgstr ""
+
msgid "Log out"
msgstr ""
@@ -439,9 +441,18 @@ msgstr ""
msgid "NFS server IP"
msgstr ""
+msgid "I want to input the server myself."
+msgstr ""
+
+msgid "I want to choose a server I used before."
+msgstr ""
+
msgid "NFS server IP or hostname. It should not be empty."
msgstr ""
+msgid "Please choose the nfs server you want to create storage pool."
+msgstr ""
+
msgid "NFS Path"
msgstr ""
diff --git a/po/pt_BR.po b/po/pt_BR.po
index 7d59503..f790154 100644
--- a/po/pt_BR.po
+++ b/po/pt_BR.po
@@ -20,14 +20,14 @@ msgid ""
msgstr ""
"Project-Id-Version: kimchi 1.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-12-18 16:04-0200\n"
+"POT-Creation-Date: 2013-12-24 10:17+0800\n"
"PO-Revision-Date: 2013-06-27 10:48+0000\n"
"Last-Translator: Alexandre Hirata <hirata(a)linux.vnet.ibm.com>\n"
"Language-Team: Aline Manera <alinefm(a)br.ibm.com>\n"
-"Language: pt_BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"Language: pt_BR\n"
"Generated-By: pygettext.py 1.5\n"
"X-Poedit-Country: Brazil\n"
"X-Poedit-Language: Portuguese\n"
@@ -424,6 +424,9 @@ msgstr ""
"Isso formatará seu disco e você perderá toda informação, você tem certeza "
"que quer continuar?"
+msgid "Please choose"
+msgstr ""
+
msgid "Log out"
msgstr "Sair"
@@ -475,8 +478,17 @@ msgstr ""
msgid "NFS server IP"
msgstr "Endereço IP do servidor NFS"
+msgid "I want to input the server myself."
+msgstr ""
+
+msgid "I want to choose a server I used before."
+msgstr ""
+
msgid "NFS server IP or hostname. It should not be empty."
-msgstr "Endereço IP ou nome do servidor NFS. Não deve ser vazio. "
+msgstr ""
+
+msgid "Please choose the nfs server you want to create storage pool."
+msgstr ""
msgid "NFS Path"
msgstr "Caminho do NFS"
diff --git a/po/zh_CN.po b/po/zh_CN.po
index de759ef..890bfb6 100644
--- a/po/zh_CN.po
+++ b/po/zh_CN.po
@@ -20,14 +20,14 @@ msgid ""
msgstr ""
"Project-Id-Version: kimchi 0.1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-12-18 16:04-0200\n"
+"POT-Creation-Date: 2013-12-24 10:17+0800\n"
"PO-Revision-Date: 2013-06-27 10:48+0000\n"
"Last-Translator: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>\n"
"Language-Team: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>\n"
-"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"Language: zh_CN\n"
"Generated-By: pygettext.py 1.5\n"
"X-Poedit-Country: CHINA\n"
"X-Poedit-Language: Chinese\n"
@@ -408,6 +408,9 @@ msgid ""
"to continue? "
msgstr "你的磁盘将会格式化,磁盘上的数据会丢失,你确定要继续吗?"
+msgid "Please choose"
+msgstr "请选择"
+
msgid "Log out"
msgstr "登出"
@@ -457,9 +460,18 @@ msgstr "存储池的路径.每个存储池的路径是唯一的。"
msgid "NFS server IP"
msgstr "NFS服务器IP"
+msgid "I want to input the server myself."
+msgstr "我想自己输入服务器"
+
+msgid "I want to choose a server I used before."
+msgstr "我想选择一个我曾经用过的服务器"
+
msgid "NFS server IP or hostname. It should not be empty."
msgstr "NFS服务器IP或者主机名, 不能为空。"
+msgid "Please choose the nfs server you want to create storage pool."
+msgstr "请选择想要创建存储池的服务器"
+
msgid "NFS Path"
msgstr "NFS 路径"
--
1.7.1
1
0
Hi, Adam and Aline
For my workitem below in sprint one, as I am not sure whether the title
is exactly reflecting the content, I want to clarify the scope of it.
Originally, we have discussed our strategy to leverage jquery ui and we
got below initiatives.
1. Make use of jquery ui widgets wherever it is possible in kimchi.
2. For any reusable common UI component in Kimchi, make it a jquery
widget.
Whether this workitem is just the one to execute our jquery-ui strategy
above or it means some special content for some other narrow-defined scope?
2
1
23 Dec '13
This patch disables the "undefine" button if the storagepool is the
'default'.
Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo(a)linux.vnet.ibm.com>
---
ui/js/src/kimchi.storage_main.js | 2 ++
1 file changed, 2 insertions(+)
diff --git a/ui/js/src/kimchi.storage_main.js b/ui/js/src/kimchi.storage_main.js
index 169e32a..70ea9a3 100644
--- a/ui/js/src/kimchi.storage_main.js
+++ b/ui/js/src/kimchi.storage_main.js
@@ -113,6 +113,8 @@ kimchi.storageBindClick = function() {
var deleteButton = storage_action.find('.pool-delete');
if ('active' === deleteButton.data('stat')) {
deleteButton.attr('disabled', 'disabled');
+ } else if ('default' === $(this).data('name')) {
+ deleteButton.attr('disabled', 'disabled');
} else {
deleteButton.removeAttr('disabled');
}
--
1.8.1.4
2
2
From: Aline Manera <alinefm(a)br.ibm.com>
This patch cleans up pep8 style issue in auth.py
Signed-off-by: Aline Manera <alinefm(a)br.ibm.com>
---
Makefile.am | 1 +
src/kimchi/auth.py | 10 ++++++----
2 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index e57d3b6..7770ba7 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -39,6 +39,7 @@ EXTRA_DIST = \
# So it will be checked from now on.
PEP8_WHITELIST = \
src/kimchi/asynctask.py \
+ src/kimchi/auth.py \
src/kimchi/config.py.in \
src/kimchi/disks.py \
src/kimchi/server.py \
diff --git a/src/kimchi/auth.py b/src/kimchi/auth.py
index b665f21..f38d3d6 100644
--- a/src/kimchi/auth.py
+++ b/src/kimchi/auth.py
@@ -20,7 +20,7 @@
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import base64
import cherrypy
@@ -53,9 +53,9 @@ def authenticate(username, password, service="passwd"):
elif qtype == PAM.PAM_PROMPT_ERROR_MSG:
cherrypy.log.error_log.error("PAM authenticate prompt error "
"message: %s" % query)
- resp.append(('', 0));
+ resp.append(('', 0))
elif qtype == PAM.PAM_PROMPT_TEXT_INFO:
- resp.append(('', 0));
+ resp.append(('', 0))
else:
return None
return resp
@@ -72,6 +72,7 @@ def authenticate(username, password, service="passwd"):
return True
+
def from_browser():
# Enable Basic Authentication for REST tools.
# Ajax request sent from jQuery in browser will have "X-Requested-With"
@@ -79,6 +80,7 @@ def from_browser():
requestHeader = cherrypy.request.headers.get("X-Requested-With", None)
return (requestHeader == "XMLHttpRequest")
+
def check_auth_session():
"""
A user is considered authenticated if we have an established session open
@@ -99,7 +101,7 @@ def check_auth_httpba():
"""
REST API users may authenticate with HTTP Basic Auth. This is not allowed
for the UI because web browsers would cache the credentials and make it
- impossible for the user to log out without closing their browser completely.
+ impossible for the user to log out without closing their browser completely
"""
if from_browser() or not template.can_accept('application/json'):
return False
--
1.7.10.4
2
3
From: Aline Manera <alinefm(a)br.ibm.com>
This patch cleans up pep8 style issue in cachebust.py
Signed-off-by: Aline Manera <alinefm(a)br.ibm.com>
---
Makefile.am | 1 +
src/kimchi/cachebust.py | 4 ++--
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index e57d3b6..2c390c4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -39,6 +39,7 @@ EXTRA_DIST = \
# So it will be checked from now on.
PEP8_WHITELIST = \
src/kimchi/asynctask.py \
+ src/kimchi/cachebust.py \
src/kimchi/config.py.in \
src/kimchi/disks.py \
src/kimchi/server.py \
diff --git a/src/kimchi/cachebust.py b/src/kimchi/cachebust.py
index 4dcfb56..9a71f4f 100644
--- a/src/kimchi/cachebust.py
+++ b/src/kimchi/cachebust.py
@@ -18,7 +18,7 @@
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import os
@@ -28,6 +28,6 @@ from kimchi.config import get_prefix
def href(url):
# for error.html, url is absolute path
- f = os.path.join(get_prefix(),'ui', url.lstrip("/"))
+ f = os.path.join(get_prefix(), 'ui', url.lstrip("/"))
mtime = os.path.getmtime(f)
return "%s?cacheBust=%s" % (url, mtime)
--
1.7.10.4
2
2
[PATCH V4 (rebase) ] Bug fix: Kimchi will try to create directory for 'DIR' storagepool
by Rodrigo Trujillo 23 Dec '13
by Rodrigo Trujillo 23 Dec '13
23 Dec '13
When you create a 'DIR' storagepool, kimchi does not check if the
path exists, returning errors when you try to activate the SP.
This path makes kimchi check the path and created the directory,
if it does not exist.
Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo(a)linux.vnet.ibm.com>
---
src/kimchi/model.py | 8 ++++----
ui/pages/storagepool-add.html.tmpl | 4 +++-
2 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/src/kimchi/model.py b/src/kimchi/model.py
index d5d0dd8..a6790b8 100644
--- a/src/kimchi/model.py
+++ b/src/kimchi/model.py
@@ -1009,16 +1009,16 @@ class Model(object):
return name
pool = conn.storagePoolDefineXML(xml, 0)
- if params['type'] == 'logical':
- pool.setAutostart(1)
+ if params['type'] in ['logical', 'dir']:
pool.build(libvirt.VIR_STORAGE_POOL_BUILD_NEW)
- if params['type'] == 'dir':
- # autostart dir storage pool created from kimchi
+ # autostart dir and logical storage pool created from kimchi
pool.setAutostart(1)
else:
# disable autostart for others
pool.setAutostart(0)
except libvirt.libvirtError as e:
+ msg = "Problem creating Storage Pool: %s"
+ kimchi_log.error(msg, e)
raise OperationFailed(e.get_error_message())
return name
diff --git a/ui/pages/storagepool-add.html.tmpl b/ui/pages/storagepool-add.html.tmpl
index 3ab9ae5..5a2dd45 100644
--- a/ui/pages/storagepool-add.html.tmpl
+++ b/ui/pages/storagepool-add.html.tmpl
@@ -63,6 +63,8 @@
<div class="field">
<p class="text-help">
$_("The path of the Storage Pool. Each Storage Pool must have a unique path.")</p>
+ <p class="text-help">
+ $_("Kimchi will try to create the directory when it does not already exist in your system.")</p>
<input id="pathId" type="text" class="text" style="width: 300px">
</div>
<div class="clear"></div>
@@ -116,4 +118,4 @@
</div>
</script>
</body>
-</html>
\ No newline at end of file
+</html>
--
1.8.1.4
2
2
Hi all,
continuing the discussion:
So, what to do ?
Create/Enable SCSI StoragePool ?, then list FC devices on it ?
############## from previous emails ################
- Fibre Channel is used as a subtype of SCSI
- FC LUNs will be mapped as /dev/sdX devices
- Those devices can be used as PV in a VG , or as a Logical StoragePool
- LUNs can also be mapped as
/dev/disk/by-id/wwn-0x600507680280865df800000000000060
Links:
http://wiki.libvirt.org/page/NPIV_in_libvirt#NPIV_in_libvirt
http://libvirt.org/formatstorage.html (search for 'fc_host')
##############################################
3
2
Re: [Kimchi-devel] [project-kimchi][PATCHv3 3/5] Storage server: Update API.md
by Royce Lv 23 Dec '13
by Royce Lv 23 Dec '13
23 Dec '13
On 2013年12月06日 22:30, Mark Wu wrote:
> On 12/06/2013 06:12 PM, lvroyce(a)linux.vnet.ibm.com wrote:
>> From: Royce Lv <lvroyce(a)linux.vnet.ibm.com>
>>
>> Update API.md to specify storage server api.
>>
>> Signed-off-by: Royce Lv <lvroyce(a)linux.vnet.ibm.com>
>> ---
>> docs/API.md | 13 +++++++++++++
>> 1 file changed, 13 insertions(+)
>>
>> diff --git a/docs/API.md b/docs/API.md
>> index f60d0b0..42a5cd5 100644
>> --- a/docs/API.md
>> +++ b/docs/API.md
>> @@ -436,6 +436,19 @@ creation.
>> not tested yet
>> * **POST**: *See Configuration Actions*
>>
>> +**Actions (POST):**
>> +
>> +*No actions defined*
>> +
>> +### Collection: Storage Servers
>> +
>> +**URI:** /storageservers
>> +
>> +**Methods:**
>> +
>> +* **GET**: Retrieve a summarized list of used storage servers.
>> + * type: Filter server list with given type, currently support 'netfs'.
> You could claim that it's a param in url. And it seems it's
> inconsistent with what you define in API.json for it. type is better
> for me.
I will fix inconsistency between API.json and doc. For GET request its
param cannot put in body, and just in url.
>> +
>> ### Collection: Distros
>>
>> **URI:** /config/distros
>
1
0
Re: [Kimchi-devel] [project-kimchi] [PATCH v1 0/4] Stroage Pool: refactor and add iSCSI support
by Sheldon 23 Dec '13
by Sheldon 23 Dec '13
23 Dec '13
wow. cool, Bing Bu.
You should send out this patch easily, then we can avoid so many
repetitions work.
To Aline:
Bing Bu not only implement iscsi and nfs pool, but also implement a test
case for iscsi and nfs pool.
On 12/20/2013 06:54 AM, Bing Bu Cao wrote:
> On 12/16/2013 04:01 PM, Zhou Zheng Sheng wrote:
>> Hi all!
>>
>> I'm implementing iSCSI storage pool support in kimchi. I found that the
>> current design and implementation is not very flexible to add new
>> type of
>> pool.
>>
>> Firstly it uses "if ... elif" to inspect the requested pool type and
>> go into
>> the corresponding branch and create XML. This could be turn into a
>> dispatching
>> dict, with the pool type as the key, and XML generating function as
>> the value.
>>
>> Secondly I think a few arguments for various type of pool can be
>> re-used if
>> they are for the same purpose. For example, both NFS pool and iSCSI
>> pool need
>> a remote host argument, so I renamed the "nfsserver" to "srcHost",
>> and use
>> "srcHost" in both NFS and iSCSI pool. I also change the relative
>> front end
>> code.
>>
>> After the refactoring, I add the iSCSI support. I've tested all the
>> code on
>> Fedora 19 by defining various types of storage pool with various
>> argument.
>>
>> Zhou Zheng Sheng (4):
>> storagepool: dispatch _get_pool_xml() to respective
>> _get_XXX_storagepool_xml() function
>> storagepool: rename and consolidate arguments of creating (back end)
>> storagepool: rename and consolidate arguments of creating (front end)
>> storagepool: Support Creating iSCSI storagepool in model.py
>>
>> docs/API.md | 21 ++--
>> src/kimchi/model.py | 158
>> +++++++++++++++++++++----------
>> ui/js/src/kimchi.storagepool_add_main.js | 9 +-
>> 3 files changed, 128 insertions(+), 60 deletions(-)
>>
> Before I leave kimchi, I written one patch set which refactoring the
> storage pool part.
> But Adam did not allow me to send this patch out because he thought it
> is not important at that moment.
> Whatever, I hope this can give you some other ideas for storage pool
> refactoring. But I am afraid you should make a big rebase because they
> are really so old...
>
>From 6cd5a7900a4f9794c97a1cf86f32128f046bf526 Mon Sep 17 00:00:00 2001
From: Bing Bu Cao <mars(a)linux.vnet.ibm.com>
Date: Thu, 22 Aug 2013 10:00:00 +0800
Subject: [PATCH 1/4] Generate the storage object XML in storage.py
Signed-off-by: Bing Bu Cao <mars(a)linux.vnet.ibm.com>
---
src/kimchi/Makefile.am | 1 +
src/kimchi/storage.py | 168
++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 169 insertions(+), 0 deletions(-)
create mode 100644 src/kimchi/storage.py
diff --git a/src/kimchi/Makefile.am b/src/kimchi/Makefile.am
index c5cf5cc..b4e92d4 100644
--- a/src/kimchi/Makefile.am
+++ b/src/kimchi/Makefile.am
@@ -36,6 +36,7 @@ kimchi_PYTHON = \
screenshot.py \
server.py \
sslcert.py \
+ storage.py \
template.py \
vmtemplate.py \
vnc.py \
diff --git a/src/kimchi/storage.py b/src/kimchi/storage.py
new file mode 100644
index 0000000..1114756
--- /dev/null
+++ b/src/kimchi/storage.py
@@ -0,0 +1,168 @@
+#
+# Project Burnet
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Bing Bu Cao <mars(a)linux.vnet.ibm.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
+
+import copy
+import os
+from kimchi.exception import *
+
+STOR_TYPE_POOL = 'pool'
+STOR_TYPE_VOLUME = 'volume'
+STOR_TYPE_LIST = [STOR_TYPE_POOL, STOR_TYPE_VOLUME]
+
+DIR = "dir"
+NETFS = "netfs"
+ISCSI = "iscsi"
+POOL_TYPE_LIST = [DIR, NETFS, ISCSI]
+
+DEFAULT_DIR_TARGET_BASE = "/var/lib/libvirt/images/"
+DEFAULT_SCSI_TARGET = "/dev/disk/by-path"
+
+
+class storage(object):
+ def __init__(self, storage_type, pool_type=None, **kwargs):
+ """
+ @param storage_type: 'pool' or 'volume'
+ @param pool_type: 'pool' or 'volume'
+ @param kwargs:
+ """
+ if storage_type not in STOR_TYPE_LIST:
+ raise InvalidParameter(
+ "Unknown Storage Type: %s" % storage_type)
+ if storage_type == 'volume' and pool_type not in POOL_TYPE_LIST:
+ raise InvalidParameter(
+ "Invalid Pool Type: %s" % pool_type)
+
+ self.pool_type = pool_type
+ self.params = copy.copy(kwargs)
+ self.type = storage_type
+
+ def dumpxml(self):
+ return getattr(self, "_get_%s_xml" % self.type)(self.pool_type)
+
+ def _get_pool_xml(self, pool_type):
+ return getattr(self, "_get_%(type)s_pool_xml" % self.params)()
+
+ def _get_dir_pool_xml(self):
+ # Required parameters
+ # name:
+ # type:
+ # Optional parameters
+ # path:
+ kwargs = self.params
+ kwargs.setdefault('path', DEFAULT_DIR_TARGET_BASE)
+ xml = """
+ <pool type='%(type)s'>
+ <name>%(name)s</name>
+ <target>
+ <path>%(path)s</path>
+ </target>
+ </pool>
+ """ % kwargs
+ return xml
+
+ def _get_netfs_pool_xml(self):
+ # Required parameters
+ # name:
+ # type:
+ # Optional parameters
+ # path:
+ kwargs = self.params
+ formats = ["auto", "nfs", "glusterfs"]
+ kwargs.setdefault('path', os.path.join(DEFAULT_DIR_TARGET_BASE,
+ kwargs['name']))
+ kwargs.setdefault('format', "auto")
+ format = kwargs['format']
+ if not format in formats:
+ raise InvalidParameter(
+ "Unknown Network Filesystem format: %s" %
format)
+ xml = """
+ <pool type='%(type)s'>
+ <name>%(name)s</name>
+ <source>
+ <host name='%(host)s'/>
+ <dir path='%(src_path)s'/>
+ <format type='%(format)s'/>
+ </source>
+ <target>
+ <path>%(path)s</path>
+ </target>
+ </pool>
+ """ % kwargs
+ return xml
+
+ def _get_iscsi_pool_xml(self):
+ # Required parameters
+ # name:
+ # type:
+ # Optional parameters
+ # path:
+ kwargs = self.params
+ kwargs.setdefault('path', DEFAULT_SCSI_TARGET)
+ xml = """
+ <pool type='%(type)s'>
+ <name>%(name)s</name>
+ <source>
+ <host name='%(host)s'/>
+ <device path='%(src_path)s'/>
+ </source>
+ <target>
+ <path>%(path)s</path>
+ </target>
+ </pool>
+ """ % kwargs
+ return xml
+
+ def _get_volume_xml(self, pool_type):
+ # Required parameters
+ # name:
+ # capacity:
+ # path:
+ # Optional parameters
+ # allocation:
+ # format(not used for iscsi pool):
+ if pool_type == ISCSI:
+ raise InvalidOperation(
+ "Creating volume is not supported for iscsi pool")
+ kwargs = self.params
+ kwargs.setdefault('allocation', 0)
+ kwargs.setdefault('format', 'qcow2')
+ target = self._get_vol_target_xml(kwargs.get('format'))
+ kwargs.update({'target': target})
+ xml = """
+ <volume>
+ <name>%(name)s</name>
+ <allocation unit="MiB">%(allocation)s</allocation>
+ <capacity unit="MiB">%(capacity)s</capacity>
+ <source>
+ </source>%(target)s
+ </volume>
+ """ % kwargs
+ return xml
+
+ def _get_vol_target_xml(self, format):
+ target_xml = ""
+ if self.pool_type == DIR:
+ target_xml = """
+ <target>
+ <format type='%s'/>
+ </target>""" % format
+ return target_xml
--
1.7.7.6
>From 93dd8c5d78706d7444ad54cf52845812aa8d7173 Mon Sep 17 00:00:00 2001
From: Bing Bu Cao <mars(a)linux.vnet.ibm.com>
Date: Thu, 22 Aug 2013 10:02:02 +0800
Subject: [PATCH 2/4] Use new xml generator in model for StoragePool and
StorageVolume operation
Signed-off-by: Bing Bu Cao <mars(a)linux.vnet.ibm.com>
---
src/kimchi/model.py | 53
+++++++-------------------------------------------
1 files changed, 8 insertions(+), 45 deletions(-)
diff --git a/src/kimchi/model.py b/src/kimchi/model.py
index f05b532..670fb7e 100644
--- a/src/kimchi/model.py
+++ b/src/kimchi/model.py
@@ -54,6 +54,7 @@ from kimchi.exception import *
from kimchi.utils import kimchi_log, is_digit
from kimchi.distroloader import DistroLoader
+from kimchi.storage import storage
ISO_POOL_NAME = u'kimchi_isos'
STATS_INTERVAL = 5
@@ -581,7 +582,8 @@ class Model(object):
name = params['name']
if name in (ISO_POOL_NAME, ):
raise InvalidOperation("StoragePool already exists")
- xml = _get_pool_xml(**params)
+ storpool = storage('pool', **params)
+ xml = storpool.dumpxml()
except KeyError, key:
raise MissingParameter(key)
if name in self.storagepools_get_list():
@@ -689,10 +691,13 @@ class Model(object):
raise
def storagevolumes_create(self, pool, params):
- info = self.storagepool_lookup(pool)
+ pool_obj = self._get_storagepool(pool)
+ pooltype = xmlutils.xpath_get_text(pool_obj.XMLDesc(0),
+ "/pool/@type")[0]
try:
name = params['name']
- xml = _get_volume_xml(**params)
+ storvol = storage('volume', pool_type=pooltype, **params)
+ xml = storvol.dumpxml()
except KeyError, key:
raise MissingParameter(key)
pool = self._get_storagepool(pool)
@@ -826,48 +831,6 @@ class LibvirtVMScreenshot(VMScreenshot):
os.close(fd)
-def _get_pool_xml(**kwargs):
- # Required parameters
- # name:
- # type:
- # path:
- xml = """
- <pool type='%(type)s'>
- <name>%(name)s</name>
- <target>
- <path>%(path)s</path>
- </target>
- </pool>
- """ % kwargs
- return xml
-
-
-def _get_volume_xml(**kwargs):
- # Required parameters
- # name:
- # capacity:
- #
- # Optional:
- # allocation:
- # format:
- kwargs.setdefault('allocation', 0)
- kwargs.setdefault('format', 'qcow2')
-
- xml = """
- <volume>
- <name>%(name)s</name>
- <allocation unit="MiB">%(allocation)s</allocation>
- <capacity unit="MiB">%(capacity)s</capacity>
- <source>
- </source>
- <target>
- <format type='%(format)s'/>
- </target>
- </volume>
- """ % kwargs
- return xml
-
-
class LibvirtConnection(object):
def __init__(self, uri):
self.uri = uri
--
1.7.7.6
>From 21433a52d1b135d696e187d0e29b68d5954749f5 Mon Sep 17 00:00:00 2001
From: Bing Bu Cao <mars(a)linux.vnet.ibm.com>
Date: Thu, 22 Aug 2013 10:03:39 +0800
Subject: [PATCH 3/4] Add testcases in test_model.py to test the ISCSI
and NFS
storagepool
Signed-off-by: Bing Bu Cao <mars(a)linux.vnet.ibm.com>
---
tests/test_model.py | 123
+++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 123 insertions(+), 0 deletions(-)
diff --git a/tests/test_model.py b/tests/test_model.py
index dcabbcb..3f6a10e 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -26,6 +26,10 @@ import threading
import os
import time
import tempfile
+import shlex
+import subprocess
+import random
+import shutil
import kimchi.model
import kimchi.objectstore
@@ -256,6 +260,125 @@ class ModelTests(unittest.TestCase):
for key in params.keys():
self.assertEquals(params[key], info[key])
+ def _execute_cmd(self, cmd):
+ print cmd
+ args = shlex.split(cmd)
+ p = subprocess.Popen(args, close_fds=True, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out, err = p.communicate()
+ print out, err
+
+ def _create_iscsi_target(self, path, iqn, capacity):
+ self._execute_cmd("dd if=/dev/zero of=%s bs=1M count=%i" %
(path, capacity))
+ conf = """
+ <target %s>
+ backing-store %s
+ </target>
+ """ % (iqn, path)
+ conf_file = '/etc/tgt/targets.conf'
+ with open(conf_file, 'a') as file:
+ file.write(conf)
+
+ self._execute_cmd("tgt-admin --update %s" % iqn)
+
+ def _get_target(self, iqn):
+ self._execute_cmd("tgt-admin --show")
+
+ def _clean_iscsi_target(self, iqn):
+ self._execute_cmd("tgt-admin --delete %s" % iqn)
+ #remove the lines added before
+ conf = open('/etc/tgt/targets.conf')
+ lines = conf.readlines()
+ conf.close()
+ conf = open('/etc/tgt/targets.conf', 'w')
+ lines = lines[:-4]
+ conf.writelines(lines)
+ conf.close()
+ self._execute_cmd("exportfs -rf")
+
+ @unittest.skipUnless(utils.running_as_root(), 'Must be run as root')
+ def test_iscsi_pool(self):
+ inst = kimchi.model.Model('qemu:///system', self.tmp_store)
+ path = '/srv/images'
+ name = 'iscsi-test-pool'
+ img = 'kimchi-%s.img' % random.getrandbits(16)
+ iqn = 'iqn.2013.08.org.foo.bar:%s' % random.getrandbits(16)
+ cap = random.getrandbits(8)
+ if not os.path.exists(path):
+ os.makedirs(path)
+
+ self._create_iscsi_target(os.path.join(path, img), iqn, cap)
+ self._get_target(iqn)
+
+ with utils.RollbackContext() as rollback:
+ args = {'name': name,
+ 'type': 'iscsi',
+ 'host': '127.0.0.1',
+ 'src_path': iqn}
+ inst.storagepools_create(args)
+ rollback.prependDefer(inst.storagepool_delete, name)
+ inst.storagepool_activate(name)
+ rollback.prependDefer(inst.storagepool_deactivate, name)
+ poolinfo = inst.storagepool_lookup(name)
+ self.assertEquals(cap, poolinfo['capacity'])
+
+ self._clean_iscsi_target(iqn)
+ shutil.rmtree(path)
+
+ def _create_nfs(self, path):
+ conf = '%s 127.0.0.1(rw,sync,root_squash)\n' % path
+ conf_file = '/etc/exports'
+ with open(conf_file, 'a') as file:
+ file.write(conf)
+
+ self._execute_cmd("exportfs -rv")
+
+ def _clean_nfs(self, path):
+ #remove the line added in _create_nfs()
+ conf = open('/etc/exports')
+ output = []
+ for line in conf:
+ if not path in line:
+ output.append(line)
+ conf.close()
+ conf = open('/etc/exports', 'w')
+ conf.writelines(output)
+ conf.close()
+ self._execute_cmd("exportfs -rf")
+
+ @unittest.skipUnless(utils.running_as_root(), 'Must be run as root')
+ def test_nfs_pool(self):
+ inst = kimchi.model.Model('qemu:///system', self.tmp_store)
+ src_path = '/tmp/nfs-%s' % random.getrandbits(16)
+ src_file = 'kimchi-%s.img' % random.getrandbits(16)
+ name = "pool-nfs-test"
+ cap = 32 * 1024 * 1024
+ if not os.path.exists(src_path):
+ os.makedirs(src_path)
+ fd = open(os.path.join(src_path, src_file), 'w')
+ fd.truncate(cap)
+ self._create_nfs(src_path)
+
+ dst_path = '/mnt/nfs'
+ if not os.path.exists(dst_path):
+ os.makedirs(dst_path)
+ with utils.RollbackContext() as rollback:
+ args = {"type": "netfs",
+ "name": name,
+ "host": "127.0.0.1",
+ "format": "nfs",
+ "src_path": src_path,
+ "path": dst_path}
+ inst.storagepools_create(args)
+ rollback.prependDefer(inst.storagepool_delete, name)
+ inst.storagepool_activate(name)
+ rollback.prependDefer(inst.storagepool_deactivate, name)
+ volinfo = inst.storagevolume_lookup(name, src_file)
+ self.assertEquals(cap >> 20, volinfo['capacity'])
+
+ self._clean_nfs(src_path)
+ shutil.rmtree(src_path)
+
def test_multithreaded_connection(self):
def worker():
for i in xrange(100):
--
1.7.7.6
>From 74c089a3a8174e16f2b03795b5c759334f4ce2ec Mon Sep 17 00:00:00 2001
From: Bing Bu Cao <mars(a)linux.vnet.ibm.com>
Date: Thu, 22 Aug 2013 10:17:00 +0800
Subject: [PATCH 4/4] Redefine the storagepool 'POST' description in API.md
Signed-off-by: Bing Bu Cao <mars(a)linux.vnet.ibm.com>
---
docs/API.md | 6 +++++-
1 files changed, 5 insertions(+), 1 deletions(-)
diff --git a/docs/API.md b/docs/API.md
index 0c187e0..73d3396 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -163,8 +163,12 @@ Represents a snapshot of the Virtual Machine's
primary monitor.
* **GET**: Retrieve a summarized list of all defined Storage Pools
* **POST**: Create a new Storage Pool
* name: The name of the Storage Pool
- * path: The path of the defined Storage Pool
+ * path: The target path of the defined Storage Pool
+ * src_path: The source path of the defined Storage pool
* type: The type of the defined Storage Pool
+ * host: The remote server which provide the pool source
+ Only and necessary for netfs and iscsi storage pool
+ * format: The format of the pool, only valid for netfs storage pool
### Resource: Storage Pool
--
1.7.7.6
--
Sheldon Feng(冯少合)<shaohef(a)linux.vnet.ibm.com>
IBM Linux Technology Center
1
0
Follow this rule:
1) Import common modules
import ...
import ...
from ... import ...
from ... import ...
2) Import kimchi modules
import kimchi.<mod>
import kimchi.<mod>
from kimchi import ...
from kimchi import ...
Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo(a)linux.vnet.ibm.com>
---
plugins/sample/__init__.py | 8 ++++++--
plugins/sample/model.py | 2 +-
src/kimchi/model.py | 4 ++--
src/kimchi/server.py | 2 +-
src/kimchi/sslcert.py | 2 +-
src/kimchi/websocket.py | 17 ++++++++++++++---
src/kimchi/websockify.py | 15 +++++++++++++--
tests/test_exception.py | 6 ++++--
tests/test_mockmodel.py | 5 +++--
tests/test_model.py | 18 ++++++++++--------
tests/test_networkxml.py | 4 +++-
tests/test_osinfo.py | 3 +++
tests/test_plugin.py | 4 +++-
tests/test_rest.py | 10 +++++++---
tests/test_server.py | 4 ++--
tests/test_vmtemplate.py | 2 ++
tests/utils.py | 15 +++++++++------
17 files changed, 84 insertions(+), 37 deletions(-)
diff --git a/plugins/sample/__init__.py b/plugins/sample/__init__.py
index a20f5e6..7064904 100644
--- a/plugins/sample/__init__.py
+++ b/plugins/sample/__init__.py
@@ -22,12 +22,16 @@
import json
import os
+
+
from cherrypy import expose
-from kimchi.controller import Resource, Collection
+
+
+from kimchi.controller import Collection, Resource
from model import Model
-model = Model()
+model = Model()
class Drawings(Resource):
def __init__(self):
diff --git a/plugins/sample/model.py b/plugins/sample/model.py
index f6da5d0..9a2f22f 100644
--- a/plugins/sample/model.py
+++ b/plugins/sample/model.py
@@ -20,7 +20,7 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-from kimchi.exception import NotFoundError, InvalidOperation
+from kimchi.exception import InvalidOperation, NotFoundError
class Model(object):
diff --git a/src/kimchi/model.py b/src/kimchi/model.py
index 73c18ac..1e4dac2 100644
--- a/src/kimchi/model.py
+++ b/src/kimchi/model.py
@@ -42,9 +42,9 @@ import time
import uuid
-from collections import defaultdict
from cherrypy.process.plugins import BackgroundTask
from cherrypy.process.plugins import SimplePlugin
+from collections import defaultdict
from xml.etree import ElementTree
@@ -69,7 +69,7 @@ from kimchi.networkxml import to_network_xml
from kimchi.objectstore import ObjectStore
from kimchi.scan import Scanner
from kimchi.screenshot import VMScreenshot
-from kimchi.utils import kimchi_log, is_digit, get_enabled_plugins
+from kimchi.utils import get_enabled_plugins, is_digit, kimchi_log
from kimchi.vmtemplate import VMTemplate
diff --git a/src/kimchi/server.py b/src/kimchi/server.py
index 6ff6fa0..114a3a0 100644
--- a/src/kimchi/server.py
+++ b/src/kimchi/server.py
@@ -33,7 +33,7 @@ from kimchi import config
from kimchi import model
from kimchi import mockmodel
from kimchi.root import Root
-from kimchi.utils import import_class, get_enabled_plugins
+from kimchi.utils import get_enabled_plugins, import_class
LOGGING_LEVEL = {"debug": logging.DEBUG,
diff --git a/src/kimchi/sslcert.py b/src/kimchi/sslcert.py
index 70441f2..529699d 100644
--- a/src/kimchi/sslcert.py
+++ b/src/kimchi/sslcert.py
@@ -28,7 +28,7 @@
import time
-from M2Crypto import X509, EVP, RSA, ASN1
+from M2Crypto import ASN1, EVP, RSA, X509
class SSLCert(object):
diff --git a/src/kimchi/websocket.py b/src/kimchi/websocket.py
index a98fc6d..945676d 100644
--- a/src/kimchi/websocket.py
+++ b/src/kimchi/websocket.py
@@ -16,9 +16,20 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
'''
-import os, sys, time, errno, signal, socket, traceback, select
-import array, struct
-from base64 import b64encode, b64decode
+import array
+import errno
+import os
+import select
+import signal
+import socket
+import struct
+import sys
+import time
+import traceback
+
+
+from base64 import b64decode, b64encode
+
# Imports that vary by python version
diff --git a/src/kimchi/websockify.py b/src/kimchi/websockify.py
index 1154d92..2857e7c 100755
--- a/src/kimchi/websockify.py
+++ b/src/kimchi/websockify.py
@@ -11,15 +11,26 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
'''
-import signal, socket, optparse, time, os, sys, subprocess
+import optparse
+import os
+import signal
+import socket
+import subprocess
+import sys
+import time
+
+
from select import select
-import websocket
try:
from urllib.parse import parse_qs, urlparse
except:
from cgi import parse_qs
from urlparse import urlparse
+
+import websocket
+
+
class WebSocketProxy(websocket.WebSocketServer):
"""
Proxy traffic to and from a WebSockets client to a normal TCP
diff --git a/tests/test_exception.py b/tests/test_exception.py
index 9b5355a..cb60995 100644
--- a/tests/test_exception.py
+++ b/tests/test_exception.py
@@ -20,14 +20,16 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-import unittest
-import os
import json
+import os
+import unittest
+
import kimchi.mockmodel
import kimchi.server
from utils import *
+
test_server = None
model = None
host = None
diff --git a/tests/test_mockmodel.py b/tests/test_mockmodel.py
index b819172..0830a24 100644
--- a/tests/test_mockmodel.py
+++ b/tests/test_mockmodel.py
@@ -20,16 +20,17 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-import unittest
import cherrypy
import json
import os
+import unittest
+
import kimchi.mockmodel
import kimchi.controller
-
from utils import *
+
#utils.silence_server()
test_server = None
model = None
diff --git a/tests/test_model.py b/tests/test_model.py
index fb7d6dd..07b68b1 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -21,21 +21,23 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-import unittest
-import threading
import os
-import time
-import tempfile
-import psutil
import platform
+import psutil
+import tempfile
+import threading
+import time
+import unittest
import uuid
+
+import iso_gen
import kimchi.model
import kimchi.objectstore
-from kimchi.exception import *
-from kimchi import netinfo
import utils
-import iso_gen
+from kimchi import netinfo
+from kimchi.exception import *
+
class ModelTests(unittest.TestCase):
def setUp(self):
diff --git a/tests/test_networkxml.py b/tests/test_networkxml.py
index 4eeeaa2..3073bce 100644
--- a/tests/test_networkxml.py
+++ b/tests/test_networkxml.py
@@ -20,10 +20,12 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+import ipaddr
import unittest
+
+
import kimchi.networkxml as nxml
from kimchi.xmlutils import xpath_get_text
-import ipaddr
class NetworkXmlTests(unittest.TestCase):
diff --git a/tests/test_osinfo.py b/tests/test_osinfo.py
index f92567d..1dcfdaf 100644
--- a/tests/test_osinfo.py
+++ b/tests/test_osinfo.py
@@ -21,8 +21,11 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import unittest
+
+
from kimchi.osinfo import *
+
class OSInfoTests(unittest.TestCase):
def test_default_lookup(self):
name, entry = lookup(None, None)
diff --git a/tests/test_plugin.py b/tests/test_plugin.py
index 20cc598..f12b11f 100644
--- a/tests/test_plugin.py
+++ b/tests/test_plugin.py
@@ -20,17 +20,19 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-import unittest
import json
import os
import sys
+import unittest
from functools import partial
+
import kimchi.mockmodel
import kimchi.server
import utils
from kimchi import config
+
test_server = None
model = None
host = None
diff --git a/tests/test_rest.py b/tests/test_rest.py
index 73b5243..61369c3 100644
--- a/tests/test_rest.py
+++ b/tests/test_rest.py
@@ -20,16 +20,20 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-import unittest
import json
-import time
import os
+import time
+import unittest
+
+
from functools import partial
+
import kimchi.mockmodel
import kimchi.server
-from utils import *
from kimchi.asynctask import AsyncTask
+from utils import *
+
test_server = None
model = None
diff --git a/tests/test_server.py b/tests/test_server.py
index 9bb0034..734a618 100644
--- a/tests/test_server.py
+++ b/tests/test_server.py
@@ -20,12 +20,12 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-import unittest
import json
import os
+import unittest
-import utils
+import utils
import kimchi.mockmodel
#utils.silence_server()
diff --git a/tests/test_vmtemplate.py b/tests/test_vmtemplate.py
index 81382c7..92c7385 100644
--- a/tests/test_vmtemplate.py
+++ b/tests/test_vmtemplate.py
@@ -23,9 +23,11 @@
import unittest
import uuid
+
from kimchi.vmtemplate import *
from kimchi.xmlutils import xpath_get_text
+
class VMTemplateTests(unittest.TestCase):
def test_minimal_construct(self):
fields = (('name', 'test'), ('os_distro', 'unknown'),
diff --git a/tests/utils.py b/tests/utils.py
index c114813..a7596e8 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -21,16 +21,19 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
-import httplib
+import base64
import cherrypy
-import threading
-import time
+import httplib
import os
-import sys
import socket
-from contextlib import closing
+import sys
+import threading
+import time
import unittest
-import base64
+
+
+from contextlib import closing
+
import kimchi.server
import kimchi.model
--
1.8.1.4
2
2
[PATCH V3] Bug fix: Kimchi will try to create directory for 'DIR' storagepool
by Rodrigo Trujillo 20 Dec '13
by Rodrigo Trujillo 20 Dec '13
20 Dec '13
When you create a 'DIR' storagepool, kimchi does not check if the
path exists, returning errors when you try to activate the SP.
This path makes kimchi check the path and created the directory,
if it does not exist.
Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo(a)linux.vnet.ibm.com>
---
src/kimchi/model.py | 8 ++++----
ui/pages/storagepool-add.html.tmpl | 4 +++-
2 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/src/kimchi/model.py b/src/kimchi/model.py
index 73c18ac..3a4e20e 100644
--- a/src/kimchi/model.py
+++ b/src/kimchi/model.py
@@ -1006,16 +1006,16 @@ class Model(object):
return name
pool = conn.storagePoolDefineXML(xml, 0)
- if params['type'] == 'logical':
- pool.setAutostart(1)
+ if params['type'] in ['logical', 'dir']:
pool.build(libvirt.VIR_STORAGE_POOL_BUILD_NEW)
- if params['type'] == 'dir':
- # autostart dir storage pool created from kimchi
+ # autostart dir and logical storage pool created from kimchi
pool.setAutostart(1)
else:
# disable autostart for others
pool.setAutostart(0)
except libvirt.libvirtError as e:
+ msg = "Problem creating Storage Pool: %s"
+ kimchi_log.error(msg, e)
raise OperationFailed(e.get_error_message())
return name
diff --git a/ui/pages/storagepool-add.html.tmpl b/ui/pages/storagepool-add.html.tmpl
index d7b046d..792744c 100644
--- a/ui/pages/storagepool-add.html.tmpl
+++ b/ui/pages/storagepool-add.html.tmpl
@@ -63,6 +63,8 @@
<div class="field">
<p class="text-help">
$_("The path of the Storage Pool. Each Storage Pool must have a unique path.")</p>
+ <p class="text-help">
+ $_("Kimchi will try to create the directory when it does not already exist in your system.")</p>
<input id="pathId" type="text" class="text" style="width: 300px">
</div>
<div class="clear"></div>
@@ -116,4 +118,4 @@
</div>
</script>
</body>
-</html>
\ No newline at end of file
+</html>
--
1.8.1.4
2
2
From: Aline Manera <alinefm(a)br.ibm.com>
All py, css, js, tmpl and json files created by us must use 4 space for
indentation.
Imported files must keep as they are (such as those for jquery, novnc)
This patch was generated by the following command:
find . -name \*.py -o -name \*.json -o -name \*.css -o -name \*.js -o \
-name \*.tmpl | xargs -I @ sed -i s/\\t/" "/g @
Then imported files had changes reverted.
Signed-off-by: Aline Manera <alinefm(a)br.ibm.com>
---
plugins/sample/API.json | 90 +++---
src/kimchi/API.json | 416 +++++++++++++--------------
ui/css/theme-default/button.css | 490 ++++++++++++++++----------------
ui/css/theme-default/circle.css | 8 +-
ui/css/theme-default/form.css | 26 +-
ui/css/theme-default/framework.css | 10 +-
ui/css/theme-default/guest-edit.css | 48 ++--
ui/css/theme-default/list.css | 244 ++++++++--------
ui/css/theme-default/login-window.css | 8 +-
ui/css/theme-default/message.css | 158 +++++-----
ui/css/theme-default/nav-tree.css | 104 +++----
ui/css/theme-default/navbar.css | 50 ++--
ui/css/theme-default/popover.css | 124 ++++----
ui/css/theme-default/reset.css | 28 +-
ui/css/theme-default/storage.css | 40 +--
ui/css/theme-default/template-edit.css | 58 ++--
ui/css/theme-default/template.css | 78 ++---
ui/css/theme-default/template_add.css | 288 +++++++++----------
ui/css/theme-default/template_list.css | 288 +++++++++----------
ui/css/theme-default/tile-check.css | 22 +-
ui/css/theme-default/toolbar.css | 42 +--
ui/css/theme-default/topbar.css | 166 +++++------
ui/css/theme-default/window.css | 140 ++++-----
ui/js/dev.main.js | 8 +-
ui/js/src/kimchi.template_main.js | 2 +-
ui/pages/guest-add.html.tmpl | 120 ++++----
ui/pages/guest.html.tmpl | 78 ++---
ui/pages/kimchi-ui.html.tmpl | 40 +--
ui/pages/report-add.html.tmpl | 52 ++--
ui/pages/storagepool-add.html.tmpl | 144 +++++-----
ui/pages/tabs/guests.html.tmpl | 42 +--
ui/pages/tabs/host.html.tmpl | 220 +++++++-------
ui/pages/template-edit.html.tmpl | 182 ++++++------
33 files changed, 1907 insertions(+), 1907 deletions(-)
diff --git a/plugins/sample/API.json b/plugins/sample/API.json
index 58b6162..58d0969 100644
--- a/plugins/sample/API.json
+++ b/plugins/sample/API.json
@@ -1,47 +1,47 @@
{
- "$schema": "http://json-schema.org/draft-03/schema#",
- "title": "Plugin Sample API",
- "description": "Json schema for Kimchi's Sample Plugin API",
- "type": "object",
- "properties": {
- "rectangles_create": {
- "type": "object",
- "properties": {
- "name": {
- "description": "The name of the new rectangle instance",
- "type": "string",
- "required": true
- },
- "length": {
- "$ref": "#/definitions/positiveNumber",
- "required": true
- },
- "width": {
- "$ref": "#/definitions/positiveNumber",
- "required": true
- }
- }
- }
- },
- "circles_create": {
- "type": "object",
- "properties": {
- "name": {
- "description": "The name of the new circle instance",
- "type": "string",
- "required": true
- },
- "radius": {
- "$ref": "#/definitions/positiveNumber",
- "required": true
- }
- }
- },
- "definitions": {
- "positiveNumber": {
- "type": "number",
- "minimum": 0,
- "exclusiveMinimum": true
- }
- }
+ "$schema": "http://json-schema.org/draft-03/schema#",
+ "title": "Plugin Sample API",
+ "description": "Json schema for Kimchi's Sample Plugin API",
+ "type": "object",
+ "properties": {
+ "rectangles_create": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "description": "The name of the new rectangle instance",
+ "type": "string",
+ "required": true
+ },
+ "length": {
+ "$ref": "#/definitions/positiveNumber",
+ "required": true
+ },
+ "width": {
+ "$ref": "#/definitions/positiveNumber",
+ "required": true
+ }
+ }
+ }
+ },
+ "circles_create": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "description": "The name of the new circle instance",
+ "type": "string",
+ "required": true
+ },
+ "radius": {
+ "$ref": "#/definitions/positiveNumber",
+ "required": true
+ }
+ }
+ },
+ "definitions": {
+ "positiveNumber": {
+ "type": "number",
+ "minimum": 0,
+ "exclusiveMinimum": true
+ }
+ }
}
diff --git a/src/kimchi/API.json b/src/kimchi/API.json
index e364bcd..7b90826 100644
--- a/src/kimchi/API.json
+++ b/src/kimchi/API.json
@@ -1,210 +1,210 @@
{
- "$schema": "http://json-schema.org/draft-03/schema#",
- "title": "Kimchi API",
- "description": "Json schema for Kimchi API",
- "type": "object",
- "properties": {
- "vms_create": {
- "type": "object",
- "properties": {
- "name": {
- "description": "The name of the new VM",
- "type": "string"
- },
- "template": {
- "description": "The URI of a template to use when building a VM",
- "type": "string",
- "pattern": "^/templates/[^/]+/?$",
- "required": true
- },
- "storagepool": {
- "description": "Assign a specefic Storage Pool to the new VM",
- "type": "string",
- "pattern": "^/storagepools/[^/]+/?$"
- }
- }
- },
- "vm_update": {
- "type": "object",
- "properties": {
- "name": {
- "description": "New name of VM",
- "type": "string",
- "minLength": 1
- }
- }
- },
- "networks_create": {
- "type": "object",
- "properties": {
- "name": {
- "description": "The name of the new network",
- "type": "string",
- "minLength": 1,
- "required": true
- },
- "connection": {
- "description": "Specifies how this network should be connected to the other networks",
- "type": "string",
- "pattern": "^isolated|nat|bridge$",
- "required": true
- },
- "subnet": {
- "description": "Network segment in slash-separated format with ip address and prefix or netmask",
- "type": "string"
- },
- "interface": {
- "description": "The name of a network interface on the host",
- "type": "string"
- }
- }
- },
- "templates_create": {
- "type": "object",
- "properties": {
- "name": {
- "description": "The name of the template",
- "type": "string",
- "pattern": "^[^ ]+( +[^ ]+)*$",
- "minLength": 1
- },
- "icon": {
- "description": "The template icon path",
- "type": "string",
- "pattern": "^images/"
- },
- "os_distro": {
- "description": "Distribution name of the Operating System",
- "type": "string",
- "minLength": 1
- },
- "os_version": {
- "description": "Version of the Operating System",
- "type": "string",
- "minLength": 1
- },
- "cpus": {
- "description": "Number of CPUs for the template",
- "type": "integer",
- "minimum": 1
- },
- "memory": {
- "description": "Memory (MB) for the template",
- "type": "integer",
- "minimum": 512
- },
- "cdrom": {
- "description": "Path for cdrom",
- "type": "string",
- "pattern": "^((/)|(http)[s]?:|[t]?(ftp)[s]?:)+.*([.]iso)$",
- "required": true
- },
- "disks": {
- "description": "List of disks",
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "index": {
- "description": "Index of the disk",
- "type": "integer",
- "minimum": 0
- },
- "size": {
- "description": "Size (GB) of the disk",
- "type": "number",
- "minimum": 1
- }
- }
- },
- "minItems": 1,
- "uniqueItems": true
- },
- "storagepool": {
- "description": "Location of the storage pool",
- "type": "string",
- "pattern": "^/storagepools/[^/]+/?$"
- },
- "folder": {
- "description": "Folder",
- "type": "array",
- "items": { "type": "string" }
- }
- },
- "additionalProperties": false
- },
- "template_update": {
- "type": "object",
- "properties": {
- "name": {
- "description": "The name of the template",
- "type": "string",
- "pattern": "^[^ ]+( +[^ ]+)*$",
- "minLength": 1
- },
- "icon": {
- "description": "The template icon path",
- "type": "string",
- "pattern": "^images/"
- },
- "os_distro": {
- "description": "Distribution name of the Operating System",
- "type": "string",
- "minLength": 1
- },
- "os_version": {
- "description": "Version of the Operating System",
- "type": "string",
- "minLength": 1
- },
- "cpus": {
- "description": "Number of CPUs for the template",
- "type": "integer",
- "minimum": 1
- },
- "memory": {
- "description": "Memory (MB) for the template",
- "type": "integer",
- "minimum": 512
- },
- "cdrom": {
- "description": "Path for cdrom",
- "type": "string",
- "pattern": "^((/)|(http)[s]?:|[t]?(ftp)[s]?:)+.*([.]iso)$"
- },
- "disks": {
- "description": "List of disks",
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "index": {
- "description": "Index of the disk",
- "type": "integer",
- "minimum": 0
- },
- "size": {
- "description": "Size (GB) of the disk",
- "type": "number",
- "minimum": 1
- }
- }
- },
- "minItems": 1,
- "uniqueItems": true
- },
- "storagepool": {
- "description": "Location of the storage pool",
- "type": "string",
- "pattern": "^/storagepools/[^/]+/?$"
- },
- "folder": {
- "description": "Folder",
- "type": "array",
- "items": { "type": "string" }
- }
- },
- "additionalProperties": false
- }
- }
+ "$schema": "http://json-schema.org/draft-03/schema#",
+ "title": "Kimchi API",
+ "description": "Json schema for Kimchi API",
+ "type": "object",
+ "properties": {
+ "vms_create": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "description": "The name of the new VM",
+ "type": "string"
+ },
+ "template": {
+ "description": "The URI of a template to use when building a VM",
+ "type": "string",
+ "pattern": "^/templates/[^/]+/?$",
+ "required": true
+ },
+ "storagepool": {
+ "description": "Assign a specefic Storage Pool to the new VM",
+ "type": "string",
+ "pattern": "^/storagepools/[^/]+/?$"
+ }
+ }
+ },
+ "vm_update": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "description": "New name of VM",
+ "type": "string",
+ "minLength": 1
+ }
+ }
+ },
+ "networks_create": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "description": "The name of the new network",
+ "type": "string",
+ "minLength": 1,
+ "required": true
+ },
+ "connection": {
+ "description": "Specifies how this network should be connected to the other networks",
+ "type": "string",
+ "pattern": "^isolated|nat|bridge$",
+ "required": true
+ },
+ "subnet": {
+ "description": "Network segment in slash-separated format with ip address and prefix or netmask",
+ "type": "string"
+ },
+ "interface": {
+ "description": "The name of a network interface on the host",
+ "type": "string"
+ }
+ }
+ },
+ "templates_create": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "description": "The name of the template",
+ "type": "string",
+ "pattern": "^[^ ]+( +[^ ]+)*$",
+ "minLength": 1
+ },
+ "icon": {
+ "description": "The template icon path",
+ "type": "string",
+ "pattern": "^images/"
+ },
+ "os_distro": {
+ "description": "Distribution name of the Operating System",
+ "type": "string",
+ "minLength": 1
+ },
+ "os_version": {
+ "description": "Version of the Operating System",
+ "type": "string",
+ "minLength": 1
+ },
+ "cpus": {
+ "description": "Number of CPUs for the template",
+ "type": "integer",
+ "minimum": 1
+ },
+ "memory": {
+ "description": "Memory (MB) for the template",
+ "type": "integer",
+ "minimum": 512
+ },
+ "cdrom": {
+ "description": "Path for cdrom",
+ "type": "string",
+ "pattern": "^((/)|(http)[s]?:|[t]?(ftp)[s]?:)+.*([.]iso)$",
+ "required": true
+ },
+ "disks": {
+ "description": "List of disks",
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "index": {
+ "description": "Index of the disk",
+ "type": "integer",
+ "minimum": 0
+ },
+ "size": {
+ "description": "Size (GB) of the disk",
+ "type": "number",
+ "minimum": 1
+ }
+ }
+ },
+ "minItems": 1,
+ "uniqueItems": true
+ },
+ "storagepool": {
+ "description": "Location of the storage pool",
+ "type": "string",
+ "pattern": "^/storagepools/[^/]+/?$"
+ },
+ "folder": {
+ "description": "Folder",
+ "type": "array",
+ "items": { "type": "string" }
+ }
+ },
+ "additionalProperties": false
+ },
+ "template_update": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "description": "The name of the template",
+ "type": "string",
+ "pattern": "^[^ ]+( +[^ ]+)*$",
+ "minLength": 1
+ },
+ "icon": {
+ "description": "The template icon path",
+ "type": "string",
+ "pattern": "^images/"
+ },
+ "os_distro": {
+ "description": "Distribution name of the Operating System",
+ "type": "string",
+ "minLength": 1
+ },
+ "os_version": {
+ "description": "Version of the Operating System",
+ "type": "string",
+ "minLength": 1
+ },
+ "cpus": {
+ "description": "Number of CPUs for the template",
+ "type": "integer",
+ "minimum": 1
+ },
+ "memory": {
+ "description": "Memory (MB) for the template",
+ "type": "integer",
+ "minimum": 512
+ },
+ "cdrom": {
+ "description": "Path for cdrom",
+ "type": "string",
+ "pattern": "^((/)|(http)[s]?:|[t]?(ftp)[s]?:)+.*([.]iso)$"
+ },
+ "disks": {
+ "description": "List of disks",
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "index": {
+ "description": "Index of the disk",
+ "type": "integer",
+ "minimum": 0
+ },
+ "size": {
+ "description": "Size (GB) of the disk",
+ "type": "number",
+ "minimum": 1
+ }
+ }
+ },
+ "minItems": 1,
+ "uniqueItems": true
+ },
+ "storagepool": {
+ "description": "Location of the storage pool",
+ "type": "string",
+ "pattern": "^/storagepools/[^/]+/?$"
+ },
+ "folder": {
+ "description": "Folder",
+ "type": "array",
+ "items": { "type": "string" }
+ }
+ },
+ "additionalProperties": false
+ }
+ }
}
diff --git a/ui/css/theme-default/button.css b/ui/css/theme-default/button.css
index e9354cf..c7ed3f6 100644
--- a/ui/css/theme-default/button.css
+++ b/ui/css/theme-default/button.css
@@ -21,57 +21,57 @@
/* Generated at http://colorzilla.com/gradient-editor/ */
.btn {
- display: inline-block;
- height: 42px;
- margin: 3px;
- vertical-align: top;
- border: 1px solid #aaa;
- -webkit-border-radius: 5px;
- -moz-border-radius: 5px;
- border-radius: 5px;
- -moz-box-sizing: content-box;
- box-sizing: content-box;
- box-shadow: -2px -2px 2px #eaeaea, 2px 2px 2px #fff, 3px 3px 3px white inset, -3px -3px 3px rgba(0, 0, 0, .25) inset;
- background: #ffffff;
- background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #e5e5e5));
- background: -webkit-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
- background: -o-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
- background: -ms-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
- background: linear-gradient(to bottom, #ffffff 0%, #e5e5e5 100%);
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e5e5e5', GradientType=0);
- line-height: 42px;
- color: #333;
- font-size: 13px;
- text-shadow: -1px -1px 1px #aaa, 1px 1px 1px #fff;
- text-align: center;
- text-overflow: ellipsis;
- white-space: nowrap;
- cursor: pointer;
+ display: inline-block;
+ height: 42px;
+ margin: 3px;
+ vertical-align: top;
+ border: 1px solid #aaa;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+ box-shadow: -2px -2px 2px #eaeaea, 2px 2px 2px #fff, 3px 3px 3px white inset, -3px -3px 3px rgba(0, 0, 0, .25) inset;
+ background: #ffffff;
+ background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #e5e5e5));
+ background: -webkit-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
+ background: -o-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
+ background: -ms-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
+ background: linear-gradient(to bottom, #ffffff 0%, #e5e5e5 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e5e5e5', GradientType=0);
+ line-height: 42px;
+ color: #333;
+ font-size: 13px;
+ text-shadow: -1px -1px 1px #aaa, 1px 1px 1px #fff;
+ text-align: center;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ cursor: pointer;
}
.btn:not([disabled]):hover {
- box-shadow: -2px -2px 2px #dadada, 2px 2px 2px #fff, 3px 3px 3px white inset, -3px -3px 3px rgba(0, 0, 0, .25) inset;
- background: #d5d5d5;
- background: -moz-linear-gradient(top, #d5d5d5 0%, #eeeeee 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #d5d5d5), color-stop(100%, #eeeeee));
- background: -webkit-linear-gradient(top, #d5d5d5 0%, #eeeeee 100%);
- background: -o-linear-gradient(top, #d5d5d5 0%, #eeeeee 100%);
- background: -ms-linear-gradient(top, #d5d5d5 0%, #eeeeee 100%);
- background: linear-gradient(to bottom, #d5d5d5 0%, #eeeeee 100%);
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#d5d5d5', endColorstr='#eeeeee', GradientType=0);
+ box-shadow: -2px -2px 2px #dadada, 2px 2px 2px #fff, 3px 3px 3px white inset, -3px -3px 3px rgba(0, 0, 0, .25) inset;
+ background: #d5d5d5;
+ background: -moz-linear-gradient(top, #d5d5d5 0%, #eeeeee 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #d5d5d5), color-stop(100%, #eeeeee));
+ background: -webkit-linear-gradient(top, #d5d5d5 0%, #eeeeee 100%);
+ background: -o-linear-gradient(top, #d5d5d5 0%, #eeeeee 100%);
+ background: -ms-linear-gradient(top, #d5d5d5 0%, #eeeeee 100%);
+ background: linear-gradient(to bottom, #d5d5d5 0%, #eeeeee 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#d5d5d5', endColorstr='#eeeeee', GradientType=0);
}
.btn:not([disabled]):active {
- box-shadow: -2px -2px 2px #eaeaea, 2px 2px 2px #fff, 3px 3px 3px rgba(0, 0, 0, .25) inset, -3px -3px 3px white inset;
- background: #ffffff;
- background: -moz-linear-gradient(top, #e5e5e5 0%, #ffffff 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #e5e5e5), color-stop(100%, #ffffff));
- background: -webkit-linear-gradient(top, #e5e5e5 0%, #ffffff 100%);
- background: -o-linear-gradient(top, #e5e5e5 0%, #ffffff 100%);
- background: -ms-linear-gradient(top, #e5e5e5 0%, #ffffff 100%);
- background: linear-gradient(to bottom, #e5e5e5 0%, #ffffff 100%);
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#e5e5e5', endColorstr='#ffffff', GradientType=0);
+ box-shadow: -2px -2px 2px #eaeaea, 2px 2px 2px #fff, 3px 3px 3px rgba(0, 0, 0, .25) inset, -3px -3px 3px white inset;
+ background: #ffffff;
+ background: -moz-linear-gradient(top, #e5e5e5 0%, #ffffff 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #e5e5e5), color-stop(100%, #ffffff));
+ background: -webkit-linear-gradient(top, #e5e5e5 0%, #ffffff 100%);
+ background: -o-linear-gradient(top, #e5e5e5 0%, #ffffff 100%);
+ background: -ms-linear-gradient(top, #e5e5e5 0%, #ffffff 100%);
+ background: linear-gradient(to bottom, #e5e5e5 0%, #ffffff 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#e5e5e5', endColorstr='#ffffff', GradientType=0);
}
.btn.loading {
@@ -84,321 +84,321 @@
}
.btn .text {
- padding: 0 10px;
+ padding: 0 10px;
}
.btn .icon {
- display: block;
- width: 42px;
- height: 42px;
+ display: block;
+ width: 42px;
+ height: 42px;
}
.btn.dropdown {
- text-align: left;
- position: relative;
- padding-right: 25px;
+ text-align: left;
+ position: relative;
+ padding-right: 25px;
}
.btn.dropdown .arrow {
- position: absolute;
- width: 15px;
- height: 42px;
- line-height: 42px;
- top: 0;
- right: 10px;
- background: url(../images/theme-default/arrow-down-black.png) no-repeat center center;
- right: 10px;
+ position: absolute;
+ width: 15px;
+ height: 42px;
+ line-height: 42px;
+ top: 0;
+ right: 10px;
+ background: url(../images/theme-default/arrow-down-black.png) no-repeat center center;
+ right: 10px;
}
/* Generated at http://colorzilla.com/gradient-editor/ */
.btn-tool {
- display: inline-block;
- height: 38px;
- margin: 6px 3px;
- vertical-align: top;
- -webkit-border-radius: 5px;
- -moz-border-radius: 5px;
- border-radius: 5px;
- -moz-box-sizing: content-box;
- box-sizing: content-box;
- box-shadow: -1px -1px 1px #777, 1px 1px 1px #eee, 2px 2px 2px #ddd inset, -3px -3px 3px rgba(0, 0, 0, .25) inset;
- background: #f3f3f3;
- background: -moz-linear-gradient(top, #f3f3f3 0%, #dddddd 50%, #d8d8d8 51%, #cccccc 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #f3f3f3), color-stop(50%, #dddddd),
- color-stop(51%, #d8d8d8), color-stop(100%, #cccccc));
- background: -webkit-linear-gradient(top, #f3f3f3 0%, #dddddd 50%, #d8d8d8 51%, #cccccc 100%);
- background: -o-linear-gradient(top, #f3f3f3 0%, #dddddd 50%, #d8d8d8 51%, #cccccc 100%);
- background: -ms-linear-gradient(top, #f3f3f3 0%, #dddddd 50%, #d8d8d8 51%, #cccccc 100%);
- background: linear-gradient(to bottom, #f3f3f3 0%, #dddddd 50%, #d8d8d8 51%, #cccccc 100%);
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3f3f3', endColorstr='#cccccc', GradientType=0);
- overflow: hidden;
- cursor: pointer;
+ display: inline-block;
+ height: 38px;
+ margin: 6px 3px;
+ vertical-align: top;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+ box-shadow: -1px -1px 1px #777, 1px 1px 1px #eee, 2px 2px 2px #ddd inset, -3px -3px 3px rgba(0, 0, 0, .25) inset;
+ background: #f3f3f3;
+ background: -moz-linear-gradient(top, #f3f3f3 0%, #dddddd 50%, #d8d8d8 51%, #cccccc 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #f3f3f3), color-stop(50%, #dddddd),
+ color-stop(51%, #d8d8d8), color-stop(100%, #cccccc));
+ background: -webkit-linear-gradient(top, #f3f3f3 0%, #dddddd 50%, #d8d8d8 51%, #cccccc 100%);
+ background: -o-linear-gradient(top, #f3f3f3 0%, #dddddd 50%, #d8d8d8 51%, #cccccc 100%);
+ background: -ms-linear-gradient(top, #f3f3f3 0%, #dddddd 50%, #d8d8d8 51%, #cccccc 100%);
+ background: linear-gradient(to bottom, #f3f3f3 0%, #dddddd 50%, #d8d8d8 51%, #cccccc 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3f3f3', endColorstr='#cccccc', GradientType=0);
+ overflow: hidden;
+ cursor: pointer;
}
.btn-tool:hover {
- box-shadow: -1px -1px 1px #777, 1px 1px 1px #eee, 2px 2px 2px rgba(0, 0, 0, .25) inset, -3px -3px 3px rgba(0, 0, 0, .25)
- inset;
+ box-shadow: -1px -1px 1px #777, 1px 1px 1px #eee, 2px 2px 2px rgba(0, 0, 0, .25) inset, -3px -3px 3px rgba(0, 0, 0, .25)
+ inset;
}
/* Generated at http://colorzilla.com/gradient-editor/ */
.btn-tool.left,.btn-tool.right {
- -webkit-border-radius: 0;
- -moz-border-radius: 0;
- border-radius: 0;
- background: #dddddd;
- background: -moz-linear-gradient(top, #dddddd 0%, #999999 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #dddddd), color-stop(100%, #999999));
- background: -webkit-linear-gradient(top, #dddddd 0%, #999999 100%);
- background: -o-linear-gradient(top, #dddddd 0%, #999999 100%);
- background: -ms-linear-gradient(top, #dddddd 0%, #999999 100%);
- background: linear-gradient(to bottom, #dddddd 0%, #999999 100%);
- filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#dddddd', endColorstr='#999999', GradientType=0);
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+ background: #dddddd;
+ background: -moz-linear-gradient(top, #dddddd 0%, #999999 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #dddddd), color-stop(100%, #999999));
+ background: -webkit-linear-gradient(top, #dddddd 0%, #999999 100%);
+ background: -o-linear-gradient(top, #dddddd 0%, #999999 100%);
+ background: -ms-linear-gradient(top, #dddddd 0%, #999999 100%);
+ background: linear-gradient(to bottom, #dddddd 0%, #999999 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#dddddd', endColorstr='#999999', GradientType=0);
}
.btn-tool:active,.btn-tool.active {
- box-shadow: -1px -1px 1px #777, 1px 1px 1px #eee, 3px 3px 3px rgba(0, 0, 0, .35) inset;
+ box-shadow: -1px -1px 1px #777, 1px 1px 1px #eee, 3px 3px 3px rgba(0, 0, 0, .35) inset;
}
.btn-tool.left {
- -webkit-border-top-left-radius: 5px;
- -moz-border-top-left-radius: 5px;
- border-top-left-radius: 5px;
- -webkit-border-bottom-left-radius: 5px;
- -moz-border-bottom-left-radius: 5px;
- border-bottom-left-radius: 5px;
+ -webkit-border-top-left-radius: 5px;
+ -moz-border-top-left-radius: 5px;
+ border-top-left-radius: 5px;
+ -webkit-border-bottom-left-radius: 5px;
+ -moz-border-bottom-left-radius: 5px;
+ border-bottom-left-radius: 5px;
}
.btn-tool.right {
- -webkit-border-top-right-radius: 5px;
- -moz-border-top-right-radius: 5px;
- border-top-right-radius: 5px;
- -webkit-border-bottom-right-radius: 5px;
- -moz-border-bottom-right-radius: 5px;
- border-bottom-right-radius: 5px;
+ -webkit-border-top-right-radius: 5px;
+ -moz-border-top-right-radius: 5px;
+ border-top-right-radius: 5px;
+ -webkit-border-bottom-right-radius: 5px;
+ -moz-border-bottom-right-radius: 5px;
+ border-bottom-right-radius: 5px;
}
.btn-tool .icon {
- display: block;
- width: 48px;
- height: 38px;
+ display: block;
+ width: 48px;
+ height: 38px;
}
.icon.reset {
- background: url(../images/theme-default/icon-reset.png) center center no-repeat;
+ background: url(../images/theme-default/icon-reset.png) center center no-repeat;
}
.icon.power-up {
- background: url(../images/theme-default/icon-power-up.png) center center no-repeat;
+ background: url(../images/theme-default/icon-power-up.png) center center no-repeat;
}
.icon.power-down {
- background: url(../images/theme-default/icon-power-down.png) center center no-repeat;
+ background: url(../images/theme-default/icon-power-down.png) center center no-repeat;
}
.icon.search {
- background: url(../images/theme-default/icon-search.png) no-repeat center center;
+ background: url(../images/theme-default/icon-search.png) no-repeat center center;
}
.icon.sort {
- background: url(../images/theme-default/icon-sort.png) no-repeat center center;
+ background: url(../images/theme-default/icon-sort.png) no-repeat center center;
}
.icon.design {
- background: url(../images/theme-default/icon-design.png) no-repeat center center;
+ background: url(../images/theme-default/icon-design.png) no-repeat center center;
}
.icon.list {
- background: url(../images/theme-default/icon-list.png) no-repeat center center;
+ background: url(../images/theme-default/icon-list.png) no-repeat center center;
}
.icon.detail {
- background: url(../images/theme-default/icon-detail.png) no-repeat center center;
+ background: url(../images/theme-default/icon-detail.png) no-repeat center center;
}
.icon.add {
- line-height: 32px;
- text-align: center;
- text-shadow: -1px -1px 1px #aaa, 1px 1px 1px #eee;
- font-size: 38px;
- font-weight: bold;
- color: #7cae0a;
+ line-height: 32px;
+ text-align: center;
+ text-shadow: -1px -1px 1px #aaa, 1px 1px 1px #eee;
+ font-size: 38px;
+ font-weight: bold;
+ color: #7cae0a;
}
.icon.tree {
- width: 42px;
- background: url(../images/theme-default/icon-tree.png) no-repeat center center;
+ width: 42px;
+ background: url(../images/theme-default/icon-tree.png) no-repeat center center;
}
/* Generated at http://colorzilla.com/gradient-editor/ */
.btn-tool.tree {
- width: 42px;
- margin: 5px 10px;
- background: #ffffff;
- background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #e5e5e5));
- background: -webkit-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
- background: -o-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
- background: -ms-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
- background: linear-gradient(to bottom, #ffffff 0%, #e5e5e5 100%);
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e5e5e5', GradientType=0);
- box-shadow: -1px -1px 1px #03385c, 1px 1px 1px #09F, 3px 3px 3px white inset, -3px -3px 3px rgba(0, 0, 0, .25) inset;
+ width: 42px;
+ margin: 5px 10px;
+ background: #ffffff;
+ background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #e5e5e5));
+ background: -webkit-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
+ background: -o-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
+ background: -ms-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
+ background: linear-gradient(to bottom, #ffffff 0%, #e5e5e5 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e5e5e5', GradientType=0);
+ box-shadow: -1px -1px 1px #03385c, 1px 1px 1px #09F, 3px 3px 3px white inset, -3px -3px 3px rgba(0, 0, 0, .25) inset;
}
.btn-select {
- display: inline-block;
- position: relative;
- height: 38px;
- padding-right: 20px;
- margin: 5px;
- vertical-align: top;
- -webkit-border-radius: 5px;
- -moz-border-radius: 5px;
- border-radius: 5px;
- background: #eee;
- box-shadow: -1px -1px 1px #666, 1px 1px 1px #fff, 2px 2px 2px rgba(0, 0, 0, .15) inset;
- font-size: 13px;
- line-height: 38px;
- text-align: left;
- cursor: pointer;
+ display: inline-block;
+ position: relative;
+ height: 38px;
+ padding-right: 20px;
+ margin: 5px;
+ vertical-align: top;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+ background: #eee;
+ box-shadow: -1px -1px 1px #666, 1px 1px 1px #fff, 2px 2px 2px rgba(0, 0, 0, .15) inset;
+ font-size: 13px;
+ line-height: 38px;
+ text-align: left;
+ cursor: pointer;
}
.btn-select .text {
- padding: 0 10px;
+ padding: 0 10px;
}
.btn-select .arrow {
- position: absolute;
- width: 15px;
- height: 38px;
- line-height: 38px;
- top: 0;
- right: 5px;
- background: url(../images/theme-default/arrow-down-black.png) no-repeat center center;
+ position: absolute;
+ width: 15px;
+ height: 38px;
+ line-height: 38px;
+ top: 0;
+ right: 5px;
+ background: url(../images/theme-default/arrow-down-black.png) no-repeat center center;
}
/* Generated at http://colorzilla.com/gradient-editor/ */
.button-big {
- display: block;
- margin-bottom: 10px;
- border: 1px solid #ccc;
- box-shadow: -1px -1px 1px #ccc, 1px 1px 1px #eee;
- text-shadow: -1px -1px 1px #ccc, 1px 1px 1px #fff;
- -webkit-border-radius: 05px;
- -moz-border-radius: 05px;
- border-radius: 05px;
- background: #ffffff;
- background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #e5e5e5));
- background: -webkit-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
- background: -o-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
- background: -ms-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
- background: linear-gradient(to bottom, #ffffff 0%, #e5e5e5 100%);
- filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#e5e5e5', GradientType=0);
- text-align: center;
- font-size: 13px;
- line-height: 38px;
- width: 100%;
+ display: block;
+ margin-bottom: 10px;
+ border: 1px solid #ccc;
+ box-shadow: -1px -1px 1px #ccc, 1px 1px 1px #eee;
+ text-shadow: -1px -1px 1px #ccc, 1px 1px 1px #fff;
+ -webkit-border-radius: 05px;
+ -moz-border-radius: 05px;
+ border-radius: 05px;
+ background: #ffffff;
+ background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #e5e5e5));
+ background: -webkit-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
+ background: -o-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
+ background: -ms-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
+ background: linear-gradient(to bottom, #ffffff 0%, #e5e5e5 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#e5e5e5', GradientType=0);
+ text-align: center;
+ font-size: 13px;
+ line-height: 38px;
+ width: 100%;
}
.button-big:not([disabled]):hover {
- box-shadow: -1px -1px 1px #bbb, 1px 1px 1px #ddd;
- background: #eeeeee;
- background: -moz-linear-gradient(top, #eeeeee 0%, #d5d5d5 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #eeeeee), color-stop(100%, #d5d5d5));
- background: -webkit-linear-gradient(top, #eeeeee 0%, #d5d5d5 100%);
- background: -o-linear-gradient(top, #eeeeee 0%, #d5d5d5 100%);
- background: -ms-linear-gradient(top, #eeeeee 0%, #d5d5d5 100%);
- background: linear-gradient(to bottom, #eeeeee 0%, #d5d5d5 100%);
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#d5d5d5', GradientType=0);
+ box-shadow: -1px -1px 1px #bbb, 1px 1px 1px #ddd;
+ background: #eeeeee;
+ background: -moz-linear-gradient(top, #eeeeee 0%, #d5d5d5 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #eeeeee), color-stop(100%, #d5d5d5));
+ background: -webkit-linear-gradient(top, #eeeeee 0%, #d5d5d5 100%);
+ background: -o-linear-gradient(top, #eeeeee 0%, #d5d5d5 100%);
+ background: -ms-linear-gradient(top, #eeeeee 0%, #d5d5d5 100%);
+ background: linear-gradient(to bottom, #eeeeee 0%, #d5d5d5 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#d5d5d5', GradientType=0);
}
.button-big:not([disabled]):active {
- box-shadow: -1px -1px 1px #eee, 1px 1px 1px #ccc, 2px 2px 2px #ccc inset, -2px -2px 2px #aaa inset;
- background: -moz-linear-gradient(top, #e5e5e5 0%, #ffffff 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #e5e5e5), color-stop(100%, #ffffff));
- background: -webkit-linear-gradient(top, #e5e5e5 0%, #ffffff 100%);
- background: -o-linear-gradient(top, #e5e5e5 0%, #ffffff 100%);
- background: -ms-linear-gradient(top, #e5e5e5 0%, #ffffff 100%);
- background: linear-gradient(to bottom, #e5e5e5 0%, #ffffff 100%);
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#e5e5e5', endColorstr='#ffffff', GradientType=0);
+ box-shadow: -1px -1px 1px #eee, 1px 1px 1px #ccc, 2px 2px 2px #ccc inset, -2px -2px 2px #aaa inset;
+ background: -moz-linear-gradient(top, #e5e5e5 0%, #ffffff 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #e5e5e5), color-stop(100%, #ffffff));
+ background: -webkit-linear-gradient(top, #e5e5e5 0%, #ffffff 100%);
+ background: -o-linear-gradient(top, #e5e5e5 0%, #ffffff 100%);
+ background: -ms-linear-gradient(top, #e5e5e5 0%, #ffffff 100%);
+ background: linear-gradient(to bottom, #e5e5e5 0%, #ffffff 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#e5e5e5', endColorstr='#ffffff', GradientType=0);
}
/* Generated at http://colorzilla.com/gradient-editor/ */
.button-big.red:not([disabled]) {
- text-shadow: -1px -1px 1px #9e0505, 1px 1px 1px #fc5d4c;
- border: 1px solid #b10f14;
- background: #ff3019;
- background: -moz-linear-gradient(top, #ff3019 0%, #cf0404 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ff3019), color-stop(100%, #cf0404));
- background: -webkit-linear-gradient(top, #ff3019 0%, #cf0404 100%);
- background: -o-linear-gradient(top, #ff3019 0%, #cf0404 100%);
- background: -ms-linear-gradient(top, #ff3019 0%, #cf0404 100%);
- background: linear-gradient(to bottom, #ff3019 0%, #cf0404 100%);
- filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ff3019', endColorstr='#cf0404', GradientType=0);
- color: #fff;
+ text-shadow: -1px -1px 1px #9e0505, 1px 1px 1px #fc5d4c;
+ border: 1px solid #b10f14;
+ background: #ff3019;
+ background: -moz-linear-gradient(top, #ff3019 0%, #cf0404 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ff3019), color-stop(100%, #cf0404));
+ background: -webkit-linear-gradient(top, #ff3019 0%, #cf0404 100%);
+ background: -o-linear-gradient(top, #ff3019 0%, #cf0404 100%);
+ background: -ms-linear-gradient(top, #ff3019 0%, #cf0404 100%);
+ background: linear-gradient(to bottom, #ff3019 0%, #cf0404 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ff3019', endColorstr='#cf0404', GradientType=0);
+ color: #fff;
}
.button-big.red:not([disabled]):hover {
- background: #ef2009;
- background: -moz-linear-gradient(top, #ef2009 0%, #bf0404 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ef2009), color-stop(100%, #bf0404));
- background: -webkit-linear-gradient(top, #ef2009 0%, #bf0404 100%);
- background: -o-linear-gradient(top, #ef2009 0%, #bf0404 100%);
- background: -ms-linear-gradient(top, #ef2009 0%, #bf0404 100%);
- background: linear-gradient(to bottom, #ef2009 0%, #bf0404 100%);
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ef2009', endColorstr='#bf0404', GradientType=0);
- color: #fff;
+ background: #ef2009;
+ background: -moz-linear-gradient(top, #ef2009 0%, #bf0404 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ef2009), color-stop(100%, #bf0404));
+ background: -webkit-linear-gradient(top, #ef2009 0%, #bf0404 100%);
+ background: -o-linear-gradient(top, #ef2009 0%, #bf0404 100%);
+ background: -ms-linear-gradient(top, #ef2009 0%, #bf0404 100%);
+ background: linear-gradient(to bottom, #ef2009 0%, #bf0404 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ef2009', endColorstr='#bf0404', GradientType=0);
+ color: #fff;
}
.button-big.red:not([disabled]):active {
- background: -moz-linear-gradient(top, #cf0404 0%, #ff3019 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #cf0404), color-stop(100%, #ff3019));
- background: -webkit-linear-gradient(top, #cf0404 0%, #ff3019 100%);
- background: -o-linear-gradient(top, #cf0404 0%, #ff3019 100%);
- background: -ms-linear-gradient(top, #cf0404 0%, #ff3019 100%);
- background: linear-gradient(to bottom, #cf0404 0%, #ff3019 100%);
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#cf0404', endColorstr='#ff3019', GradientType=0);
+ background: -moz-linear-gradient(top, #cf0404 0%, #ff3019 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #cf0404), color-stop(100%, #ff3019));
+ background: -webkit-linear-gradient(top, #cf0404 0%, #ff3019 100%);
+ background: -o-linear-gradient(top, #cf0404 0%, #ff3019 100%);
+ background: -ms-linear-gradient(top, #cf0404 0%, #ff3019 100%);
+ background: linear-gradient(to bottom, #cf0404 0%, #ff3019 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#cf0404', endColorstr='#ff3019', GradientType=0);
}
.button-big.disable {
- display: none;
+ display: none;
}
.btn-normal {
- display: inline-block;
- height: 38px;
- margin: 9px 3px;
- vertical-align: top;
- background: #06F;
- line-height: 38px;
- padding: 0 20px;
- color: #EEE;
+ display: inline-block;
+ height: 38px;
+ margin: 9px 3px;
+ vertical-align: top;
+ background: #06F;
+ line-height: 38px;
+ padding: 0 20px;
+ color: #EEE;
border-radius: 8px;
font-size: 13px;
}
.btn-normal:not([disabled]):hover {
- background: #04D;
+ background: #04D;
}
.btn-normal:not([disabled]):active {
- box-shadow: -1px -1px 1px #eee, 1px 1px 1px #ccc, 2px 2px 2px #ccc inset, -2px -2px 2px #aaa inset;
- background: -moz-linear-gradient(top, #04d 0%, #06f 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #04d), color-stop(100%, #06f));
- background: -webkit-linear-gradient(top, #04d 0%, #ff3019 100%);
- background: -o-linear-gradient(top, #04d 0%, #06f 100%);
- background: -ms-linear-gradient(top, #04d 0%, #06f 100%);
- background: linear-gradient(to bottom, #04d 0%, #06f 100%);
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#04d', endColorstr='#06f', GradientType=0);
+ box-shadow: -1px -1px 1px #eee, 1px 1px 1px #ccc, 2px 2px 2px #ccc inset, -2px -2px 2px #aaa inset;
+ background: -moz-linear-gradient(top, #04d 0%, #06f 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #04d), color-stop(100%, #06f));
+ background: -webkit-linear-gradient(top, #04d 0%, #ff3019 100%);
+ background: -o-linear-gradient(top, #04d 0%, #06f 100%);
+ background: -ms-linear-gradient(top, #04d 0%, #06f 100%);
+ background: linear-gradient(to bottom, #04d 0%, #06f 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#04d', endColorstr='#06f', GradientType=0);
}
.btn-normal[disabled] {
- background-color: silver;
+ background-color: silver;
}
.btn-group {
- float: right;
- padding: 0 10px;
+ float: right;
+ padding: 0 10px;
}
.btn-small {
diff --git a/ui/css/theme-default/circle.css b/ui/css/theme-default/circle.css
index 0cd7909..8008219 100644
--- a/ui/css/theme-default/circle.css
+++ b/ui/css/theme-default/circle.css
@@ -19,8 +19,8 @@
* limitations under the License.
*/
.circle {
- position: relative;
- margin: 30px 10px 10px 10px;
- width: 70px;
- height: 70px;
+ position: relative;
+ margin: 30px 10px 10px 10px;
+ width: 70px;
+ height: 70px;
}
diff --git a/ui/css/theme-default/form.css b/ui/css/theme-default/form.css
index 9b7fd68..c24b277 100644
--- a/ui/css/theme-default/form.css
+++ b/ui/css/theme-default/form.css
@@ -19,29 +19,29 @@
* limitations under the License.
*/
.form-section {
- padding: 10px;
+ padding: 10px;
}
.form-section>h2 {
- font-size: 14px;
- padding: 5px;
+ font-size: 14px;
+ padding: 5px;
}
.form-section .field {
- padding: 5px 5px 5px 20px;
- overflow: hidden;
+ padding: 5px 5px 5px 20px;
+ overflow: hidden;
}
.form-section .field .text-help {
- font-size: 12px;
- color: #333;
- margin: 0 0 5px 5px;
+ font-size: 12px;
+ color: #333;
+ margin: 0 0 5px 5px;
}
.form-section .field input.text {
- border: 1px solid #ccc;
- font-size: 16px;
- height: 30px;
- line-height: 30px;
- padding: 0 5px;
+ border: 1px solid #ccc;
+ font-size: 16px;
+ height: 30px;
+ line-height: 30px;
+ padding: 0 5px;
}
diff --git a/ui/css/theme-default/framework.css b/ui/css/theme-default/framework.css
index 200dd6b..d904527 100644
--- a/ui/css/theme-default/framework.css
+++ b/ui/css/theme-default/framework.css
@@ -19,18 +19,18 @@
* limitations under the License.
*/
body {
- background: url(../images/theme-default/bg.png);
+ background: url(../images/theme-default/bg.png);
}
.tmpl-html {
- display: none;
+ display: none;
}
.container {
- margin: 0 auto;
- width: 1024px;
+ margin: 0 auto;
+ width: 1024px;
}
.hidden {
- display: none;
+ display: none;
}
diff --git a/ui/css/theme-default/guest-edit.css b/ui/css/theme-default/guest-edit.css
index 5f97a12..79fca83 100644
--- a/ui/css/theme-default/guest-edit.css
+++ b/ui/css/theme-default/guest-edit.css
@@ -19,49 +19,49 @@
* limitations under the License.
*/
#guest-edit-window {
- font-size: 13px;
- height: 380px;
- width: 420px;
+ font-size: 13px;
+ height: 380px;
+ width: 420px;
}
.guest-edit-fieldset {
- float: left;
- padding: 1em;
+ float: left;
+ padding: 1em;
}
.guest-edit-wrapper-label, .guest-edit-wrapper-controls {
- display: inline-block;
+ display: inline-block;
}
.guest-edit-wrapper-controls input[type="text"][disabled] {
- color: #bbb;
- background-color: #fafafa;
- cursor: not-allowed;
+ color: #bbb;
+ background-color: #fafafa;
+ cursor: not-allowed;
}
.guest-edit-wrapper-label {
- width: 10em;
+ width: 10em;
}
.guest-edit-wrapper-controls {
- width: 18em;
+ width: 18em;
}
.guest-edit-wrapper-controls input[type="text"] {
- height: 38px;
- line-height: 38px;
- background: #fff;
- -webkit-border-radius: 5px;
- border-radius: 5px;
- box-shadow: 2px 2px 2px #eee inset;
- border-top: 1px solid #bbb;
- border-left: 1px solid #bbb;
- padding-left: 10px;
- width: 100%;
+ height: 38px;
+ line-height: 38px;
+ background: #fff;
+ -webkit-border-radius: 5px;
+ border-radius: 5px;
+ box-shadow: 2px 2px 2px #eee inset;
+ border-top: 1px solid #bbb;
+ border-left: 1px solid #bbb;
+ padding-left: 10px;
+ width: 100%;
}
.guest-edit-wrapper-controls input[type="text"][disabled] {
- color: #bbb;
- background-color: #fafafa;
- cursor: not-allowed;
+ color: #bbb;
+ background-color: #fafafa;
+ cursor: not-allowed;
}
diff --git a/ui/css/theme-default/list.css b/ui/css/theme-default/list.css
index e34772f..468361f 100644
--- a/ui/css/theme-default/list.css
+++ b/ui/css/theme-default/list.css
@@ -19,232 +19,232 @@
* limitations under the License.
*/
.list-vm {
- margin: 10px;
+ margin: 10px;
}
/* Generated at http://colorzilla.com/gradient-editor/ */
.list-vm>li {
- margin-bottom: 10px;
- background: #ffffff;
- background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #e5e5e5));
- background: -webkit-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
- background: -o-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
- background: -ms-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
- background: linear-gradient(to bottom, #ffffff 0%, #e5e5e5 100%);
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e5e5e5', GradientType=0);
- border: 1px solid #ccc;
- color: #333;
- -webkit-border-radius: 8px;
- -moz-border-radius: 8px;
- border-radius: 8px;
+ margin-bottom: 10px;
+ background: #ffffff;
+ background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #e5e5e5));
+ background: -webkit-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
+ background: -o-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
+ background: -ms-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
+ background: linear-gradient(to bottom, #ffffff 0%, #e5e5e5 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e5e5e5', GradientType=0);
+ border: 1px solid #ccc;
+ color: #333;
+ -webkit-border-radius: 8px;
+ -moz-border-radius: 8px;
+ border-radius: 8px;
}
.list-vm li>* {
- height: 130px;
- display: table-cell;
- vertical-align: top;
- position: relative;
- border-left: 1px solid #ccc;
- border-right: 1px solid #fff;
+ height: 130px;
+ display: table-cell;
+ vertical-align: top;
+ position: relative;
+ border-left: 1px solid #ccc;
+ border-right: 1px solid #fff;
}
.list-vm li>*:FIRST-CHILD {
- border-left: none;
+ border-left: none;
}
.list-vm li>*:LAST-CHILD {
- border-right: none;
+ border-right: none;
}
.list-vm li>.guest-tile{
- text-align: center;
- vertical-align: middle;
+ text-align: center;
+ vertical-align: middle;
}
.list-vm .handle {
- display: block;
- width: 50px;
- height: 130px;
- box-sizing: border-box;
- box-shadow: inset 4px 4px 4px #0289e2, inset -4px -4px 4px #04385d;
- background: #0b6bad url(../images/theme-default/arrow_out.png) center center no-repeat;
- border-top-right: 1px solid #CCC;
- -webkit-border-top-right-radius: 8px;
- -moz-border-top-right-radius: 8px;
- border-top-right-radius: 8px;
- border-bottom-right: 1px solid #CCC;
- -webkit-border-bottom-right-radius: 8px;
- -moz-border-bottom-right-radius: 8px;
- border-bottom-right-radius: 8px;
+ display: block;
+ width: 50px;
+ height: 130px;
+ box-sizing: border-box;
+ box-shadow: inset 4px 4px 4px #0289e2, inset -4px -4px 4px #04385d;
+ background: #0b6bad url(../images/theme-default/arrow_out.png) center center no-repeat;
+ border-top-right: 1px solid #CCC;
+ -webkit-border-top-right-radius: 8px;
+ -moz-border-top-right-radius: 8px;
+ border-top-right-radius: 8px;
+ border-bottom-right: 1px solid #CCC;
+ -webkit-border-bottom-right-radius: 8px;
+ -moz-border-bottom-right-radius: 8px;
+ border-bottom-right-radius: 8px;
}
.list-vm .subtitle {
- color: #666;
- font-size: 13px;
- text-align: center;
- line-height: 10px;
- font-weight: bold;
+ color: #666;
+ font-size: 13px;
+ text-align: center;
+ line-height: 10px;
+ font-weight: bold;
}
.list-vm .tile .imgload {
- display: none;
- max-height: 110px;
- max-width: 170px;
- height: auto;
- width: auto;
+ display: none;
+ max-height: 110px;
+ max-width: 170px;
+ height: auto;
+ width: auto;
}
.list-vm .tile .imgactive {
- max-height: 110px;
- max-width: 170px;
- height: auto;
- width: auto;
+ max-height: 110px;
+ max-width: 170px;
+ height: auto;
+ width: auto;
}
.guest-type {
- width: 257px;
+ width: 257px;
}
.guest-cpu {
- width: 91px;
+ width: 91px;
}
.guest-network {
- width: 91px;
+ width: 91px;
}
.guest-storage {
- width: 91px;
+ width: 91px;
}
.guest-tile {
- width: 190px;
+ width: 190px;
}
.guest-users {
- width: 93px;
+ width: 93px;
}
.guest-actions {
- width: 125px;
- min-width: 125px;
+ width: 125px;
+ min-width: 125px;
}
.guest-handle {
- width: 50px;
+ width: 50px;
}
.guest-general {
- padding: 10px;
- border-bottom: 1px solid #ccc;
- width: 237px;
+ padding: 10px;
+ border-bottom: 1px solid #ccc;
+ width: 237px;
}
.guest-ip {
- padding: 0 10px;
- border-top: 1px solid #fff;
+ padding: 0 10px;
+ border-top: 1px solid #fff;
}
.guest-general .title {
- color: #666;
- font-size: 16px;
- font-weight: normal;
- height: 25px;
- line-height: 25px;
- text-shadow: -1px -1px 1px #ccc, 1px 1px 1px #fff;
- max-width: 237px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
+ color: #666;
+ font-size: 16px;
+ font-weight: normal;
+ height: 25px;
+ line-height: 25px;
+ text-shadow: -1px -1px 1px #ccc, 1px 1px 1px #fff;
+ max-width: 237px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
.guest-general .text {
- font-weight: bold;
- color: #999;
- font-size: 11px;
- text-shadow: -1px -1px 1px #ccc, 1px 1px 1px #fff;
+ font-weight: bold;
+ color: #999;
+ font-size: 11px;
+ text-shadow: -1px -1px 1px #ccc, 1px 1px 1px #fff;
}
.guest-users .top {
- border-bottom: 1px solid #ccc;
- padding: 3px 10px;
+ border-bottom: 1px solid #ccc;
+ padding: 3px 10px;
}
.guest-users .bottom {
- border-top: 1px solid #fff;
- padding: 3px 10px;
+ border-top: 1px solid #fff;
+ padding: 3px 10px;
}
.guest-users .users {
- height: 45px;
- line-height: 45px;
- background: url(../images/theme-default/icon-user.png) left center no-repeat;
- padding-left: 50px;
- font-size: 36px;
- font-weight: bold;
+ height: 45px;
+ line-height: 45px;
+ background: url(../images/theme-default/icon-user.png) left center no-repeat;
+ padding-left: 50px;
+ font-size: 36px;
+ font-weight: bold;
}
.guest-users .snapshots {
- height: 40px;
- line-height: 40px;
- background: url(../images/theme-default/icon-camera.png) left center no-repeat;
- padding-left: 50px;
- font-size: 36px;
- font-weight: bold;
+ height: 40px;
+ line-height: 40px;
+ background: url(../images/theme-default/icon-camera.png) left center no-repeat;
+ padding-left: 50px;
+ font-size: 36px;
+ font-weight: bold;
}
.guest-users .mini-text {
- font-size: 11px;
- font-weight: normal;
- text-shadow: -1px -1px 1px #ccc, 1px 1px 1px #fff;
+ font-size: 11px;
+ font-weight: normal;
+ text-shadow: -1px -1px 1px #ccc, 1px 1px 1px #fff;
}
.guest-actions .top {
- padding: 7px 10px;
+ padding: 7px 10px;
}
.guest-actions .bottom {
- padding: 0 10px;
+ padding: 0 10px;
}
.list-vm .tile {
- max-width: 170px;
- max-height: 110px;
- width: auto;
- height: auto;
- margin: 10px;
+ max-width: 170px;
+ max-height: 110px;
+ width: auto;
+ height: auto;
+ margin: 10px;
}
.list-vm .tile:not(.shutoff) img {
- box-shadow: -1px -1px 2px rgb(0, 0, 0, .25), 3px 3px 3px #fff;
+ box-shadow: -1px -1px 2px rgb(0, 0, 0, .25), 3px 3px 3px #fff;
}
.list-vm .shutoff {
- box-shadow: none !important;
+ box-shadow: none !important;
}
.list-vm .shutoff img {
- opacity: 0.4;
+ opacity: 0.4;
}
.list-title {
- color: #666;
- font-weight: bold;
- font-size: 12px;
- overflow: hidden;
- margin: 10px;
+ color: #666;
+ font-weight: bold;
+ font-size: 12px;
+ overflow: hidden;
+ margin: 10px;
}
.list-title li {
- display: table-cell;
- padding: 0 1px;
+ display: table-cell;
+ padding: 0 1px;
}
.list-no-result {
- font-size: 16px;
- height: 48px;
- line-height: 48px;
- text-shadow: -1px -1px 1px #ccc, 1px 1px 1px #fff;
- padding-left: 10px;
+ font-size: 16px;
+ height: 48px;
+ line-height: 48px;
+ text-shadow: -1px -1px 1px #ccc, 1px 1px 1px #fff;
+ padding-left: 10px;
}
diff --git a/ui/css/theme-default/login-window.css b/ui/css/theme-default/login-window.css
index c2f445d..a5a2729 100644
--- a/ui/css/theme-default/login-window.css
+++ b/ui/css/theme-default/login-window.css
@@ -26,10 +26,10 @@
#login-window>header>.title-text {
front: #000000;
font-size: 18px;
- height: 48px;
- line-height: 48px;
- font-weight: bold;
- text-shadow: -1px -1px 1px #eaeaea, 1px 1px 1px #fff;
+ height: 48px;
+ line-height: 48px;
+ font-weight: bold;
+ text-shadow: -1px -1px 1px #eaeaea, 1px 1px 1px #fff;
}
#login-window footer #form-language {
diff --git a/ui/css/theme-default/message.css b/ui/css/theme-default/message.css
index d2c7192..46d1a55 100644
--- a/ui/css/theme-default/message.css
+++ b/ui/css/theme-default/message.css
@@ -19,120 +19,120 @@
* limitations under the License.
*/
#messageField {
- position: fixed;
- margin: auto;
- left: 0;
- right: 0;
- top: 0;
- width: 1024px;
- max-width: 100%;
- z-index: 200;
+ position: fixed;
+ margin: auto;
+ left: 0;
+ right: 0;
+ top: 0;
+ width: 1024px;
+ max-width: 100%;
+ z-index: 200;
}
.message {
- background-color: #FFF68F;
- position: relative;
- margin-bottom: 5px;
+ background-color: #FFF68F;
+ position: relative;
+ margin-bottom: 5px;
}
.message.warn {
- background-color: #FFF68F;
+ background-color: #FFF68F;
}
.message.error {
- background-color: #FFAEB9;
+ background-color: #FFAEB9;
}
.message.success {
- background-color: #90EE90;
+ background-color: #90EE90;
}
.message .close {
- position: absolute;
- width: 30px;
- height: 30px;
- top: 0;
- right: 0;
- color: #545454;
- font-size: 12px;
- text-align: center;
- line-height: 30px;
- cursor: pointer;
+ position: absolute;
+ width: 30px;
+ height: 30px;
+ top: 0;
+ right: 0;
+ color: #545454;
+ font-size: 12px;
+ text-align: center;
+ line-height: 30px;
+ cursor: pointer;
}
.message .content {
- padding: 0 30px 0 10px;
- line-height: 30px;
+ padding: 0 30px 0 10px;
+ line-height: 30px;
}
.confirmbox {
- position: absolute;
- margin: auto;
- left: 0;
- right: 0;
- top: 0;
- bottom: 0;
- box-shadow: 2px 2px 6px #000;
- border: 2px solid #0f71b4;
- -webkit-border-radius: 8px;
- -moz-border-radius: 8px;
- border-radius: 8px;
- background-color: black;
- box-sizing: border-box;
- max-width: 100%;
- max-height: 100%;
- width: 350px;
- height: 170px;
- z-index: 9999;
+ position: absolute;
+ margin: auto;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ box-shadow: 2px 2px 6px #000;
+ border: 2px solid #0f71b4;
+ -webkit-border-radius: 8px;
+ -moz-border-radius: 8px;
+ border-radius: 8px;
+ background-color: black;
+ box-sizing: border-box;
+ max-width: 100%;
+ max-height: 100%;
+ width: 350px;
+ height: 170px;
+ z-index: 9999;
}
.confirmbox>footer {
- position: absolute;
- left: 0;
- right: 0;
- bottom: 0;
- height: 48px;
- box-shadow: 0 -1px 1px rgba(0, 0, 0, 0.15);
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ height: 48px;
+ box-shadow: 0 -1px 1px rgba(0, 0, 0, 0.15);
}
.confirmbox .title {
- display: inline-block;
- padding: 10px 10px 0 10px;
- line-height: 20px;
- color: white;
+ display: inline-block;
+ padding: 10px 10px 0 10px;
+ line-height: 20px;
+ color: white;
}
.confirmbox .content {
- padding: 20px 10px 0px 100px;
- line-height: 16px;
- font-size: 13px;
- color: white;
- background: url(../images/theme-default/alert.png) no-repeat 20px center;
- height: 41px;
- vertical-align: middle;
+ padding: 20px 10px 0px 100px;
+ line-height: 16px;
+ font-size: 13px;
+ color: white;
+ background: url(../images/theme-default/alert.png) no-repeat 20px center;
+ height: 41px;
+ vertical-align: middle;
}
.confirmbox .close {
- position: absolute;
- width: 13px;
- height: 13px;
- top: 7px;
- right: 7px;
- -webkit-border-radius: 13px;
- -moz-border-radius: 13px;
- border-radius: 13px;
- border: 2px solid #ccc;
- color: #ccc;
- font-size: 13px;
- text-align: center;
- line-height: 13px;
- font-weight: bold;
- background: #4a4a4a;;
- cursor: pointer;
+ position: absolute;
+ width: 13px;
+ height: 13px;
+ top: 7px;
+ right: 7px;
+ -webkit-border-radius: 13px;
+ -moz-border-radius: 13px;
+ border-radius: 13px;
+ border: 2px solid #ccc;
+ color: #ccc;
+ font-size: 13px;
+ text-align: center;
+ line-height: 13px;
+ font-weight: bold;
+ background: #4a4a4a;;
+ cursor: pointer;
}
.confirmbox>header>.close:hover {
- border: 2px solid #444;
- color: #444;
+ border: 2px solid #444;
+ color: #444;
}
\ No newline at end of file
diff --git a/ui/css/theme-default/nav-tree.css b/ui/css/theme-default/nav-tree.css
index 7a9356a..52c0506 100644
--- a/ui/css/theme-default/nav-tree.css
+++ b/ui/css/theme-default/nav-tree.css
@@ -19,98 +19,98 @@
* limitations under the License.
*/
.nav-tree {
- overflow: hidden;
- border-bottom: 1px solid #000;
+ overflow: hidden;
+ border-bottom: 1px solid #000;
}
.nav-tree .item {
- overflow: hidden;
- border-top: 1px solid #000;
- border-bottom: 1px solid #444;
- background: #222;
- *vertical-align: top;
+ overflow: hidden;
+ border-top: 1px solid #000;
+ border-bottom: 1px solid #444;
+ background: #222;
+ *vertical-align: top;
}
.nav-tree .item .arrow {
- float: left;
- width: 42px;
- height: 48px;
- border-right: 1px solid #000;
- background: transparent url("../images/theme-default/folder-arrow-right.png") center center no-repeat;
+ float: left;
+ width: 42px;
+ height: 48px;
+ border-right: 1px solid #000;
+ background: transparent url("../images/theme-default/folder-arrow-right.png") center center no-repeat;
}
.nav-tree .item.on .arrow {
- background: transparent url("../images/theme-default/folder-arrow-down.png") center center no-repeat;
+ background: transparent url("../images/theme-default/folder-arrow-down.png") center center no-repeat;
}
.nav-tree .item.nochild .arrow {
- background: none;
+ background: none;
}
.nav-tree .item .arrow+.item-link {
- border-left: 1px solid #444;
+ border-left: 1px solid #444;
}
.nav-tree .sub {
- display: none;
+ display: none;
}
.nav-tree .item.on+.sub {
- display: block;
+ display: block;
}
.nav-tree .sub .item {
- padding-left: 44px;
+ padding-left: 44px;
}
.nav-tree .item-link {
- display: block;
- position: relative;
- height: 48px;
- overflow: hidden;
- line-height: 48px;
- color: #eee;
+ display: block;
+ position: relative;
+ height: 48px;
+ overflow: hidden;
+ line-height: 48px;
+ color: #eee;
}
.nav-tree .item-link .count {
- position: absolute;
- height: 24px;
- right: 10px;
- margin: 12px 0;
- padding: 0 9px;
- background: #900;
- box-shadow: 1px 1px 1px #333, -1px -1px 1px #000, 1px 1px 1px #600 inset;
- -webkit-border-radius: 24px;
- -moz-border-radius: 24px;
- border-radius: 24px;
- text-align: center;
- color: #fff;
- font-size: 10px;
- line-height: 24px;
+ position: absolute;
+ height: 24px;
+ right: 10px;
+ margin: 12px 0;
+ padding: 0 9px;
+ background: #900;
+ box-shadow: 1px 1px 1px #333, -1px -1px 1px #000, 1px 1px 1px #600 inset;
+ -webkit-border-radius: 24px;
+ -moz-border-radius: 24px;
+ border-radius: 24px;
+ text-align: center;
+ color: #fff;
+ font-size: 10px;
+ line-height: 24px;
}
.nav-tree .item-link .title {
- position: absolute;
- left: 42px;
- right: 42px;
- overflow: hidden;
- color: #fff;
- font-size: 13px;
- text-overflow: ellipsis;
- white-space: nowrap;
- overflow: hidden;
+ position: absolute;
+ left: 42px;
+ right: 42px;
+ overflow: hidden;
+ color: #fff;
+ font-size: 13px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
}
.nav-tree .item-link .thumb {
- position: absolute;
- width: 42px;
- height: 48px;
+ position: absolute;
+ width: 42px;
+ height: 48px;
}
.nav-tree .item-link .thumb.host {
- background: url("../images/theme-default/thumb-host.png") center center no-repeat;
+ background: url("../images/theme-default/thumb-host.png") center center no-repeat;
}
.nav-tree .item-link .thumb.guest {
- background: url("../images/theme-default/thumb-guest.png") center center no-repeat;
+ background: url("../images/theme-default/thumb-guest.png") center center no-repeat;
}
diff --git a/ui/css/theme-default/navbar.css b/ui/css/theme-default/navbar.css
index efd08aa..7ecfcf1 100644
--- a/ui/css/theme-default/navbar.css
+++ b/ui/css/theme-default/navbar.css
@@ -19,44 +19,44 @@
* limitations under the License.
*/
.navbar {
- height: 48px;
- border-top: 1px solid #2c85c3;
- border-bottom: 1px solid #04385d;
- background: #0769ac url(../images/theme-default/nav-bg.png) repeat-x;
+ height: 48px;
+ border-top: 1px solid #2c85c3;
+ border-bottom: 1px solid #04385d;
+ background: #0769ac url(../images/theme-default/nav-bg.png) repeat-x;
}
.nav-menu {
- height: 48px;
- position: relative;
+ height: 48px;
+ position: relative;
}
.nav-menu li {
- float: left;
- height: 48px;
+ float: left;
+ height: 48px;
}
.nav-menu .item {
- display: block;
- width: 100px;
- height: 48px;
- line-height: 48px;
- padding: 0 10px;
- text-align: center;
- font-size: 13px;
- color: #eee;
- outline: none;
+ display: block;
+ width: 100px;
+ height: 48px;
+ line-height: 48px;
+ padding: 0 10px;
+ text-align: center;
+ font-size: 13px;
+ color: #eee;
+ outline: none;
}
.nav-menu .item.current:focus {
- outline: white dotted thin;
+ outline: white dotted thin;
}
.menu-arrow {
- position: absolute;
- width: 0;
- bottom: -1px;
- left: 50px;
- border: 10px solid transparent;
- border-top: none;
- border-bottom-color: #e4e4e4;
+ position: absolute;
+ width: 0;
+ bottom: -1px;
+ left: 50px;
+ border: 10px solid transparent;
+ border-top: none;
+ border-bottom-color: #e4e4e4;
}
diff --git a/ui/css/theme-default/popover.css b/ui/css/theme-default/popover.css
index 65ec3a2..95eb183 100644
--- a/ui/css/theme-default/popover.css
+++ b/ui/css/theme-default/popover.css
@@ -19,102 +19,102 @@
* limitations under the License.
*/
.popover {
- -webkit-border-radius: 5px;
- -moz-border-radius: 5px;
- border-radius: 5px;
- background: #eee;
- border: 2px solid #096aad;
- box-shadow: 1px 1px 2px rgba(0, 0, 0, .5);
- z-index: 9999;
- position: absolute;
- top: 125%;
- left: 0;
- display: none;
- cursor: default;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+ background: #eee;
+ border: 2px solid #096aad;
+ box-shadow: 1px 1px 2px rgba(0, 0, 0, .5);
+ z-index: 9999;
+ position: absolute;
+ top: 125%;
+ left: 0;
+ display: none;
+ cursor: default;
}
.popover:BEFORE {
- content: "";
- display: block;
- border: 12px solid transparent;
- border-bottom-color: #096aad;
- position: absolute;
- top: -24px;
- left: 20px;
+ content: "";
+ display: block;
+ border: 12px solid transparent;
+ border-bottom-color: #096aad;
+ position: absolute;
+ top: -24px;
+ left: 20px;
}
.popover:AFTER {
- content: "";
- display: block;
- border: 10px solid transparent;
- border-bottom-color: #eee;
- position: absolute;
- top: -20px;
- left: 22px;
+ content: "";
+ display: block;
+ border: 10px solid transparent;
+ border-bottom-color: #eee;
+ position: absolute;
+ top: -20px;
+ left: 22px;
}
.popover.right-side {
- left: auto;
- right: 0;
+ left: auto;
+ right: 0;
}
.popover.right-side:BEFORE {
- left: auto;
- right: 20px;
+ left: auto;
+ right: 20px;
}
.popover.right-side:AFTER {
- left: auto;
- right: 22px;
+ left: auto;
+ right: 22px;
}
.open>.popover {
- display: block;
+ display: block;
}
/* Generated at http://colorzilla.com/gradient-editor/ */
.actionsheet {
- background: rgb(238, 238, 238);
- background: -moz-linear-gradient(top, rgba(238, 238, 238, 1) 0%, rgba(204, 204, 204, 1) 10px, rgba(204, 204, 204, 1) 96%,
- rgba(165, 165, 165, 1) 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(238, 238, 238, 1)),
- color-stop(10px, rgba(204, 204, 204, 1)), color-stop(96%, rgba(204, 204, 204, 1)),
- color-stop(100%, rgba(165, 165, 165, 1)));
- background: -webkit-linear-gradient(top, rgba(238, 238, 238, 1) 0%, rgba(204, 204, 204, 1) 10px, rgba(204, 204, 204, 1)
- 96%, rgba(165, 165, 165, 1) 100%);
- background: -o-linear-gradient(top, rgba(238, 238, 238, 1) 0%, rgba(204, 204, 204, 1) 10px, rgba(204, 204, 204, 1) 96%,
- rgba(165, 165, 165, 1) 100%);
- background: -ms-linear-gradient(top, rgba(238, 238, 238, 1) 0%, rgba(204, 204, 204, 1) 10px, rgba(204, 204, 204, 1) 96%,
- rgba(165, 165, 165, 1) 100%);
- background: linear-gradient(to bottom, rgba(238, 238, 238, 1) 0%, rgba(204, 204, 204, 1) 10px, rgba(204, 204, 204, 1)
- 96%, rgba(165, 165, 165, 1) 100%);
- padding: 10px 10px 0 10px;
+ background: rgb(238, 238, 238);
+ background: -moz-linear-gradient(top, rgba(238, 238, 238, 1) 0%, rgba(204, 204, 204, 1) 10px, rgba(204, 204, 204, 1) 96%,
+ rgba(165, 165, 165, 1) 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(238, 238, 238, 1)),
+ color-stop(10px, rgba(204, 204, 204, 1)), color-stop(96%, rgba(204, 204, 204, 1)),
+ color-stop(100%, rgba(165, 165, 165, 1)));
+ background: -webkit-linear-gradient(top, rgba(238, 238, 238, 1) 0%, rgba(204, 204, 204, 1) 10px, rgba(204, 204, 204, 1)
+ 96%, rgba(165, 165, 165, 1) 100%);
+ background: -o-linear-gradient(top, rgba(238, 238, 238, 1) 0%, rgba(204, 204, 204, 1) 10px, rgba(204, 204, 204, 1) 96%,
+ rgba(165, 165, 165, 1) 100%);
+ background: -ms-linear-gradient(top, rgba(238, 238, 238, 1) 0%, rgba(204, 204, 204, 1) 10px, rgba(204, 204, 204, 1) 96%,
+ rgba(165, 165, 165, 1) 100%);
+ background: linear-gradient(to bottom, rgba(238, 238, 238, 1) 0%, rgba(204, 204, 204, 1) 10px, rgba(204, 204, 204, 1)
+ 96%, rgba(165, 165, 165, 1) 100%);
+ padding: 10px 10px 0 10px;
}
.select-list {
- max-height: 195px;
- overflow: auto;
+ max-height: 195px;
+ overflow: auto;
}
.select-list>li {
- height: 48px;
- padding: 0 20px 0 40px;
- border-bottom: 1px solid #ccc;
- box-shadow: 0px 1px 1px #fff;
- text-shadow: -1px -1px 1px #ddd, 1px 1px 1px #fff;
- color: #222;
- font-size: 12px;
- line-height: 48px;
+ height: 48px;
+ padding: 0 20px 0 40px;
+ border-bottom: 1px solid #ccc;
+ box-shadow: 0px 1px 1px #fff;
+ text-shadow: -1px -1px 1px #ddd, 1px 1px 1px #fff;
+ color: #222;
+ font-size: 12px;
+ line-height: 48px;
}
.select-list>li:LAST-CHILD {
- border-bottom: none;
- box-shadow: inherit;
+ border-bottom: none;
+ box-shadow: inherit;
}
.select-list>li:hover {
- background: #f8f8f8 url(../images/theme-default/check-grey.png) no-repeat 10px center;
+ background: #f8f8f8 url(../images/theme-default/check-grey.png) no-repeat 10px center;
}
.select-list>li.active {
- background: #f8f8f8 url(../images/theme-default/check-green.png) no-repeat 10px center;
+ background: #f8f8f8 url(../images/theme-default/check-green.png) no-repeat 10px center;
}
diff --git a/ui/css/theme-default/reset.css b/ui/css/theme-default/reset.css
index b956119..05b4836 100644
--- a/ui/css/theme-default/reset.css
+++ b/ui/css/theme-default/reset.css
@@ -19,39 +19,39 @@
* limitations under the License.
*/
html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video,button
- {
- margin: 0;
- padding: 0;
- border: 0;
- font-family: Tahoma, Geneva, sans-serif;
+ {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-family: Tahoma, Geneva, sans-serif;
}
article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section
- {
- display: block;
+ {
+ display: block;
}
ol,ul {
- list-style: none;
+ list-style: none;
}
table {
- border-collapse: collapse;
- border-spacing: 0;
+ border-collapse: collapse;
+ border-spacing: 0;
}
img {
- border: none;
+ border: none;
}
a {
- text-decoration: none;
+ text-decoration: none;
}
a:active,a:hover {
- outline: none;
+ outline: none;
}
a label {
- cursor: pointer;
+ cursor: pointer;
}
diff --git a/ui/css/theme-default/storage.css b/ui/css/theme-default/storage.css
index 6ac7d10..d81dc75 100644
--- a/ui/css/theme-default/storage.css
+++ b/ui/css/theme-default/storage.css
@@ -526,33 +526,33 @@
}
.storage-type-wrapper-controls {
- width: 300px;
- display: inline-block;
- vertical-align: top;
- padding: 5px 5px 5px 20px;
+ width: 300px;
+ display: inline-block;
+ vertical-align: top;
+ padding: 5px 5px 5px 20px;
}
.storage-type-wrapper-controls input[type="text"] {
- height: 38px;
- line-height: 38px;
- background: #fff;
- -webkit-border-radius: 5px;
- border-radius: 5px;
- box-shadow: 2px 2px 2px #eee inset;
- border-top: 1px solid #bbb;
- border-left: 1px solid #bbb;
- padding: 0 10px;
- margin-top: 5px;
- width: 250px;
+ height: 38px;
+ line-height: 38px;
+ background: #fff;
+ -webkit-border-radius: 5px;
+ border-radius: 5px;
+ box-shadow: 2px 2px 2px #eee inset;
+ border-top: 1px solid #bbb;
+ border-left: 1px solid #bbb;
+ padding: 0 10px;
+ margin-top: 5px;
+ width: 250px;
}
.storage-type-wrapper-controls > .dropdown {
- margin: 5px 0 0 1px;
- width: 250px;
+ margin: 5px 0 0 1px;
+ width: 250px;
}
.storage-type-wrapper-controls input[type="text"][disabled] {
- color: #bbb;
- background-color: #fafafa;
- cursor: not-allowed;
+ color: #bbb;
+ background-color: #fafafa;
+ cursor: not-allowed;
}
\ No newline at end of file
diff --git a/ui/css/theme-default/template-edit.css b/ui/css/theme-default/template-edit.css
index 1a049c0..e61b2fb 100644
--- a/ui/css/theme-default/template-edit.css
+++ b/ui/css/theme-default/template-edit.css
@@ -19,57 +19,57 @@
* limitations under the License.
*/
#template-edit-window {
- font-size: 13px;
- height: 400px;
- width: 1000px;
+ font-size: 13px;
+ height: 400px;
+ width: 1000px;
}
.template-edit-fieldset {
- float: left;
- padding: 1em;
+ float: left;
+ padding: 1em;
}
.template-edit-wrapper-label, .template-edit-wrapper-controls {
- display: inline-block;
- vertical-align: top;
+ display: inline-block;
+ vertical-align: top;
}
.template-edit-wrapper-label {
- width: 150px;
- height: 38px;
- line-height: 38px;
- margin-top: 5px;
+ width: 150px;
+ height: 38px;
+ line-height: 38px;
+ margin-top: 5px;
}
.template-edit-wrapper-controls {
- width: 300px;
+ width: 300px;
}
.template-edit-wrapper-controls input[type="text"] {
- height: 38px;
- line-height: 38px;
- background: #fff;
- -webkit-border-radius: 5px;
- border-radius: 5px;
- box-shadow: 2px 2px 2px #eee inset;
- border-top: 1px solid #bbb;
- border-left: 1px solid #bbb;
- padding: 0 10px;
- margin-top: 5px;
- width: 250px;
+ height: 38px;
+ line-height: 38px;
+ background: #fff;
+ -webkit-border-radius: 5px;
+ border-radius: 5px;
+ box-shadow: 2px 2px 2px #eee inset;
+ border-top: 1px solid #bbb;
+ border-left: 1px solid #bbb;
+ padding: 0 10px;
+ margin-top: 5px;
+ width: 250px;
}
.template-edit-wrapper-controls > .dropdown {
- margin: 5px 0 0 1px;
- width: 250px;
+ margin: 5px 0 0 1px;
+ width: 250px;
}
.template-edit-wrapper-controls input[type="text"][disabled] {
- color: #bbb;
- background-color: #fafafa;
- cursor: not-allowed;
+ color: #bbb;
+ background-color: #fafafa;
+ cursor: not-allowed;
}
.hidden-area {
- display: none;
+ display: none;
}
diff --git a/ui/css/theme-default/template.css b/ui/css/theme-default/template.css
index 8629183..0962926 100644
--- a/ui/css/theme-default/template.css
+++ b/ui/css/theme-default/template.css
@@ -19,70 +19,70 @@
* limitations under the License.
*/
.tile-template>li>label:hover .summary {
- opacity: 0.1;
+ opacity: 0.1;
}
.tile-template>li>label:hover .list-info {
- top: 0;
+ top: 0;
}
.tile-template .summary {
- -webkit-transition: opacity 0.25s;
- -moz-transition: opacity 0.25s;
- transition: opacity 0.25s;
+ -webkit-transition: opacity 0.25s;
+ -moz-transition: opacity 0.25s;
+ transition: opacity 0.25s;
}
.tile-template .list-info {
- -webkit-transition: top 0.25s;
- -moz-transition: top 0.25s;
- transition: top 0.25s;
- position: absolute;
- top: 100%;
- width: 100%;
+ -webkit-transition: top 0.25s;
+ -moz-transition: top 0.25s;
+ transition: top 0.25s;
+ position: absolute;
+ top: 100%;
+ width: 100%;
}
.tile-template .list-info>li {
- border-bottom: 1px dotted #ccc;
- padding: 5px;
- font-size: 12px;
- line-height: 20px;
- overflow: hidden;
- width: 96%;
+ border-bottom: 1px dotted #ccc;
+ padding: 5px;
+ font-size: 12px;
+ line-height: 20px;
+ overflow: hidden;
+ width: 96%;
}
.tile-template .list-info>li>label {
- display: inline-block;
- color: #111;
- width: auto;
- text-align: left;
- cursor: pointer;
+ display: inline-block;
+ color: #111;
+ width: auto;
+ text-align: left;
+ cursor: pointer;
}
.tile-template .list-info>li>span {
- float: right;
- color: #444693;
- width: auto;
- text-align: right;
+ float: right;
+ color: #444693;
+ width: auto;
+ text-align: right;
}
.os-icon {
- text-align: center;
+ text-align: center;
}
.os-icon .title {
- display: block;
- font-size: 14px;
- margin-bottom: 5px;
- overflow: hidden;
- width: 260px;
- word-break: break-all;
- word-wrap: break-word;
- height: 50px;
- line-height: 25px;
+ display: block;
+ font-size: 14px;
+ margin-bottom: 5px;
+ overflow: hidden;
+ width: 260px;
+ word-break: break-all;
+ word-wrap: break-word;
+ height: 50px;
+ line-height: 25px;
}
.os-icon img {
- margin-top: 7px;
- width: 64px;
- height: 64px;
+ margin-top: 7px;
+ width: 64px;
+ height: 64px;
}
diff --git a/ui/css/theme-default/template_add.css b/ui/css/theme-default/template_add.css
index 38fa375..511dd98 100644
--- a/ui/css/theme-default/template_add.css
+++ b/ui/css/theme-default/template_add.css
@@ -19,264 +19,264 @@
* limitations under the License.
*/
.page-list {
- position: absolute;
- left: 0;
- right: 0;
- top: 0;
- bottom: 0;
- overflow: hidden;
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ overflow: hidden;
}
.page {
- position: absolute;
- left: 100%;
- width: 100%;
- height: 100%;
- overflow: auto;
+ position: absolute;
+ left: 100%;
+ width: 100%;
+ height: 100%;
+ overflow: auto;
}
.page>header {
- position: relative;
- overflow: hidden;
+ position: relative;
+ overflow: hidden;
}
.back {
- float: left; display : block;
- width: 50px;
- height: 52px;
- background: url(../../images/theme-default/icon-back.png) center center no-repeat;
- cursor: pointer;
- display: block;
+ float: left; display : block;
+ width: 50px;
+ height: 52px;
+ background: url(../../images/theme-default/icon-back.png) center center no-repeat;
+ cursor: pointer;
+ display: block;
}
.step-title {
- color: #333;
- font-size: 18px;
- font-weight: normal;
- padding: 15px 10px;
+ color: #333;
+ font-size: 18px;
+ font-weight: normal;
+ padding: 15px 10px;
}
.step-choose>li>a {
- display: block;
- margin: 0 10px 10px;
- padding: 20px 10px 20px 65px;
- border: 2px solid #ccc;
- background: url(../../images/theme-default/icon-local.png) 15px center no-repeat;
- cursor: pointer;
+ display: block;
+ margin: 0 10px 10px;
+ padding: 20px 10px 20px 65px;
+ border: 2px solid #ccc;
+ background: url(../../images/theme-default/icon-local.png) 15px center no-repeat;
+ cursor: pointer;
}
.step-choose>li>a.local {
- background-image: url(../../images/theme-default/icon-local.png);
+ background-image: url(../../images/theme-default/icon-local.png);
}
.step-choose>li>a.remote {
- background-image: url(../../images/theme-default/icon-remote.png);
+ background-image: url(../../images/theme-default/icon-remote.png);
}
.step-choose>li>a:HOVER {
- border: 2px solid #06C;
+ border: 2px solid #06C;
}
.step-subtitle {
- font-size: 16px;
- height: 48px;
- line-height: 48px;
- color: #06C;
- margin: 0 10px;
- font-weight: bold;
- text-shadow: -1px -1px 1px #eaeaea, 1px 1px 1px #fff;
+ font-size: 16px;
+ height: 48px;
+ line-height: 48px;
+ color: #06C;
+ margin: 0 10px;
+ font-weight: bold;
+ text-shadow: -1px -1px 1px #eaeaea, 1px 1px 1px #fff;
}
.custom-iso-field {
- position: relative;
- padding: 0 10px 10px;
+ position: relative;
+ padding: 0 10px 10px;
}
.custom-iso-field>.input-wrapper {
- margin-right: 110px;
+ margin-right: 110px;
}
.custom-iso-field>.input-wrapper>input.text {
- padding: 10px;
- color: #333;
- font-size: 13px;
- background: #fff;
- -webkit-border-radius: 5px;
- -moz-border-radius: 5px;
- border-radius: 5px;
- box-shadow: 2px 2px 2px #eee inset;
- border-top: 1px solid #bbb;
- border-left: 1px solid #bbb;
- width: 100%;
+ padding: 10px;
+ color: #333;
+ font-size: 13px;
+ background: #fff;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+ box-shadow: 2px 2px 2px #eee inset;
+ border-top: 1px solid #bbb;
+ border-left: 1px solid #bbb;
+ width: 100%;
}
.custom-iso-field>button {
- position: absolute;
- top: -6px;
- right: 8px;
+ position: absolute;
+ top: -6px;
+ right: 8px;
}
.iso-field .button-field {
- padding: 0 20px;
- text-align: right;
+ padding: 0 20px;
+ text-align: right;
}
.check-all {
- display: inline-block;
- position: relative;
- height: 38px;
- line-height: 38px;
- margin: 5px;
- font-size: 13px;
+ display: inline-block;
+ position: relative;
+ height: 38px;
+ line-height: 38px;
+ margin: 5px;
+ font-size: 13px;
}
.check-all input {
- margin: 0 5px 0 0;
+ margin: 0 5px 0 0;
}
.box {
- background: #ffffff;
- background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #e5e5e5));
- background: -webkit-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
- background: -o-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
- background: -ms-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
- background: linear-gradient(to bottom, #ffffff 0%, #e5e5e5 100%);
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e5e5e5', GradientType=0);
- border: 1px solid #ccc;
- color: #333;
- text-shadow: -1px -1px 1px #ccc, 1px 1px 1px #fff;
- -webkit-border-radius: 8px;
- -moz-border-radius: 8px;
- border-radius: 8px;
+ background: #ffffff;
+ background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #e5e5e5));
+ background: -webkit-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
+ background: -o-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
+ background: -ms-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
+ background: linear-gradient(to bottom, #ffffff 0%, #e5e5e5 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e5e5e5', GradientType=0);
+ border: 1px solid #ccc;
+ color: #333;
+ text-shadow: -1px -1px 1px #ccc, 1px 1px 1px #fff;
+ -webkit-border-radius: 8px;
+ -moz-border-radius: 8px;
+ border-radius: 8px;
}
.box:HOVER {
- border: 1px solid #aaa;
- -webkit-box-shadow: #bbb 0px 0px 5px;
- box-shadow: #bbb 0px 0px 5px;
+ border: 1px solid #aaa;
+ -webkit-box-shadow: #bbb 0px 0px 5px;
+ box-shadow: #bbb 0px 0px 5px;
}
.box-iso {
- padding: 10px;
- margin: 5px;
- overflow: hidden;
+ padding: 10px;
+ margin: 5px;
+ overflow: hidden;
}
.iso-icon {
- float: left;
- width: 58px;
- height: 58px;
- margin: 0 5px 0 0;
- border: 1px solid #CCCCCC;
- border-radius: 8px;
- background: url(../../images/icon-vm.png) center center no-repeat;
- background-size: 58px;
+ float: left;
+ width: 58px;
+ height: 58px;
+ margin: 0 5px 0 0;
+ border: 1px solid #CCCCCC;
+ border-radius: 8px;
+ background: url(../../images/icon-vm.png) center center no-repeat;
+ background-size: 58px;
}
.iso-icon.centos {
- background-image: url(../../images/icon-centos.png);
+ background-image: url(../../images/icon-centos.png);
}
.iso-icon.debian {
- background-image: url(../../images/icon-debian.png);
+ background-image: url(../../images/icon-debian.png);
}
.iso-icon.fedora {
- background-image: url(../../images/icon-fedora.png);
+ background-image: url(../../images/icon-fedora.png);
}
.iso-icon.opensuse {
- background-image: url(../../images/icon-opensuse.png);
+ background-image: url(../../images/icon-opensuse.png);
}
.iso-icon.ubuntu {
- background-image: url(../../images/icon-ubuntu.png);
+ background-image: url(../../images/icon-ubuntu.png);
}
.list-iso {
- overflow: hidden;
- margin: 5px;
+ overflow: hidden;
+ margin: 5px;
}
.list-iso li {
- float: left;
- width: 320px;
+ float: left;
+ width: 320px;
}
.list-iso>li>label {
- display: block;
- cursor: pointer;
+ display: block;
+ cursor: pointer;
}
.list-iso>li>label>input[type="checkbox"] {
- display: none;
+ display: none;
}
.list-iso>li>label>input[type="checkbox"]:CHECKED+.box-iso {
- border: 1px solid rgb(11, 107, 173);
- -webkit-box-shadow: rgb(11, 107, 173) 0px 0px 4px;
- box-shadow: rgb(11, 107, 173) 0px 0px 4px;
+ border: 1px solid rgb(11, 107, 173);
+ -webkit-box-shadow: rgb(11, 107, 173) 0px 0px 4px;
+ box-shadow: rgb(11, 107, 173) 0px 0px 4px;
}
.iso-title {
- margin: 0;
- display: block;
- position: relative;
- height: 23px;
- line-height: 23px;
- font-size: 14px;
- font-weight: normal;
- max-width: 100%;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
+ margin: 0;
+ display: block;
+ position: relative;
+ height: 23px;
+ line-height: 23px;
+ font-size: 14px;
+ font-weight: normal;
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
.iso-title>label>input {
- display: block;
- position: absolute;
- top: 0;
- right: 2px;
+ display: block;
+ position: absolute;
+ top: 0;
+ right: 2px;
}
.iso-info {
- margin-top: 5px;
- overflow: hidden;
+ margin-top: 5px;
+ overflow: hidden;
}
.iso-info-col {
- float: left;
- width: 50%;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
- padding: 0 0 0 5px;
+ float: left;
+ width: 50%;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ padding: 0 0 0 5px;
}
.iso-info-col:FIRST-CHILD {
- padding: 0 5px 0 0;
- border-right: 1px solid #999;
+ padding: 0 5px 0 0;
+ border-right: 1px solid #999;
}
.iso-info-item {
- font-weight: bold;
- color: #999;
- font-size: 11px;
- line-height: 18px;
- max-width: 106px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
+ font-weight: bold;
+ color: #999;
+ font-size: 11px;
+ line-height: 18px;
+ max-width: 106px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
#iso-search {
- margin: 10px 15px;
+ margin: 10px 15px;
}
#iso-search-loading {
- margin: 10px 15px;
- background: #C0C0C0 url(../../images/theme-default/loading.gif) 7px center no-repeat;
- padding: 0 20px 0 26px;
+ margin: 10px 15px;
+ background: #C0C0C0 url(../../images/theme-default/loading.gif) 7px center no-repeat;
+ padding: 0 20px 0 26px;
}
#iso-more-loading {
diff --git a/ui/css/theme-default/template_list.css b/ui/css/theme-default/template_list.css
index 8bf5d4c..bd250a9 100644
--- a/ui/css/theme-default/template_list.css
+++ b/ui/css/theme-default/template_list.css
@@ -19,232 +19,232 @@
* limitations under the License.
*/
.list-template.framework {
- float: left;
- clear: both;
+ float: left;
+ clear: both;
}
.template-box {
- border-radius: 8px 8px 8px 8px;
- box-shadow: none;
- color: #666666;
- float: left;
- height: auto;
- margin: 10px 11px 10px 0;
- padding: 10px;
- width: 308px;
+ border-radius: 8px 8px 8px 8px;
+ box-shadow: none;
+ color: #666666;
+ float: left;
+ height: auto;
+ margin: 10px 11px 10px 0;
+ padding: 10px;
+ width: 308px;
}
.template-title {
- font-size: 16px;
- height: 25px;
- line-height: 25px;
+ font-size: 16px;
+ height: 25px;
+ line-height: 25px;
}
.template-icon {
- border: 1px solid #CCCCCC;
- border-radius: 8px 8px 8px 8px;
- height: 58px;
- margin: 0 10px 10px 0;
- width: 48px;
+ border: 1px solid #CCCCCC;
+ border-radius: 8px 8px 8px 8px;
+ height: 58px;
+ margin: 0 10px 10px 0;
+ width: 48px;
}
.template-icon img {
- width: 58px;
+ width: 58px;
}
.template-text {
- color: #999999;
- float: left;
- font-size: 11px;
- font-weight: bold;
- height: 18px;
- line-height: 18px;
- width: 142px;
- display: table;
+ color: #999999;
+ float: left;
+ font-size: 11px;
+ font-weight: bold;
+ height: 18px;
+ line-height: 18px;
+ width: 142px;
+ display: table;
}
.white-box {
- background: #ffffff;
- background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #e5e5e5));
- background: -webkit-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
- background: -o-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
- background: -ms-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
- background: linear-gradient(to bottom, #ffffff 0%, #e5e5e5 100%);
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e5e5e5', GradientType=0);
- border: 1px solid #CCCCCC;
- color: #333333;
- text-shadow: -1px -1px 1px #CCCCCC, 1px 1px 1px #FFFFFF;
+ background: #ffffff;
+ background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #e5e5e5));
+ background: -webkit-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
+ background: -o-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
+ background: -ms-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
+ background: linear-gradient(to bottom, #ffffff 0%, #e5e5e5 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e5e5e5', GradientType=0);
+ border: 1px solid #CCCCCC;
+ color: #333333;
+ text-shadow: -1px -1px 1px #CCCCCC, 1px 1px 1px #FFFFFF;
}
.row-select {
- -moz-border-bottom-colors: none;
- -moz-border-left-colors: none;
- -moz-border-right-colors: none;
- -moz-border-top-colors: none;
- background: linear-gradient(to bottom, #FFFFFF 0%, #E5E5E5 100%) repeat scroll 0 0 transparent;
- border-color: #999999 #AAAAAA #AAAAAA #999999;
- border-image: none;
- border-radius: 5px 5px 5px 5px;
- border-right: 1px solid #AAAAAA;
- border-style: solid;
- border-width: 1px;
- float: left;
- font-size: 13px;
- height: 42px;
- line-height: 42px;
- margin: 5px 0 0 10px;
- padding-left: 10px;
- text-align: left;
- text-shadow: -1px -1px 1px #AAAAAA, 1px 1px 1px #FFFFFF;
- width: 100px;
+ -moz-border-bottom-colors: none;
+ -moz-border-left-colors: none;
+ -moz-border-right-colors: none;
+ -moz-border-top-colors: none;
+ background: linear-gradient(to bottom, #FFFFFF 0%, #E5E5E5 100%) repeat scroll 0 0 transparent;
+ border-color: #999999 #AAAAAA #AAAAAA #999999;
+ border-image: none;
+ border-radius: 5px 5px 5px 5px;
+ border-right: 1px solid #AAAAAA;
+ border-style: solid;
+ border-width: 1px;
+ float: left;
+ font-size: 13px;
+ height: 42px;
+ line-height: 42px;
+ margin: 5px 0 0 10px;
+ padding-left: 10px;
+ text-align: left;
+ text-shadow: -1px -1px 1px #AAAAAA, 1px 1px 1px #FFFFFF;
+ width: 100px;
}
.bevel3 {
- box-shadow: -2px -2px 2px #EAEAEA, 2px 2px 2px #FFFFFF, 3px 3px 3px white inset, -3px -3px 3px rgba(0, 0, 0, 0.25) inset;
- color: #333333;
+ box-shadow: -2px -2px 2px #EAEAEA, 2px 2px 2px #FFFFFF, 3px 3px 3px white inset, -3px -3px 3px rgba(0, 0, 0, 0.25) inset;
+ color: #333333;
}
.row-drop {
- left: 10px;
- position: relative;
- top: 50px;
+ left: 10px;
+ position: relative;
+ top: 50px;
}
.template-action-hidden {
- visibility: hidden;
+ visibility: hidden;
}
.template-action-show {
- visibility: visible;
- display: block;
+ visibility: visible;
+ display: block;
}
template-hidden {
- display: none;
+ display: none;
}
.select-drop {
- background: none repeat scroll 0 0 #EEEEEE;
- border: 2px solid #096AAD;
- border-radius: 5px 5px 5px 5px;
- box-shadow: 6px 6px 6px;
- height: 147px;
- left: 0;
- position: absolute;
- top: 8px;
- width: 250px;
- z-index: 2147483647;
+ background: none repeat scroll 0 0 #EEEEEE;
+ border: 2px solid #096AAD;
+ border-radius: 5px 5px 5px 5px;
+ box-shadow: 6px 6px 6px;
+ height: 147px;
+ left: 0;
+ position: absolute;
+ top: 8px;
+ width: 250px;
+ z-index: 2147483647;
}
.button-drop {
- background: linear-gradient(to bottom, #EEEEEE 0%, #CCCCCC 10px, #CCCCCC 96%, #A5A5A5 100%) repeat scroll 0 0 transparent;
+ background: linear-gradient(to bottom, #EEEEEE 0%, #CCCCCC 10px, #CCCCCC 96%, #A5A5A5 100%) repeat scroll 0 0 transparent;
}
.action-bevel {
- box-shadow: -1px -1px 1px #CCCCCC, 1px 1px 1px #EEEEEE;
+ box-shadow: -1px -1px 1px #CCCCCC, 1px 1px 1px #EEEEEE;
}
.template-border {
- border: 1px solid rgb(204, 204, 204);
+ border: 1px solid rgb(204, 204, 204);
}
.template-button-position {
- position: relative;
- left: 250px;
- top: 55px;
- z-index: 5555;
+ position: relative;
+ left: 250px;
+ top: 55px;
+ z-index: 5555;
}
.tempate-action-position {
- float: right;
- width: 83px;
- margin: 0;
+ float: right;
+ width: 83px;
+ margin: 0;
}
.template-actiontext-position {
- width: 250px;
- height: 160px;
+ width: 250px;
+ height: 160px;
}
.template-line {
- left: 200px;
+ left: 200px;
}
.template-os-position {
- padding-right: 10px;
- clear: both;
- width: 142px;
- border-right: 1px solid #999;
- float: left;
+ padding-right: 10px;
+ clear: both;
+ width: 142px;
+ border-right: 1px solid #999;
+ float: left;
}
.template-cpu-position {
- border-left: 1px solid #eee;
- padding-left: 10px;
- float: left;
- width: 132px;
+ border-left: 1px solid #eee;
+ padding-left: 10px;
+ float: left;
+ width: 132px;
}
.template-icon-position {
- float: left;
- height: 58px;
- width: 58px;
+ float: left;
+ height: 58px;
+ width: 58px;
}
.template-title-position {
- float: left;
- width: 120px;
+ float: left;
+ width: 120px;
}
.template-results {
- background: linear-gradient(to bottom, #FFFFFF 35px, rgba(255, 255, 255, 0) 100%) repeat scroll 0 0 transparent;
- float: left;
- height: 60px;
- margin-bottom: -22px;
- padding-left: 10px;
- width: 1014px;
+ background: linear-gradient(to bottom, #FFFFFF 35px, rgba(255, 255, 255, 0) 100%) repeat scroll 0 0 transparent;
+ float: left;
+ height: 60px;
+ margin-bottom: -22px;
+ padding-left: 10px;
+ width: 1014px;
}
.select-row-action {
- background: linear-gradient(to bottom, #FFFFFF 0%, #E5E5E5 100%) repeat scroll 0 0 transparent;
- border: 1px solid #CCCCCC;
- border-radius: 5px 5px 5px 5px;
- float: left;
- font-size: 13px;
- height: 38px;
- line-height: 38px;
- margin: 10px 10px 0;
- text-align: center;
- text-shadow: -1px -1px 1px #CCCCCC, 1px 1px 1px #FFFFFF;
- width: 230px;
+ background: linear-gradient(to bottom, #FFFFFF 0%, #E5E5E5 100%) repeat scroll 0 0 transparent;
+ border: 1px solid #CCCCCC;
+ border-radius: 5px 5px 5px 5px;
+ float: left;
+ font-size: 13px;
+ height: 38px;
+ line-height: 38px;
+ margin: 10px 10px 0;
+ text-align: center;
+ text-shadow: -1px -1px 1px #CCCCCC, 1px 1px 1px #FFFFFF;
+ width: 230px;
}
.select-row-delete {
- background: linear-gradient(to bottom, #FF3019 0%, #CF0404 100%) repeat scroll 0 0 transparent;
- border: 1px solid #B10F14;
- border-radius: 5px 5px 5px 5px;
- color: #FFFFFF;
- float: left;
- font-size: 13px;
- font-weight: bold;
- height: 38px;
- line-height: 38px;
- margin: 10px 10px 0;
- text-align: center;
- text-shadow: -1px -1px 1px #9E0505, 1px 1px 1px #FC5D4C;
- width: 230px;
+ background: linear-gradient(to bottom, #FF3019 0%, #CF0404 100%) repeat scroll 0 0 transparent;
+ border: 1px solid #B10F14;
+ border-radius: 5px 5px 5px 5px;
+ color: #FFFFFF;
+ float: left;
+ font-size: 13px;
+ font-weight: bold;
+ height: 38px;
+ line-height: 38px;
+ margin: 10px 10px 0;
+ text-align: center;
+ text-shadow: -1px -1px 1px #9E0505, 1px 1px 1px #FC5D4C;
+ width: 230px;
}
.template-general .title {
- color: black;
- font-size: 16px;
- font-weight: normal;
- height: 25px;
- line-height: 25px;
- text-shadow: -1px -1px 1px #ccc, 1px 1px 1px #fff;
- max-width: 130px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
+ color: black;
+ font-size: 16px;
+ font-weight: normal;
+ height: 25px;
+ line-height: 25px;
+ text-shadow: -1px -1px 1px #ccc, 1px 1px 1px #fff;
+ max-width: 130px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
diff --git a/ui/css/theme-default/tile-check.css b/ui/css/theme-default/tile-check.css
index 0e41010..e30e173 100644
--- a/ui/css/theme-default/tile-check.css
+++ b/ui/css/theme-default/tile-check.css
@@ -19,30 +19,30 @@
* limitations under the License.
*/
.tile-check {
- overflow: hidden;
+ overflow: hidden;
}
.tile-check>li {
- float: left;
- padding: 5px;
+ float: left;
+ padding: 5px;
}
.tile-check>li>label {
- display: block;
- cursor: pointer;
+ display: block;
+ cursor: pointer;
}
.tile-check>li>label>input[type="radio"] {
- display: none;
+ display: none;
}
.tile-check>li>label>.info {
- display: block;
- position: relative;
- overflow: hidden;
- border: 4px solid #ccc;
+ display: block;
+ position: relative;
+ overflow: hidden;
+ border: 4px solid #ccc;
}
.tile-check>li>label>input[type="radio"]:CHECKED+.info {
- border: 4px solid #06C;
+ border: 4px solid #06C;
}
diff --git a/ui/css/theme-default/toolbar.css b/ui/css/theme-default/toolbar.css
index 6bd1037..5f47ed8 100644
--- a/ui/css/theme-default/toolbar.css
+++ b/ui/css/theme-default/toolbar.css
@@ -21,34 +21,34 @@
/* Generated at http://colorzilla.com/gradient-editor/ */
.toolbar {
- position: relative;
- height: 48px;
- padding: 0 5px;
- overflow: hidden;
- background: #e5e5e5;
- background: -moz-linear-gradient(top, #e5e5e5 0%, #c4c4c4 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #e5e5e5), color-stop(100%, #c4c4c4));
- background: -webkit-linear-gradient(top, #e5e5e5 0%, #c4c4c4 100%);
- background: -o-linear-gradient(top, #e5e5e5 0%, #c4c4c4 100%);
- background: -ms-linear-gradient(top, #e5e5e5 0%, #c4c4c4 100%);
- background: linear-gradient(to bottom, #e5e5e5 0%, #c4c4c4 100%);
- filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#e5e5e5', endColorstr='#c4c4c4', GradientType=0);
- border-bottom: 1px solid #aaa;
+ position: relative;
+ height: 48px;
+ padding: 0 5px;
+ overflow: hidden;
+ background: #e5e5e5;
+ background: -moz-linear-gradient(top, #e5e5e5 0%, #c4c4c4 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #e5e5e5), color-stop(100%, #c4c4c4));
+ background: -webkit-linear-gradient(top, #e5e5e5 0%, #c4c4c4 100%);
+ background: -o-linear-gradient(top, #e5e5e5 0%, #c4c4c4 100%);
+ background: -ms-linear-gradient(top, #e5e5e5 0%, #c4c4c4 100%);
+ background: linear-gradient(to bottom, #e5e5e5 0%, #c4c4c4 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#e5e5e5', endColorstr='#c4c4c4', GradientType=0);
+ border-bottom: 1px solid #aaa;
}
.toolbar .filters {
- float: left;
+ float: left;
}
.toolbar .tools {
- float: right;
+ float: right;
}
.toolbar .divide {
- display: inline-block;
- height: 42px;
- margin: 3px;
- vertical-align: top;
- border-left: 1px solid #999;
- border-right: 1px solid #eee;
+ display: inline-block;
+ height: 42px;
+ margin: 3px;
+ vertical-align: top;
+ border-left: 1px solid #999;
+ border-right: 1px solid #eee;
}
diff --git a/ui/css/theme-default/topbar.css b/ui/css/theme-default/topbar.css
index 42859cc..6a5aec6 100644
--- a/ui/css/theme-default/topbar.css
+++ b/ui/css/theme-default/topbar.css
@@ -21,147 +21,147 @@
/* Generated at http://colorzilla.com/gradient-editor/ */
.topbar {
- height: 48px;
- background: #4a4a4a; /* Old browsers */
- background: -moz-linear-gradient(top, #4a4a4a 0%, #272727 100%); /* FF3.6+ */
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #4a4a4a), color-stop(100%, #272727));
- /* Chrome,Safari4+ */
- background: -webkit-linear-gradient(top, #4a4a4a 0%, #272727 100%); /* Chrome10+,Safari5.1+ */
- background: -o-linear-gradient(top, #4a4a4a 0%, #272727 100%); /* Opera 11.10+ */
- background: -ms-linear-gradient(top, #4a4a4a 0%, #272727 100%); /* IE10+ */
- background: linear-gradient(to bottom, #4a4a4a 0%, #272727 100%); /* W3C */
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#4a4a4a', endColorstr='#272727', GradientType=0);
- border-top: 1px solid #6b6868;
- boder-bottom: 1px solid #151718;
- position: relative;
+ height: 48px;
+ background: #4a4a4a; /* Old browsers */
+ background: -moz-linear-gradient(top, #4a4a4a 0%, #272727 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #4a4a4a), color-stop(100%, #272727));
+ /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #4a4a4a 0%, #272727 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #4a4a4a 0%, #272727 100%); /* Opera 11.10+ */
+ background: -ms-linear-gradient(top, #4a4a4a 0%, #272727 100%); /* IE10+ */
+ background: linear-gradient(to bottom, #4a4a4a 0%, #272727 100%); /* W3C */
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#4a4a4a', endColorstr='#272727', GradientType=0);
+ border-top: 1px solid #6b6868;
+ boder-bottom: 1px solid #151718;
+ position: relative;
}
#logo {
- height: 48px;
- line-height: 48px;
- padding: 0 10px;
- font-size: 20px;
- font-weight: normal;
- color: #eee;
- text-shadow: -1px -1px 1px #000, 1px 1px 1px #555;
- vertical-align: top;
- max-width: 250px;
- overflow: hidden;
+ height: 48px;
+ line-height: 48px;
+ padding: 0 10px;
+ font-size: 20px;
+ font-weight: normal;
+ color: #eee;
+ text-shadow: -1px -1px 1px #000, 1px 1px 1px #555;
+ vertical-align: top;
+ max-width: 250px;
+ overflow: hidden;
}
.nav-top {
- position: absolute;
- right: 0;
- top: 0;
- border-left: 1px solid #000;
- border-right: 1px solid #585858;
+ position: absolute;
+ right: 0;
+ top: 0;
+ border-left: 1px solid #000;
+ border-right: 1px solid #585858;
}
.nav-top>li {
- float: left;
- border-left: 1px solid #585858;
- border-right: 1px solid #000;
+ float: left;
+ border-left: 1px solid #585858;
+ border-right: 1px solid #000;
}
.nav-top .icon {
- display: block;
- position: relative;
- width: 58px;
- height: 48px;
+ display: block;
+ position: relative;
+ width: 58px;
+ height: 48px;
}
.nav-top .icon .count {
- position: absolute;
- top: 3px;
- right: 3px;
- height: 16px;
- line-height: 17px;
- padding: 0 5px;
- -webkit-border-radius: 12px;
- -moz-border-radius: 12px;
- border-radius: 12px;
- font-size: 10px;
- color: #fff;
- text-align: center;
- background: #06C;
- text-shadow: none;
+ position: absolute;
+ top: 3px;
+ right: 3px;
+ height: 16px;
+ line-height: 17px;
+ padding: 0 5px;
+ -webkit-border-radius: 12px;
+ -moz-border-radius: 12px;
+ border-radius: 12px;
+ font-size: 10px;
+ color: #fff;
+ text-align: center;
+ background: #06C;
+ text-shadow: none;
}
.icon.setting {
- background: url(../images/theme-default/icon-setting.png) no-repeat center center;
+ background: url(../images/theme-default/icon-setting.png) no-repeat center center;
}
.icon.tool {
- background: url(../images/theme-default/icon-tool.png) no-repeat center center;
+ background: url(../images/theme-default/icon-tool.png) no-repeat center center;
}
.icon.event {
- background: url(../images/theme-default/icon-event.png) no-repeat center center;
+ background: url(../images/theme-default/icon-event.png) no-repeat center center;
}
.icon.alert {
- background: url(../images/theme-default/icon-alert.png) no-repeat center center;
+ background: url(../images/theme-default/icon-alert.png) no-repeat center center;
}
#user {
- color: white;
- cursor: pointer;
- display: block;
- position: relative;
- height: 48px;
- margin: 0 12px;
+ color: white;
+ cursor: pointer;
+ display: block;
+ position: relative;
+ height: 48px;
+ margin: 0 12px;
}
#user span {
- display: inline-block;
- margin-top: 16px;
+ display: inline-block;
+ margin-top: 16px;
}
#user:hover #user-name {
- border-bottom: 1px solid white;
+ border-bottom: 1px solid white;
}
#user.not-logged-in {
- display: none;
+ display: none;
}
#user-icon {
- background: url("/images/theme-default/user-icon.png") no-repeat left top;
- height: 16px;
- width: 16px;
+ background: url("/images/theme-default/user-icon.png") no-repeat left top;
+ height: 16px;
+ width: 16px;
}
#user-name {
- height: 16px;
- line-height: 16px;
+ height: 16px;
+ line-height: 16px;
}
#user .arrow {
- border: 6px solid transparent;
- border-bottom: none;
- border-top-color: white;
- display: inline-block;
- width: 0;
+ border: 6px solid transparent;
+ border-bottom: none;
+ border-top-color: white;
+ display: inline-block;
+ width: 0;
}
#user .action-panel {
- top: 45px;
- color: black;
- padding: 12px 18px;
- white-space: nowrap;
+ top: 45px;
+ color: black;
+ padding: 12px 18px;
+ white-space: nowrap;
}
#btn-logout {
- color: black;
- font-size: 14px;
+ color: black;
+ font-size: 14px;
}
a#btn-logout:hover {
- text-decoration: underline;
+ text-decoration: underline;
}
@media ( max-width : 640px) {
- #logo {
- display: none;
- }
+ #logo {
+ display: none;
+ }
}
diff --git a/ui/css/theme-default/window.css b/ui/css/theme-default/window.css
index 712bdb7..0a95dce 100644
--- a/ui/css/theme-default/window.css
+++ b/ui/css/theme-default/window.css
@@ -20,98 +20,98 @@
*/
/* Hide the "Build Me" warning */
#buildme {
- display: none;
+ display: none;
}
.bgmask {
- position: fixed;
- left: 0;
- right: 0;
- top: 0;
- bottom: 0;
- background: url(../images/theme-default/bg-mask.png);
- z-index: 100;
- overflow: hidden;
+ position: fixed;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ background: url(../images/theme-default/bg-mask.png);
+ z-index: 100;
+ overflow: hidden;
}
.window {
- position: absolute;
- margin: auto;
- left: 0;
- right: 0;
- top: 0;
- bottom: 0;
- box-shadow: 2px 2px 6px #000;
- border: 2px solid #0f71b4;
- -webkit-border-radius: 8px;
- -moz-border-radius: 8px;
- border-radius: 8px;
- background-color: #eee;
- box-sizing: border-box;
- max-width: 100%;
- max-height: 100%;
+ position: absolute;
+ margin: auto;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ box-shadow: 2px 2px 6px #000;
+ border: 2px solid #0f71b4;
+ -webkit-border-radius: 8px;
+ -moz-border-radius: 8px;
+ border-radius: 8px;
+ background-color: #eee;
+ box-sizing: border-box;
+ max-width: 100%;
+ max-height: 100%;
}
.window>header {
- position: absolute;
- left: 0;
- right: 0;
- top: 0;
- height: 48px;
- z-index: 100;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.05);
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ height: 48px;
+ z-index: 100;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.05);
}
.window footer {
- position: absolute;
- left: 0;
- right: 0;
- bottom: 0;
- height: 56px;
- z-index: 100;
- box-shadow: 0 -1px 1px rgba(0, 0, 0, 0.15);
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ height: 56px;
+ z-index: 100;
+ box-shadow: 0 -1px 1px rgba(0, 0, 0, 0.15);
}
.window .content {
- position: absolute;
- left: 0;
- right: 0;
- top: 0;
- bottom: 0;
- overflow: auto;
- margin: 48px 0;
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ overflow: auto;
+ margin: 48px 0;
}
.window .close {
- position: absolute;
- width: 30px;
- height: 30px;
- top: 7px;
- right: 7px;
- -webkit-border-radius: 30px;
- -moz-border-radius: 30px;
- border-radius: 30px;
- border: 2px solid #ccc;
- color: #ccc;
- font-size: 24px;
- text-align: center;
- line-height: 30px;
- font-weight: bold;
- background: #eee;
- cursor: pointer;
+ position: absolute;
+ width: 30px;
+ height: 30px;
+ top: 7px;
+ right: 7px;
+ -webkit-border-radius: 30px;
+ -moz-border-radius: 30px;
+ border-radius: 30px;
+ border: 2px solid #ccc;
+ color: #ccc;
+ font-size: 24px;
+ text-align: center;
+ line-height: 30px;
+ font-weight: bold;
+ background: #eee;
+ cursor: pointer;
}
.window>header>.close:HOVER {
- border: 2px solid #444;
- color: #444;
+ border: 2px solid #444;
+ color: #444;
}
.window>header>.title {
- font-size: 18px;
- height: 48px;
- line-height: 48px;
- color: #06C;
- margin: 0 10px;
- font-weight: bold;
- text-shadow: -1px -1px 1px #eaeaea, 1px 1px 1px #fff;
+ font-size: 18px;
+ height: 48px;
+ line-height: 48px;
+ color: #06C;
+ margin: 0 10px;
+ font-weight: bold;
+ text-shadow: -1px -1px 1px #eaeaea, 1px 1px 1px #fff;
}
diff --git a/ui/js/dev.main.js b/ui/js/dev.main.js
index 23546e5..8a68989 100644
--- a/ui/js/dev.main.js
+++ b/ui/js/dev.main.js
@@ -232,13 +232,13 @@ function load(data)
{
console.log("load");
$.ajax({
- url: "/vms",
- dataType: "json"
+ url: "/vms",
+ dataType: "json"
}).done(load_vms);
$.ajax({
- url: "/templates",
- dataType: "json"
+ url: "/templates",
+ dataType: "json"
}).done(load_templates);
$.ajax({
diff --git a/ui/js/src/kimchi.template_main.js b/ui/js/src/kimchi.template_main.js
index 932fa70..ffc7306 100644
--- a/ui/js/src/kimchi.template_main.js
+++ b/ui/js/src/kimchi.template_main.js
@@ -10,7 +10,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
diff --git a/ui/pages/guest-add.html.tmpl b/ui/pages/guest-add.html.tmpl
index e91d57d..36a0f35 100644
--- a/ui/pages/guest-add.html.tmpl
+++ b/ui/pages/guest-add.html.tmpl
@@ -28,68 +28,68 @@
<html>
<body>
<div class="window" style="width: 900px;height: 580px;">
- <header>
- <h1 class="title">$_("Create a New Virtual Machine")</h1>
- <div class="close">X</div>
- </header>
- <div class="content">
- <form id="form-vm-add">
- <section class="form-section">
- <h2>1. $_("Virtual Machine Name")</h2>
- <div class="field">
- <p class="text-help">
- $_("The name used to identify the VM. If omitted, a name will be chosen based on the template used.")
- </p>
- <input type="text" class="text" style="width: 300px" name="name">
- </div>
- </section>
- <section class="form-section">
- <h2>2. $_("Template")</h2>
- <div class="field">
- <div class="text-help">
- <div id="prompt-create-template" class="hidden">
- <div>$_("Please create a template first.")</div>
- <a id="btn-create-template" class="btn-normal" href="templates.html">
- <span class="text">$_("Create a Template")</span>
- </a>
- </div>
- <div id="prompt-choose-template" class="hidden">
- <span>$_("Please choose a template.")</span>
- </div>
- </div>
- <ul id="templateTile" class="tile-check tile-template">
- </ul>
- <script type="html/text" id="tmpl-template" class="tmpl-html">
- <li>
- <label>
- <input type="radio" name="template" value="/templates/{name}">
- <div class="info">
- <div class="summary os-icon">
- <img src="{icon}">
- <span class="title">{name}</span>
- </div>
- <ul class="list-info">
- <li><label>$_("OS")</label><span>{os_distro}</span></li>
- <li><label>$_("OS Version")</label><span>{os_version}</span></li>
- <li><label>$_("CPUS")</label><span>{cpus}</span></li>
- <li><label>$_("Memory")</label><span>{memory}M</span></li>
- </ul>
- </div>
- </label>
- </li>
- </script>
- </div>
- </section>
- </form>
- </div>
- <footer>
- <div class="btn-group">
- <button id="vm-doAdd" class="btn-normal" disabled="disabled" href="javascript:void(0);"><span class="text">$_("Create")</span></button>
- </div>
- </footer>
+ <header>
+ <h1 class="title">$_("Create a New Virtual Machine")</h1>
+ <div class="close">X</div>
+ </header>
+ <div class="content">
+ <form id="form-vm-add">
+ <section class="form-section">
+ <h2>1. $_("Virtual Machine Name")</h2>
+ <div class="field">
+ <p class="text-help">
+ $_("The name used to identify the VM. If omitted, a name will be chosen based on the template used.")
+ </p>
+ <input type="text" class="text" style="width: 300px" name="name">
+ </div>
+ </section>
+ <section class="form-section">
+ <h2>2. $_("Template")</h2>
+ <div class="field">
+ <div class="text-help">
+ <div id="prompt-create-template" class="hidden">
+ <div>$_("Please create a template first.")</div>
+ <a id="btn-create-template" class="btn-normal" href="templates.html">
+ <span class="text">$_("Create a Template")</span>
+ </a>
+ </div>
+ <div id="prompt-choose-template" class="hidden">
+ <span>$_("Please choose a template.")</span>
+ </div>
+ </div>
+ <ul id="templateTile" class="tile-check tile-template">
+ </ul>
+ <script type="html/text" id="tmpl-template" class="tmpl-html">
+ <li>
+ <label>
+ <input type="radio" name="template" value="/templates/{name}">
+ <div class="info">
+ <div class="summary os-icon">
+ <img src="{icon}">
+ <span class="title">{name}</span>
+ </div>
+ <ul class="list-info">
+ <li><label>$_("OS")</label><span>{os_distro}</span></li>
+ <li><label>$_("OS Version")</label><span>{os_version}</span></li>
+ <li><label>$_("CPUS")</label><span>{cpus}</span></li>
+ <li><label>$_("Memory")</label><span>{memory}M</span></li>
+ </ul>
+ </div>
+ </label>
+ </li>
+ </script>
+ </div>
+ </section>
+ </form>
+ </div>
+ <footer>
+ <div class="btn-group">
+ <button id="vm-doAdd" class="btn-normal" disabled="disabled" href="javascript:void(0);"><span class="text">$_("Create")</span></button>
+ </div>
+ </footer>
</div>
<script>
- kimchi.guest_add_main();
+ kimchi.guest_add_main();
</script>
</body>
</html>
diff --git a/ui/pages/guest.html.tmpl b/ui/pages/guest.html.tmpl
index 3ae1687..6bd6853 100644
--- a/ui/pages/guest.html.tmpl
+++ b/ui/pages/guest.html.tmpl
@@ -24,44 +24,44 @@
#silent t = gettext.translation($lang.domain, $lang.localedir, languages=$lang.lang)
#silent _ = t.gettext
#silent _t = t.gettext
- <li id="{name}">
- <div class="sortable guest-type">
- <div class="guest-general">
- <h2 class="title" title="{name}">{name}</h2>
- </div>
- </div>
- <div class="sortable guest-cpu">
- <div class="circle" data-value="{stats}"></div>
- </div>
- <div class="sortable guest-network">
- <div class="circle" data-value="{stats}"></div>
+ <li id="{name}">
+ <div class="sortable guest-type">
+ <div class="guest-general">
+ <h2 class="title" title="{name}">{name}</h2>
+ </div>
+ </div>
+ <div class="sortable guest-cpu">
+ <div class="circle" data-value="{stats}"></div>
+ </div>
+ <div class="sortable guest-network">
+ <div class="circle" data-value="{stats}"></div>
<div class="subtitle">KB/s</div>
- </div>
- <div class="sortable guest-storage">
- <div class="circle" data-value="{stats}"></div>
+ </div>
+ <div class="sortable guest-storage">
+ <div class="circle" data-value="{stats}"></div>
<div class="subtitle">KB/s</div>
- </div>
- <div class="sortable guest-tile">
- <div class="tile {state}">
- <img class="imgactive" alt="" src="{tile-src}">
- <img class="imgload" alt="" src="{load-src}">
- </div>
- </div>
- <div class="sortable guest-actions">
- <div class="top">
- <a class="btn vm-reset" data-vmstate="{state}" data-vm="{name}" href="javascript:void(0);" title="$_("Reset")"><span class="icon reset"></span></a>
- <a class="btn vm-start" data-vmstate="{state}" data-vm="{name}" href="javascript:void(0);" title="$_("Start")"><span class="icon power-down"></span></a>
- <a class="btn vm-stop" data-vmstate="{state}" data-vm="{name}" href="javascript:void(0);" title="$_("Stop")"><span class="icon power-up"></span></a>
- </div>
- <div class="bottom">
- <div class="btn dropdown popable vm-action" data-vmstate="{state}" data-graphics="{graphics.type}" data-vm="{name}" style="width: 70px">
- <span class="text">$_("Actions")</span><span class="arrow"></span>
- <div class="popover actionsheet right-side" style="width: 250px">
- <button class="button-big vm-vnc" data-vm="{name}"><span class="text">VNC</span></button>
- <button class="button-big vm-edit" data-vm="{name}"><span class="text">$_("Edit")</span></button>
- <a class="button-big red vm-delete" data-vm="{name}">$_("Delete")</a>
- </div>
- </div>
- </div>
- </div>
- </li>
+ </div>
+ <div class="sortable guest-tile">
+ <div class="tile {state}">
+ <img class="imgactive" alt="" src="{tile-src}">
+ <img class="imgload" alt="" src="{load-src}">
+ </div>
+ </div>
+ <div class="sortable guest-actions">
+ <div class="top">
+ <a class="btn vm-reset" data-vmstate="{state}" data-vm="{name}" href="javascript:void(0);" title="$_("Reset")"><span class="icon reset"></span></a>
+ <a class="btn vm-start" data-vmstate="{state}" data-vm="{name}" href="javascript:void(0);" title="$_("Start")"><span class="icon power-down"></span></a>
+ <a class="btn vm-stop" data-vmstate="{state}" data-vm="{name}" href="javascript:void(0);" title="$_("Stop")"><span class="icon power-up"></span></a>
+ </div>
+ <div class="bottom">
+ <div class="btn dropdown popable vm-action" data-vmstate="{state}" data-graphics="{graphics.type}" data-vm="{name}" style="width: 70px">
+ <span class="text">$_("Actions")</span><span class="arrow"></span>
+ <div class="popover actionsheet right-side" style="width: 250px">
+ <button class="button-big vm-vnc" data-vm="{name}"><span class="text">VNC</span></button>
+ <button class="button-big vm-edit" data-vm="{name}"><span class="text">$_("Edit")</span></button>
+ <a class="button-big red vm-delete" data-vm="{name}">$_("Delete")</a>
+ </div>
+ </div>
+ </div>
+ </div>
+ </li>
diff --git a/ui/pages/kimchi-ui.html.tmpl b/ui/pages/kimchi-ui.html.tmpl
index 5d8f583..8e234e2 100644
--- a/ui/pages/kimchi-ui.html.tmpl
+++ b/ui/pages/kimchi-ui.html.tmpl
@@ -65,28 +65,28 @@
<body>
<div class="container">
<header class="topbar">
- <h1 id="logo"><img alt="Project Kimchi" src="images/theme-default/logo-white.png"></h1>
- <ul class="nav-top">
- <li>
- <div id="user" class="popable">
- <span id="user-icon"></span>
- <span id="user-name"></span>
- <span class="arrow"></span>
- <div class="action-panel popover right-side">
- <a id="btn-logout" href="javascript: void(0);">$_("Log out")</a>
- </div>
- </div>
- </li>
- </ul>
+ <h1 id="logo"><img alt="Project Kimchi" src="images/theme-default/logo-white.png"></h1>
+ <ul class="nav-top">
+ <li>
+ <div id="user" class="popable">
+ <span id="user-icon"></span>
+ <span id="user-name"></span>
+ <span class="arrow"></span>
+ <div class="action-panel popover right-side">
+ <a id="btn-logout" href="javascript: void(0);">$_("Log out")</a>
+ </div>
+ </div>
+ </li>
+ </ul>
</header>
<div class="content">
- <nav class="navbar">
- <ul id="nav-menu" class="nav-menu">
- <li class="menu-arrow"></li>
- </ul>
- </nav>
- <div id="main">
- </div>
+ <nav class="navbar">
+ <ul id="nav-menu" class="nav-menu">
+ <li class="menu-arrow"></li>
+ </ul>
+ </nav>
+ <div id="main">
+ </div>
</div>
</div>
<script src="$href("libs/jquery-1.10.0.min.js")"></script>
diff --git a/ui/pages/report-add.html.tmpl b/ui/pages/report-add.html.tmpl
index c9214ce..2a962d4 100644
--- a/ui/pages/report-add.html.tmpl
+++ b/ui/pages/report-add.html.tmpl
@@ -26,32 +26,32 @@
#silent _t = t.gettext
<!DOCTYPE html>
<div id="report-add-window" class="window">
- <header>
- <h1 class="title">$_("Generate a New Debug Report")</h1>
- <div class="close">X</div>
- </header>
- <div class="content">
- <form id="form-report-add">
- <section class="form-section">
- <h2>
- <label for="report-name-textbox">$_("Report Name")</label>
- </h2>
- <div class="field">
- <span>
- $_("The name used to identify the report. If omitted, a name will be chosen based on current time. Name can contain: letters, digits, \"-\", \"_\", or \".\".")
- </span>
- <input type="text" class="text" id="report-name-textbox" name="name" />
- <span id="report-error-message"></span>
- </div>
- </section>
- </form>
- </div>
- <footer>
- <div class="btn-group">
- <button id="button-report-add" class="btn-normal"><span class="text">$_("Generate")</span></button>
- </div>
- </footer>
+ <header>
+ <h1 class="title">$_("Generate a New Debug Report")</h1>
+ <div class="close">X</div>
+ </header>
+ <div class="content">
+ <form id="form-report-add">
+ <section class="form-section">
+ <h2>
+ <label for="report-name-textbox">$_("Report Name")</label>
+ </h2>
+ <div class="field">
+ <span>
+ $_("The name used to identify the report. If omitted, a name will be chosen based on current time. Name can contain: letters, digits, \"-\", \"_\", or \".\".")
+ </span>
+ <input type="text" class="text" id="report-name-textbox" name="name" />
+ <span id="report-error-message"></span>
+ </div>
+ </section>
+ </form>
+ </div>
+ <footer>
+ <div class="btn-group">
+ <button id="button-report-add" class="btn-normal"><span class="text">$_("Generate")</span></button>
+ </div>
+ </footer>
</div>
<script>
- kimchi.report_add_main();
+ kimchi.report_add_main();
</script>
diff --git a/ui/pages/storagepool-add.html.tmpl b/ui/pages/storagepool-add.html.tmpl
index d7b046d..3ab9ae5 100644
--- a/ui/pages/storagepool-add.html.tmpl
+++ b/ui/pages/storagepool-add.html.tmpl
@@ -27,89 +27,89 @@
<!DOCTYPE html>
<html>
<body>
- <div class="window" style="width: 600px; height: 600px;">
- <header>
- <h1 class="title">$_("Define a New Storage Pool")</h1>
- <div class="close">X</div>
- </header>
- <div class="content">
- <form id="form-pool-add">
- <section class="form-section">
- <h2>1. $_("Storage Pool Name")</h2>
- <div class="field">
- <p class="text-help">
+ <div class="window" style="width: 600px; height: 600px;">
+ <header>
+ <h1 class="title">$_("Define a New Storage Pool")</h1>
+ <div class="close">X</div>
+ </header>
+ <div class="content">
+ <form id="form-pool-add">
+ <section class="form-section">
+ <h2>1. $_("Storage Pool Name")</h2>
+ <div class="field">
+ <p class="text-help">
$_("The name used to identify the storage pools, and it should not be empty.")
</p>
- <input id="poolId" type="text" class="text" style="width: 300px"
- name="name">
- </div>
- </section>
- <section class="form-section">
- <h2>2. $_("Storage Pool Type")</h2>
- <div class="storage-type-wrapper-controls">
- <div class="btn dropdown popable">
- <input id="poolType" name="type" type="hidden" value="dir"/>
- <span class="text" id="pool-type-label"></span><span class="arrow"></span>
- <div class="popover" style="width: 100%">
- <ul class="select-list" id="storagePool-list" data-target="poolType" data-label="pool-type-label">
- </ul>
- </div>
- </div>
+ <input id="poolId" type="text" class="text" style="width: 300px"
+ name="name">
</div>
- </section>
+ </section>
+ <section class="form-section">
+ <h2>2. $_("Storage Pool Type")</h2>
+ <div class="storage-type-wrapper-controls">
+ <div class="btn dropdown popable">
+ <input id="poolType" name="type" type="hidden" value="dir"/>
+ <span class="text" id="pool-type-label"></span><span class="arrow"></span>
+ <div class="popover" style="width: 100%">
+ <ul class="select-list" id="storagePool-list" data-target="poolType" data-label="pool-type-label">
+ </ul>
+ </div>
+ </div>
+ </div>
+ </section>
<div class="path-section">
- <section class="form-section">
- <h2>3. $_("Storage Path")</h2>
- <div class="field">
- <p class="text-help">
+ <section class="form-section">
+ <h2>3. $_("Storage Path")</h2>
+ <div class="field">
+ <p class="text-help">
$_("The path of the Storage Pool. Each Storage Pool must have a unique path.")</p>
- <input id="pathId" type="text" class="text" style="width: 300px">
- </div>
- <div class="clear"></div>
- </section>
+ <input id="pathId" type="text" class="text" style="width: 300px">
+ </div>
+ <div class="clear"></div>
+ </section>
</div>
<div class="nfs-section tmpl-html">
- <section class="form-section">
- <h2>3. $_("NFS server IP")</h2>
- <div class="field">
- <p class="text-help">
+ <section class="form-section">
+ <h2>3. $_("NFS server IP")</h2>
+ <div class="field">
+ <p class="text-help">
$_("NFS server IP or hostname. It should not be empty.")</p>
- <input id="nfsserverId" type="text" class="text"
- style="width: 300px">
- </div>
- </section>
- <section class="form-section">
- <h2>4. $_("NFS Path")</h2>
- <div class="field">
- <p class="text-help">$_("The nfs exported path on nfs server")</p>
- <input id="nfspathId" type="text" class="text"
- style="width: 300px">
+ <input id="nfsserverId" type="text" class="text"
+ style="width: 300px">
+ </div>
+ </section>
+ <section class="form-section">
+ <h2>4. $_("NFS Path")</h2>
+ <div class="field">
+ <p class="text-help">$_("The nfs exported path on nfs server")</p>
+ <input id="nfspathId" type="text" class="text"
+ style="width: 300px">
<input type="hidden" id="localpathId" class="text"
- value="none">
- </div>
- <div class="clear"></div>
- </section>
- </div>
- <div class="logical-section tmpl-html">
- <section class="form-section storageType">
- <h2>3. $_("Device Path")</h2>
- <div class="host-partition"></div>
- </section>
- </div>
- </form>
- </div>
- <footer>
- <div class="btn-group">
- <button id="pool-doAdd" class="btn-normal">
- <span class="text">$_("Create")</span>
- </button>
- </div>
- </footer>
- </div>
- <script>
+ value="none">
+ </div>
+ <div class="clear"></div>
+ </section>
+ </div>
+ <div class="logical-section tmpl-html">
+ <section class="form-section storageType">
+ <h2>3. $_("Device Path")</h2>
+ <div class="host-partition"></div>
+ </section>
+ </div>
+ </form>
+ </div>
+ <footer>
+ <div class="btn-group">
+ <button id="pool-doAdd" class="btn-normal">
+ <span class="text">$_("Create")</span>
+ </button>
+ </div>
+ </footer>
+ </div>
+ <script>
kimchi.storagepool_add_main();
</script>
- <script id="partitionTmpl" type="html/text">
+ <script id="partitionTmpl" type="html/text">
<div>
<input type="checkbox" value="{path}" name="devices">
<label>{path}</label>
diff --git a/ui/pages/tabs/guests.html.tmpl b/ui/pages/tabs/guests.html.tmpl
index 36da9f1..d73eb39 100644
--- a/ui/pages/tabs/guests.html.tmpl
+++ b/ui/pages/tabs/guests.html.tmpl
@@ -30,29 +30,29 @@
<html>
<body>
<div id="guests-root-container">
- <div class="toolbar">
- <div class="tools">
- <a id="vm-add" class="btn-tool" href="javascript:void(0);"><span class="icon add">+</span></a>
- </div>
- </div>
- <div id="guestListField" style="display: none;">
- <ul class="list-title">
- <li class="guest-type">$_("Name")</li>
- <li class="guest-cpu">$_("CPU")</li>
- <li class="guest-network">$_("Network I/O")</li>
- <li class="guest-storage">$_("Disk I/O")</li>
- <li class="guest-tile">$_("Livetile")</li>
- <li class="guest-actions">$_("Actions")</li>
- </ul>
- <ul id="guestList" class="list-vm">
- </ul>
- </div>
- <div id="noGuests" class="list-no-result" style="display: none;">
- $_("No guests found.")
- </div>
+ <div class="toolbar">
+ <div class="tools">
+ <a id="vm-add" class="btn-tool" href="javascript:void(0);"><span class="icon add">+</span></a>
+ </div>
+ </div>
+ <div id="guestListField" style="display: none;">
+ <ul class="list-title">
+ <li class="guest-type">$_("Name")</li>
+ <li class="guest-cpu">$_("CPU")</li>
+ <li class="guest-network">$_("Network I/O")</li>
+ <li class="guest-storage">$_("Disk I/O")</li>
+ <li class="guest-tile">$_("Livetile")</li>
+ <li class="guest-actions">$_("Actions")</li>
+ </ul>
+ <ul id="guestList" class="list-vm">
+ </ul>
+ </div>
+ <div id="noGuests" class="list-no-result" style="display: none;">
+ $_("No guests found.")
+ </div>
</div>
<script>
- kimchi.guest_main();
+ kimchi.guest_main();
</script>
</body>
</html>
diff --git a/ui/pages/tabs/host.html.tmpl b/ui/pages/tabs/host.html.tmpl
index 01c817f..aa4ecfb 100644
--- a/ui/pages/tabs/host.html.tmpl
+++ b/ui/pages/tabs/host.html.tmpl
@@ -26,121 +26,121 @@
#silent _ = t.gettext
#silent _t = t.gettext
<div id="host-root-container">
- <div class="toolbar">
- <div class="tools">
- </div>
- </div>
- <div id="host-content-container"></div>
+ <div class="toolbar">
+ <div class="tools">
+ </div>
+ </div>
+ <div id="host-content-container"></div>
</div>
<script id="host-tmpl" type="kimchi/template">
- <div class="host-panel">
- <div class="logo-container">
- <div class="logo" style="background-image: url({logo});"></div>
- </div>
- <div id="host-info-container" class="info-container">
- <h2 class="hostname">{hostname}</h2>
- <div class="action-panel">
- <button id="host-button-shutdown" class="btn-normal stop">
- <div class="button-icon action-icon-stop"></div>
- $_("Shut down")
- </button>
- <button id="host-button-restart" class="btn-normal restart">
- <div class="button-icon action-icon-restart"></div>
- $_("Restart")
- </button>
- <button class="btn-normal connect" disabled="disabled">
- <div class="button-icon action-icon-connect"></div>
- $_("Connect")
- </button>
- </div>
- <div class="host-section">
- <h3 class="section-header"
- aria-expanded="false"
- aria-controls="content-sys-info">
- $_("Basic Information")
- </h3>
- <div id="content-sys-info" class="section-content">
- <div class="section-row">
- <div class="section-label">$_("OS Distro")</div>
- <div class="section-value">{os_distro}</div>
- </div>
- <div class="section-row">
- <div class="section-label">$_("OS Version")</div>
- <div class="section-value">{os_version}</div>
- </div>
- <div class="section-row">
- <div class="section-label">$_("OS Code Name")</div>
- <div class="section-value">{os_codename}</div>
- </div>
- <div class="section-row">
- <div class="section-label">$_("Processor")</div>
- <div class="section-value">{cpu}</div>
- </div>
- <div class="section-row">
- <div class="section-label">$_("Memory")</div>
- <div class="section-value">{memory}</div>
- </div>
- </div>
- </div>
- <div class="host-section">
- <h3 class="section-header"
- aria-controls="content-sys-statistics">
- $_("System Statistics")
- </h3>
- <div id="content-sys-statistics" class="section-content">
- <div class="section-row">
- <div class="section-label"></div>
- <div class="section-value">
- <input id="keep-monitoring-checkbox" type="checkbox" value="" />
- <label for="keep-monitoring-checkbox">$_("Collecting data after leaving this page")</label>
- </div>
- </div>
+ <div class="host-panel">
+ <div class="logo-container">
+ <div class="logo" style="background-image: url({logo});"></div>
+ </div>
+ <div id="host-info-container" class="info-container">
+ <h2 class="hostname">{hostname}</h2>
+ <div class="action-panel">
+ <button id="host-button-shutdown" class="btn-normal stop">
+ <div class="button-icon action-icon-stop"></div>
+ $_("Shut down")
+ </button>
+ <button id="host-button-restart" class="btn-normal restart">
+ <div class="button-icon action-icon-restart"></div>
+ $_("Restart")
+ </button>
+ <button class="btn-normal connect" disabled="disabled">
+ <div class="button-icon action-icon-connect"></div>
+ $_("Connect")
+ </button>
+ </div>
+ <div class="host-section">
+ <h3 class="section-header"
+ aria-expanded="false"
+ aria-controls="content-sys-info">
+ $_("Basic Information")
+ </h3>
+ <div id="content-sys-info" class="section-content">
+ <div class="section-row">
+ <div class="section-label">$_("OS Distro")</div>
+ <div class="section-value">{os_distro}</div>
+ </div>
+ <div class="section-row">
+ <div class="section-label">$_("OS Version")</div>
+ <div class="section-value">{os_version}</div>
+ </div>
+ <div class="section-row">
+ <div class="section-label">$_("OS Code Name")</div>
+ <div class="section-value">{os_codename}</div>
+ </div>
+ <div class="section-row">
+ <div class="section-label">$_("Processor")</div>
+ <div class="section-value">{cpu}</div>
+ </div>
+ <div class="section-row">
+ <div class="section-label">$_("Memory")</div>
+ <div class="section-value">{memory}</div>
+ </div>
+ </div>
+ </div>
+ <div class="host-section">
+ <h3 class="section-header"
+ aria-controls="content-sys-statistics">
+ $_("System Statistics")
+ </h3>
+ <div id="content-sys-statistics" class="section-content">
+ <div class="section-row">
+ <div class="section-label"></div>
+ <div class="section-value">
+ <input id="keep-monitoring-checkbox" type="checkbox" value="" />
+ <label for="keep-monitoring-checkbox">$_("Collecting data after leaving this page")</label>
+ </div>
+ </div>
- <div class="section-row">
- <div class="section-label">$_("CPU")</div>
- <div class="section-value">
- <div id="container-chart-cpu" class="inline-block"></div>
- </div>
- </div>
- <div class="section-row">
- <div class="section-label">$_("Memory")</div>
- <div class="section-value">
- <div id="container-chart-memory" class="inline-block"></div>
- </div>
- </div>
- <div class="section-row">
- <div class="section-label">$_("Disk I/O")</div>
- <div class="section-value">
- <div id="container-chart-disk-io" class="inline-block"></div>
- </div>
- </div>
- <div class="section-row">
- <div class="section-label">$_("Network I/O")</div>
- <div class="section-value">
- <div id="container-chart-network-io" class="inline-block"></div>
- </div>
- </div>
- </div>
- </div>
- <div id="debug-report-section" class="host-section hidden">
- <h3 class="section-header"
- aria-controls="content-sys-reports">
- $_("Debug Reports")
- </h3>
- <div id="content-sys-reports" class="section-content">
- <div class="section-row">
- <div class="section-label"></div>
- <div class="section-value">
- <div id="available-reports-grid-container"></div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
+ <div class="section-row">
+ <div class="section-label">$_("CPU")</div>
+ <div class="section-value">
+ <div id="container-chart-cpu" class="inline-block"></div>
+ </div>
+ </div>
+ <div class="section-row">
+ <div class="section-label">$_("Memory")</div>
+ <div class="section-value">
+ <div id="container-chart-memory" class="inline-block"></div>
+ </div>
+ </div>
+ <div class="section-row">
+ <div class="section-label">$_("Disk I/O")</div>
+ <div class="section-value">
+ <div id="container-chart-disk-io" class="inline-block"></div>
+ </div>
+ </div>
+ <div class="section-row">
+ <div class="section-label">$_("Network I/O")</div>
+ <div class="section-value">
+ <div id="container-chart-network-io" class="inline-block"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div id="debug-report-section" class="host-section hidden">
+ <h3 class="section-header"
+ aria-controls="content-sys-reports">
+ $_("Debug Reports")
+ </h3>
+ <div id="content-sys-reports" class="section-content">
+ <div class="section-row">
+ <div class="section-label"></div>
+ <div class="section-value">
+ <div id="available-reports-grid-container"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
</script>
<script type="text/javascript">
- kimchi.host_main();
+ kimchi.host_main();
</script>
diff --git a/ui/pages/template-edit.html.tmpl b/ui/pages/template-edit.html.tmpl
index ea21875..fe7314d 100644
--- a/ui/pages/template-edit.html.tmpl
+++ b/ui/pages/template-edit.html.tmpl
@@ -27,97 +27,97 @@
#silent _t = t.gettext
<div id="template-edit-window" class="window">
- <header>
- <h1 class="title">$_("Edit Template")</h1>
- <div class="close">X</div>
- </header>
- <div class="content">
- <form id="form-template-edit">
- <input type="hidden" id="template-name" name="templateName" />
- <fieldset class="template-edit-fieldset">
- <div>
- <div class="template-edit-wrapper-label">
- <label for="template-edit-id-textbox">$_("Name")</label>
- </div>
- <div class="template-edit-wrapper-controls">
- <input id="template-edit-id-textbox" name="name" type="text" />
- </div>
- </div>
- <div>
- <div class="template-edit-wrapper-label">
- <label for="template-edit-vendor-textbox">$_("Vendor")</label>
- </div>
- <div class="template-edit-wrapper-controls">
- <input id="template-edit-vendor-textbox" name="os_distro" type="text" disabled="disabled" />
- </div>
- </div>
- <div>
- <div class="template-edit-wrapper-label">
- <label for="template-edit-version-textbox">$_("Version")</label>
- </div>
- <div class="template-edit-wrapper-controls">
- <input id="template-edit-version-textbox" name="os_version" type="text" disabled="disabled" />
- </div>
- </div>
- <div>
- <div class="template-edit-wrapper-label">
- <label for="template-edit-cpu-textbox">$_("CPU Number")</label>
- </div>
- <div class="template-edit-wrapper-controls">
- <input id="template-edit-cpu-textbox" name="cpus" type="text" />
- </div>
- </div>
- <div>
- <div class="template-edit-wrapper-label">
- <label for="template-edit-memory-textbox">$_("Memory")</label>
- </div>
- <div class="template-edit-wrapper-controls">
- <input id="template-edit-memory-textbox" name="memory" type="text" />
- </div>
- </div>
- </fieldset>
- <fieldset class="template-edit-fieldset">
- <div>
- <div class="template-edit-wrapper-label">
- <label>$_("Disk (GB)")</label>
- </div>
- <div class="template-edit-wrapper-controls">
- <input id="template-edit-version-textbox" name="disks" type="text" />
- </div>
- </div>
- <div>
- <div class="template-edit-wrapper-label">
- <label>$_("CDROM")</label>
- </div>
- <div class="template-edit-wrapper-controls">
- <input id="template-edit-version-textbox" name="cdrom" type="text" disabled="disabled"/>
- </div>
- </div>
- <div>
- <div class="template-edit-wrapper-label">
- <label>$_("Storage Pool")</label>
- </div>
- <div class="template-edit-wrapper-controls">
- <div class="btn dropdown popable">
- <input id="template-edit-storagePool" name="storagepool" type="hidden" />
- <span class="text" id="template-edit-storage-label"></span><span class="arrow"></span>
- <div class="popover" style="width: 100%">
- <ul class="select-list" id="template-edit-storagePool-list" data-target="template-edit-storagePool" data-label="template-edit-storage-label">
- </ul>
- </div>
- </div>
- </div>
- </div>
- </fieldset>
- </form>
- </div>
- <footer>
- <div class="btn-group">
- <a id="tmpl-edit-button-cancel" class="btn-normal" href="javascript:void(0);"><span class="text">$_("Cancel")</span></a>
- <a id="tmpl-edit-button-save" class="btn-normal" href="javascript:void(0);"><span class="text">$_("Save")</span></a>
- </div>
- </footer>
+ <header>
+ <h1 class="title">$_("Edit Template")</h1>
+ <div class="close">X</div>
+ </header>
+ <div class="content">
+ <form id="form-template-edit">
+ <input type="hidden" id="template-name" name="templateName" />
+ <fieldset class="template-edit-fieldset">
+ <div>
+ <div class="template-edit-wrapper-label">
+ <label for="template-edit-id-textbox">$_("Name")</label>
+ </div>
+ <div class="template-edit-wrapper-controls">
+ <input id="template-edit-id-textbox" name="name" type="text" />
+ </div>
+ </div>
+ <div>
+ <div class="template-edit-wrapper-label">
+ <label for="template-edit-vendor-textbox">$_("Vendor")</label>
+ </div>
+ <div class="template-edit-wrapper-controls">
+ <input id="template-edit-vendor-textbox" name="os_distro" type="text" disabled="disabled" />
+ </div>
+ </div>
+ <div>
+ <div class="template-edit-wrapper-label">
+ <label for="template-edit-version-textbox">$_("Version")</label>
+ </div>
+ <div class="template-edit-wrapper-controls">
+ <input id="template-edit-version-textbox" name="os_version" type="text" disabled="disabled" />
+ </div>
+ </div>
+ <div>
+ <div class="template-edit-wrapper-label">
+ <label for="template-edit-cpu-textbox">$_("CPU Number")</label>
+ </div>
+ <div class="template-edit-wrapper-controls">
+ <input id="template-edit-cpu-textbox" name="cpus" type="text" />
+ </div>
+ </div>
+ <div>
+ <div class="template-edit-wrapper-label">
+ <label for="template-edit-memory-textbox">$_("Memory")</label>
+ </div>
+ <div class="template-edit-wrapper-controls">
+ <input id="template-edit-memory-textbox" name="memory" type="text" />
+ </div>
+ </div>
+ </fieldset>
+ <fieldset class="template-edit-fieldset">
+ <div>
+ <div class="template-edit-wrapper-label">
+ <label>$_("Disk (GB)")</label>
+ </div>
+ <div class="template-edit-wrapper-controls">
+ <input id="template-edit-version-textbox" name="disks" type="text" />
+ </div>
+ </div>
+ <div>
+ <div class="template-edit-wrapper-label">
+ <label>$_("CDROM")</label>
+ </div>
+ <div class="template-edit-wrapper-controls">
+ <input id="template-edit-version-textbox" name="cdrom" type="text" disabled="disabled"/>
+ </div>
+ </div>
+ <div>
+ <div class="template-edit-wrapper-label">
+ <label>$_("Storage Pool")</label>
+ </div>
+ <div class="template-edit-wrapper-controls">
+ <div class="btn dropdown popable">
+ <input id="template-edit-storagePool" name="storagepool" type="hidden" />
+ <span class="text" id="template-edit-storage-label"></span><span class="arrow"></span>
+ <div class="popover" style="width: 100%">
+ <ul class="select-list" id="template-edit-storagePool-list" data-target="template-edit-storagePool" data-label="template-edit-storage-label">
+ </ul>
+ </div>
+ </div>
+ </div>
+ </div>
+ </fieldset>
+ </form>
+ </div>
+ <footer>
+ <div class="btn-group">
+ <a id="tmpl-edit-button-cancel" class="btn-normal" href="javascript:void(0);"><span class="text">$_("Cancel")</span></a>
+ <a id="tmpl-edit-button-save" class="btn-normal" href="javascript:void(0);"><span class="text">$_("Save")</span></a>
+ </div>
+ </footer>
</div>
<script>
- kimchi.template_edit_main();
+ kimchi.template_edit_main();
</script>
--
1.7.10.4
1
1
Reviewed-by: Crístian Viana <vianac(a)linux.vnet.ibm.com>
On 20-12-2013 11:42, Aline Manera wrote:
>
>
> From: Aline Manera <alinefm(a)br.ibm.com>
>
> All py, css, js, tmpl and json files created by us must use 4 space for
> indentation.
> Imported files must keep as they are (such as those for jquery, novnc)
>
> This patch was generated by the following command:
>
> find . -name \*.py -o -name \*.json -o -name \*.css -o -name \*.js -o \
> -name \*.tmpl | xargs -I @ sed -i s/\\t/" "/g @
>
> Then imported files had changes reverted.
>
> Signed-off-by: Aline Manera <alinefm(a)br.ibm.com>
2
1
From: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>
It is recommended that the log message should use comma.
Arguments should be separated by comma.
and they should not be in a tuple.
Before this patch:
Kimchi throws an exception as follow:
TypeError: not enough arguments for format string
Logged from file disks.py, line 117
After use the log correctly:
Kimchi log works well:
Error getting partition info for 35000c5002eaa8392p2: Error executing
lsblk: lsblk: dm-2: unknown device name
Signed-off-by: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>
---
src/kimchi/disks.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/kimchi/disks.py b/src/kimchi/disks.py
index 991bb4a..a054961 100644
--- a/src/kimchi/disks.py
+++ b/src/kimchi/disks.py
@@ -114,7 +114,7 @@ def get_partition_details(name):
dev = _get_lsblk_devs(keys, [dev_path])[0]
except OperationFailed as e:
kimchi_log.error(
- "Error getting partition info for %s: %s", (name, e))
+ "Error getting partition info for %s: %s", name, e)
return {}
if dev['mountpoint']:
--
1.7.11.7
2
2
[project-kimchi][PATCHv2] screenshot: fix problem on Power of truncated picture
by lvroyce@linux.vnet.ibm.com 20 Dec '13
by lvroyce@linux.vnet.ibm.com 20 Dec '13
20 Dec '13
From: Royce Lv <lvroyce(a)linux.vnet.ibm.com>
PIL lib will report error because of picture information inconsistency,
this error may because of qemu screenshot generation,
work around this problem by load this image in advance
to prevent it raise error in thumbnail generation.
REF:
http://stackoverflow.com/questions/12984426/ \
python-pil-ioerror-image-file-truncated-with-big-images
Signed-off-by: Royce Lv <lvroyce(a)linux.vnet.ibm.com>
---
src/kimchi/screenshot.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/kimchi/screenshot.py b/src/kimchi/screenshot.py
index b17a3ce..5cfea96 100644
--- a/src/kimchi/screenshot.py
+++ b/src/kimchi/screenshot.py
@@ -174,6 +174,12 @@ class VMScreenshot(object):
self._create_black_image(thumbnail)
else:
im = Image.open(thumbnail)
+ try:
+ # Prevent Image lib from lazy load,
+ # work around pic truncate validation in thumbnail generation
+ im.load()
+ except Exception as e:
+ kimchi_log.warning("Image load with warning: %s." % e)
im.thumbnail(self.THUMBNAIL_SIZE)
im.save(thumbnail, "PNG")
--
1.8.1.2
2
2
[project-kimchi][PATCHv4] Return source information for storage pool
by lvroyce@linux.vnet.ibm.com 20 Dec '13
by lvroyce@linux.vnet.ibm.com 20 Dec '13
20 Dec '13
From: Royce Lv <lvroyce(a)linux.vnet.ibm.com>
v1>v4, Delete logical pool source because UI just care VG name.
Add update controller.
For netfs storage pool, remote export path and addr will make more
sense than local mount point.
Report storage source information in pool info.
Signed-off-by: Royce Lv <lvroyce(a)linux.vnet.ibm.com>
---
docs/API.md | 3 +++
src/kimchi/controller.py | 1 +
src/kimchi/mockmodel.py | 1 +
src/kimchi/model.py | 16 ++++++++++++++++
tests/test_rest.py | 1 +
5 files changed, 22 insertions(+)
diff --git a/docs/API.md b/docs/API.md
index 74bc1b5..9edc551 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -215,6 +215,9 @@ Represents a snapshot of the Virtual Machine's primary monitor.
* nr_volumes: The number of storage volumes for active pools, 0 for inactive pools
* autostart: Whether the storage pool will be enabled
automatically when the system boots
+ * source: Source of the storage pool,
+ * addr: mount address of this storage pool(for 'netfs' pool)
+ * path: export path of this storage pool(for 'netfs' pool)
* **PUT**: Set whether the Storage Pool should be enabled automatically when the
system boots
* autostart: Toggle the autostart flag of the VM
diff --git a/src/kimchi/controller.py b/src/kimchi/controller.py
index 3b27c27..2940278 100644
--- a/src/kimchi/controller.py
+++ b/src/kimchi/controller.py
@@ -533,6 +533,7 @@ class StoragePool(Resource):
'allocated': self.info['allocated'],
'available': self.info['available'],
'path': self.info['path'],
+ 'source': self.info['source'],
'type': self.info['type'],
'nr_volumes': self.info['nr_volumes'],
'autostart': self.info['autostart']}
diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py
index 4839d4c..348127a 100644
--- a/src/kimchi/mockmodel.py
+++ b/src/kimchi/mockmodel.py
@@ -639,6 +639,7 @@ class MockStoragePool(object):
'allocated': 512 << 20,
'available': 512 << 20,
'path': '/var/lib/libvirt/images',
+ 'source': {},
'type': 'dir',
'nr_volumes': 0,
'autostart': 0}
diff --git a/src/kimchi/model.py b/src/kimchi/model.py
index 73c18ac..3bc5d6d 100644
--- a/src/kimchi/model.py
+++ b/src/kimchi/model.py
@@ -78,6 +78,9 @@ GUESTS_STATS_INTERVAL = 5
HOST_STATS_INTERVAL = 1
VM_STATIC_UPDATE_PARAMS = {'name': './name'}
VM_LIVE_UPDATE_PARAMS = {}
+STORAGE_SOURCES = {'netfs': {'addr': '/pool/source/host/@name',
+ 'path': '/pool/source/dir/@path'}}
+
def _uri_to_name(collection, uri):
expr = '/%s/(.*?)/?$' % collection
@@ -1019,6 +1022,17 @@ class Model(object):
raise OperationFailed(e.get_error_message())
return name
+ def _get_storage_source(self, pool_type, pool_xml):
+ source = {}
+ if pool_type not in STORAGE_SOURCES:
+ return source
+
+ for key, val in STORAGE_SOURCES[pool_type].items():
+ res = xmlutils.xpath_get_text(pool_xml, val)
+ source[key] = res[0] if len(res) == 1 else res
+
+ return source
+
def storagepool_lookup(self, name):
pool = self._get_storagepool(name)
info = pool.info()
@@ -1027,8 +1041,10 @@ class Model(object):
xml = pool.XMLDesc(0)
path = xmlutils.xpath_get_text(xml, "/pool/target/path")[0]
pool_type = xmlutils.xpath_get_text(xml, "/pool/@type")[0]
+ source = self._get_storage_source(pool_type, xml)
res = {'state': Model.pool_state_map[info[0]],
'path': path,
+ 'source': source,
'type': pool_type,
'autostart': autostart,
'capacity': info[1],
diff --git a/tests/test_rest.py b/tests/test_rest.py
index 73b5243..f597796 100644
--- a/tests/test_rest.py
+++ b/tests/test_rest.py
@@ -417,6 +417,7 @@ class RestTests(unittest.TestCase):
storagepool = json.loads(resp.read())
self.assertEquals('storagepool-1', storagepool['name'])
self.assertEquals('inactive', storagepool['state'])
+ self.assertIn('source', storagepool)
def test_storagepool_action(self):
# Create a storage pool
--
1.8.1.2
3
3
[project-kimchi] [PATCH 0/4] Use one weksockify instance as all vms' vnc proxy.
by Mark Wu 20 Dec '13
by Mark Wu 20 Dec '13
20 Dec '13
It resolves two problems:
A fixed port for firewall rule.
Race between client and server. It happens on every first connection after
the host running kimchi reboot.
Mark Wu (4):
Move configuration parsing to config.py
Add a configuration for vnc websocket proxy
Use one weksockify instance as all vms' vnc proxy.
Remove vnc related code in mockmodel
docs/API.md | 1 +
src/kimchi.conf.in | 3 +++
src/kimchi/config.py.in | 24 +++++++++++++++++++++---
src/kimchi/controller.py | 5 ++++-
src/kimchi/mockmodel.py | 19 -------------------
src/kimchi/model.py | 18 +++++-------------
src/kimchi/server.py | 5 +++++
src/kimchi/vnc.py | 42 +++++++++++++++++++++++-------------------
src/kimchid.in | 20 +-------------------
ui/js/src/kimchi.api.js | 14 +++++---------
10 files changed, 68 insertions(+), 83 deletions(-)
--
1.8.3.1
--
project-kimchi mailing list <project-kimchi(a)googlegroups.com>
https://groups.google.com/forum/#!forum/project-kimchi
---
You received this message because you are subscribed to the Google Groups "project-kimchi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-kimchi+unsubscribe(a)googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
4
15
From: Aline Manera <alinefm(a)br.ibm.com>
V1 -> V2:
- Get device name from sysfs uevent file and assume its path is /dev/<devname>
Zhou Zheng Sheng (3):
PEP 8: cleanup src/kimchi/disks.py
Issue #276: logical pool: a quick fix for the device listing rules,
back-end
Issue #276, logical pool: a quick fix for the device listing rules,
front-end
Makefile.am | 1 +
src/kimchi/disks.py | 177 ++++++++++++++++--------------
ui/js/src/kimchi.storagepool_add_main.js | 2 +-
3 files changed, 94 insertions(+), 86 deletions(-)
--
1.7.10.4
--
project-kimchi mailing list <project-kimchi(a)googlegroups.com>
https://groups.google.com/forum/#!forum/project-kimchi
---
You received this message because you are subscribed to the Google Groups "project-kimchi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-kimchi+unsubscribe(a)googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
4
9
[project-kimchi] [PATCH] logical pool fixes: only list leaf devices, and read file instead of run "cat"
by Zhou Zheng Sheng 19 Dec '13
by Zhou Zheng Sheng 19 Dec '13
19 Dec '13
Some devices are parent of other devices. We should just list the leaf
devices but not parent devices. For example, if a disk contains
partitions, we should only list the partitions. If a disk is held by
multipath device, we should not list the disk.
Currently we strip the last charactter from "vda1" to get "vda" and
ignore the parent "vda" device. This may fails on disk contains lots of
logical partitions, for example "vda10"'s parent is not "vda1". Another
problem is this method can not find the parent-child relation ship
between a multipath device and its slaves.
The most accurate information is on sysfs, and lsblk lists all children
device of the requested device based on sysfs. So when "lsblk -P
devices" prints only one line, it's the device itself, when it prints
more lines, they are the children devices. This patch use this method to
detect if a device a leaf one.
This patch also avoids start new "cat" process to read uevent file, it
opens the uevent file and read it directly.
Signed-off-by: Zhou Zheng Sheng <zhshzhou(a)linux.vnet.ibm.com>
---
src/kimchi/disks.py | 45 ++++++++++++++++++++++++++++-----------------
1 file changed, 28 insertions(+), 17 deletions(-)
diff --git a/src/kimchi/disks.py b/src/kimchi/disks.py
index 991bb4a..9ab9263 100644
--- a/src/kimchi/disks.py
+++ b/src/kimchi/disks.py
@@ -29,7 +29,6 @@ from kimchi.utils import kimchi_log
def _get_partition_path(name):
""" Returns device path given a partition name """
- dev_path = None
maj_min = None
keys = ["NAME", "MAJ:MIN"]
@@ -39,15 +38,14 @@ def _get_partition_path(name):
if dev['name'] == name:
maj_min = dev['maj:min']
break
+ else:
+ raise OperationFailed(
+ "Failed to find major and minor number for %s", name)
- uevent_cmd = "cat /sys/dev/block/%s/uevent" % maj_min
- uevent = subprocess.Popen(uevent_cmd, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE, shell=True)
- out, err = uevent.communicate()
- if uevent.returncode != 0:
- raise OperationFailed("Error getting partition path for %s", name)
+ with open("/sys/dev/block/%s/uevent" % maj_min) as ueventf:
+ content = ueventf.read()
- data = dict(re.findall(r'(\S+)=(".*?"|\S+)', out.replace("\n", " ")))
+ data = dict(re.findall(r'(\S+)=(".*?"|\S+)', content.replace("\n", " ")))
return "/dev/%s" % data["DEVNAME"]
@@ -63,6 +61,22 @@ def _get_lsblk_devs(keys, devs=[]):
return _parse_lsblk_output(out, keys)
+def _is_dev_leaf(name):
+ try:
+ # By default, lsblk prints a device information followed by children
+ # device information
+ childrenCount = len(
+ _get_lsblk_devs(["NAME"], [_get_partition_path(name)])) - 1
+ except OperationFailed as e:
+ # lsblk is known to fail on multipath devices
+ # Assume these devices contain children
+ kimchi_log.error(
+ "Error getting device info for %s: %s", (name, e))
+ return False
+ else:
+ return childrenCount == 0
+
+
def _parse_lsblk_output(output, keys):
# output is on format key="value",
# where key can be NAME, TYPE, FSTYPE, SIZE, MOUNTPOINT, etc
@@ -82,7 +96,6 @@ def _parse_lsblk_output(output, keys):
def get_partitions_names():
names = []
- ignore_names = []
keys = ["NAME", "TYPE", "FSTYPE", "MOUNTPOINT"]
# output is on format key="value",
# where key can be NAME, TYPE, FSTYPE, MOUNTPOINT
@@ -90,20 +103,18 @@ def get_partitions_names():
# split()[0] to avoid the second part of the name, after the
# whiteline
name = dev['name'].split()[0]
- # Only list unmounted and unformated partition or disk.
+ # Only list unmounted and unformated and leaf and (partition or disk)
+ # leaf means a partition, a disk has no partition, or a disk not held
+ # by any multipath device.
if not all([dev['type'] in ['part', 'disk'],
dev['fstype'] == "",
- dev['mountpoint'] == ""]):
-
- # the whole disk must be ignored in it has at least one
- # mounted/formatted partition
- if dev['type'] == 'part':
- ignore_names.append(name[:-1])
+ dev['mountpoint'] == "",
+ _is_dev_leaf(name)]):
continue
names.append(name)
- return list(set(names) - set(ignore_names))
+ return names
def get_partition_details(name):
--
1.7.11.7
--
project-kimchi mailing list <project-kimchi(a)googlegroups.com>
https://groups.google.com/forum/#!forum/project-kimchi
---
You received this message because you are subscribed to the Google Groups "project-kimchi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-kimchi+unsubscribe(a)googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
1
0
Re: [Kimchi-devel] [project-kimchi] [PATCH v1 4/4] storagepool: Support Creating iSCSI storagepool in model.py
by Sheldon 19 Dec '13
by Sheldon 19 Dec '13
19 Dec '13
On 12/16/2013 04:01 PM, Zhou Zheng Sheng wrote:
> This patch implements creating iSCSI storagepool for libvirt.
> Each LUN in iSCSI storagepool can be used as a volume, but iSCSI
> storagepool does not provide ability to create volume. For now in kimchi
> we create volume for each newly created VM. Next is to implement
> attaching existing volume to a new VM.
>
> How to test
>
> Create:
> curl -u root -H 'Content-type: application/json' \
> -H 'Accept: application/json' \
> -d '{"srcTarget":
> "targetIQN",
> "srcHost": "10.0.0.X",
> "type": "iscsi",
> "name": "iscsipool"}' \
> http://127.0.0.1:8000/storagepools
>
> Show Info:
> curl -u root -H 'Accept: application/json' \
> http://127.0.0.1:8000/storagepools/iscsipool
>
> Activate:
> curl -u root -H 'Content-type: application/json' \
> -H 'Accept: application/json' \
> -d '' http://127.0.0.1:8000/storagepools/iscsipool/activate
>
> Examine:
> iscsiadm -m session
>
> Deactivate:
> curl -u root -H 'Content-type: application/json' \
> -H 'Accept: application/json' \
> -d '' http://127.0.0.1:8000/storagepools/iscsipool/deactivate
>
> Delete:
> curl -u root -X DELETE -H 'Accept: application/json' \
> http://127.0.0.1:8000/storagepools/iscsipool
>
> Signed-off-by: Zhou Zheng Sheng <zhshzhou(a)linux.vnet.ibm.com>
> ---
> docs/API.md | 6 ++++--
> src/kimchi/model.py | 34 ++++++++++++++++++++++++++++++++++
> 2 files changed, 38 insertions(+), 2 deletions(-)
>
> diff --git a/docs/API.md b/docs/API.md
> index dd3d7f1..342c583 100644
> --- a/docs/API.md
> +++ b/docs/API.md
> @@ -182,16 +182,18 @@ Represents a snapshot of the Virtual Machine's primary monitor.
> * **POST**: Create a new Storage Pool
> * name: The name of the Storage Pool.
> * type: The type of the defined Storage Pool.
> - Supported types: 'dir', 'kimchi-iso', 'netfs', 'logical'
> + Supported types: 'dir', 'kimchi-iso', 'netfs', 'logical', 'iscsi'
> * path: The path of the defined Storage Pool.
> For 'kimchi-iso' pool refers to targeted deep scan path.
> Pool types: 'dir', 'kimchi-iso'.
> * srcHost: IP or hostname of server for a pool backed from a remote host.
> - Pool types: 'nfs'.
> + Pool types: 'nfs', 'iscsi'.
> * srcPath: Export path on NFS server for NFS pool.
> Pool types: 'nfs'.
> * srcDevices: Array of devices to be used in the Storage Pool
> Pool types: 'logical'.
> + * srcTarget: Target IQN of an iSCSI pool.
> + Pool types: 'iscsi'.
>
> ### Resource: Storage Pool
>
> diff --git a/src/kimchi/model.py b/src/kimchi/model.py
> index af0d728..3888903 100644
> --- a/src/kimchi/model.py
> +++ b/src/kimchi/model.py
> @@ -1475,11 +1475,45 @@ def _get_logical_storagepool_xml(poolArgs):
> return xml
>
>
> +def _get_iscsi_storagepool_xml(poolArgs):
> + # Required parameters
> + # name:
> + # type:
> + # srcHost:
> + # srcTarget:
> + #
> + # Optional parameters
> + # srcPort:
> +
> + try:
> + portAttr = "port='%s'" % poolArgs['srcPort']
> + except KeyError:
> + portAttr = ""
> +
> + poolArgs.update({'srcPort': portAttr,
> + 'path': '/dev/disk/by-id'})
> +
> + xml = """
> + <pool type='%(type)s'>
> + <name>%(name)s</name>
> + <source>
> + <host name='%(srcHost)s' %(srcPort)s/>
> + <device path='%(srcTarget)s'/>
> + </source>
> + <target>
> + <path>%(path)s</path>
> + </target>
> + </pool>
> + """ % poolArgs
> + return xml
> +
> +
http://libvirt.org/formatstorage.html#exampleISCSI
iSCSI based storage pool
<pool type="iscsi">
<name>virtimages</name>
<source>
<host name="iscsi.example.com"/>
<device path="iqn.2013-06.com.example:iscsi-pool"/>
<auth type='chap' username='myuser'>
<secret usage='libvirtiscsi'/>
</auth>
</source>
<target>
<path>/dev/disk/by-path</path>
</target>
</pool>
auth tag?
> def _get_pool_xml(**kwargs):
> getPoolXml = {
> 'dir': _get_dir_storagepool_xml,
> 'netfs': _get_netfs_storagepool_xml,
> 'logical': _get_logical_storagepool_xml,
> + 'iscsi': _get_iscsi_storagepool_xml,
> }[kwargs['type']]
> return getPoolXml(kwargs)
>
Sheldon Feng(???)
IBM Linux Technology Center
--
project-kimchi mailing list <project-kimchi(a)googlegroups.com>
https://groups.google.com/forum/#!forum/project-kimchi
---
You received this message because you are subscribed to the Google Groups "project-kimchi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-kimchi+unsubscribe(a)googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
1
0
19 Dec '13
On 12/17/2013 02:36 PM, taget(a)linux.vnet.ibm.com wrote:
> From: Eli Qiao <taget(a)linux.vnet.ibm.com>
>
> Signed-off-by: Eli Qiao <taget(a)linux.vnet.ibm.com>
> ---
> contrib/kimchi.spec.fedora.in | 5 +++++
> contrib/kimchi.spec.suse.in | 5 +++++
> 2 files changed, 10 insertions(+), 0 deletions(-)
>
> diff --git a/contrib/kimchi.spec.fedora.in b/contrib/kimchi.spec.fedora.in
> index 14ec359..f21ae49 100644
> --- a/contrib/kimchi.spec.fedora.in
> +++ b/contrib/kimchi.spec.fedora.in
> @@ -81,6 +81,11 @@ if [ $1 -eq 1 ] ; then
> /bin/systemctl daemon-reload >/dev/null 2>&1 || :
> fi
>
> +# open 8000 and 8001 port for firewall
> +
> +iptables -I INPUT -p tcp --dport 8000 -j ACCEPT
> +iptables -I INPUT -p tcp --dport 8001 -j ACCEPT
> +
> %if 0%{?rhel} == 6
> start kimchid
> %else
> diff --git a/contrib/kimchi.spec.suse.in b/contrib/kimchi.spec.suse.in
> index 9051284..5209e03 100644
> --- a/contrib/kimchi.spec.suse.in
> +++ b/contrib/kimchi.spec.suse.in
> @@ -47,6 +47,11 @@ install -Dm 0755 contrib/kimchid.sysvinit %{buildroot}%{_initrddir}/kimchid
> service kimchid start
> chkconfig kimchid on
>
> +# open 8000 and 8001 port for firewall
> +
> +iptables -I INPUT -p tcp --dport 8000 -j ACCEPT
> +iptables -I INPUT -p tcp --dport 8001 -j ACCEPT
> +
> %preun
> service kimchid stop
>
Eli,
Thanks for the patch. But it's not a reliable configuration. This rule
will be lost after reboot.
And shipping a configuration file is better than running commands in
spec file.
Please take a look at firewalld and firewalld.service
http://manpages.ubuntu.com/manpages/raring/man5/firewalld.service.5.html
It could be a better solution for the platforms where firewalld is
available.
--
project-kimchi mailing list <project-kimchi(a)googlegroups.com>
https://groups.google.com/forum/#!forum/project-kimchi
---
You received this message because you are subscribed to the Google Groups "project-kimchi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-kimchi+unsubscribe(a)googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
4
6
[project-kimchi] Re: [PATCH v1 1/4] storagepool: dispatch _get_pool_xml() to respective _get_XXX_storagepool_xml() function
by Sheldon 19 Dec '13
by Sheldon 19 Dec '13
19 Dec '13
As Pradeep mentioned:
we will add more storage pools, how about split these xml to a new file?
On 12/16/2013 04:01 PM, Zhou Zheng Sheng wrote:
> In src/kimchi/model.py, _get_pool_xml() is to generate libvirt storage
> pool XML definition from arguments provided by the POST data (a JSON
> dict). It has to support various types of pool such as dir, netfs and
> logical. Now it uses "if ... elif ... elif" to check the requested type
> of pool and go in to the respective branch and generates the correct
> XML.
>
> This is OK for now but in future we'll have more types of pool, so in
> this patch we turn "if ... elif" into a dispatching dict, and put the
> respective XML generating code to separated functions.
>
> Signed-off-by: Zhou Zheng Sheng <zhshzhou(a)linux.vnet.ibm.com>
> ---
> src/kimchi/model.py | 121 +++++++++++++++++++++++++++++++---------------------
> 1 file changed, 73 insertions(+), 48 deletions(-)
>
> diff --git a/src/kimchi/model.py b/src/kimchi/model.py
> index 90a9a1b..d22e02d 100644
> --- a/src/kimchi/model.py
> +++ b/src/kimchi/model.py
> @@ -1403,60 +1403,85 @@ class LibvirtVMScreenshot(VMScreenshot):
> finally:
> os.close(fd)
>
> -def _get_pool_xml(**kwargs):
> +
> +def _get_dir_storagepool_xml(poolArgs):
> # Required parameters
> # name:
> # type:
> # path:
> + xml = """
> + <pool type='%(type)s'>
> + <name>%(name)s</name>
> + <target>
> + <path>%(path)s</path>
> + </target>
> + </pool>
> + """ % poolArgs
> + return xml
> +
> +
> +def _get_netfs_storagepool_xml(poolArgs):
> + # Required parameters
> + # name:
> + # type:
> + # nfsserver:
> # nfspath:
> - if kwargs['type'] == 'dir':
> - xml = """
> - <pool type='%(type)s'>
> - <name>%(name)s</name>
> - <target>
> - <path>%(path)s</path>
> - </target>
> - </pool>
> - """
> - elif kwargs['type'] == 'netfs':
> - path = '/var/lib/kimchi/nfs_mount/' + kwargs['name'];
> - if not os.path.exists(path):
> - os.makedirs(path)
> - kwargs.update({'path': path})
> - xml = """
> - <pool type='%(type)s'>
> - <name>%(name)s</name>
> - <source>
> - <host name='%(nfsserver)s'/>
> - <dir path='%(nfspath)s'/>
> - </source>
> - <target>
> - <path>%(path)s</path>
> - </target>
> - </pool>
> - """
> - elif kwargs['type'] == 'logical':
> - path = '/var/lib/kimchi/logical_mount/' + kwargs['name'];
> - if not os.path.exists(path):
> - os.makedirs(path)
> - xml = """
> - <pool type='%(type)s'>
> - <name>%(name)s</name>
> - <source>
> - %(devices)s
> - </source>
> - <target>
> - <path>%(path)s</path>
> - </target>
> - </pool>
> - """
> - devices = ''
> - for device_path in kwargs['devices']:
> - devices += "<device path=\""+device_path+"\" />"
> + path = '/var/lib/kimchi/nfs_mount/' + poolArgs['name']
> + if not os.path.exists(path):
> + os.makedirs(path)
> + poolArgs['path'] = path
> + xml = """
> + <pool type='%(type)s'>
> + <name>%(name)s</name>
> + <source>
> + <host name='%(nfsserver)s'/>
> + <dir path='%(nfspath)s'/>
> + </source>
> + <target>
> + <path>%(path)s</path>
> + </target>
> + </pool>
> + """ % poolArgs
> + return xml
> +
> +
> +def _get_logical_storagepool_xml(poolArgs):
> + # Required parameters
> + # name:
> + # type:
> + # devices:
> + path = '/var/lib/kimchi/logical_mount/' + poolArgs['name']
> + if not os.path.exists(path):
> + os.makedirs(path)
> +
> + devices = []
> + for device_path in poolArgs['devices']:
> + devices.append('<device path="%s" />' % device_path)
> +
> + poolArgs.update({'devices': ''.join(devices),
> + 'path': path})
> + xml = """
> + <pool type='%(type)s'>
> + <name>%(name)s</name>
> + <source>
> + %(devices)s
> + </source>
> + <target>
> + <path>%(path)s</path>
> + </target>
> + </pool>
> + """ % poolArgs
> + return xml
> +
> +
> +def _get_pool_xml(**kwargs):
> + getPoolXml = {
> + 'dir': _get_dir_storagepool_xml,
> + 'netfs': _get_netfs_storagepool_xml,
> + 'logical': _get_logical_storagepool_xml,
> + }[kwargs['type']]
> + return getPoolXml(kwargs)
>
> - kwargs.update({'devices': devices})
> - kwargs.update({'path': path})
> - return xml % kwargs
>
> def _get_volume_xml(**kwargs):
> # Required parameters
Sheldon Feng(???)
IBM Linux Technology Center
--
project-kimchi mailing list <project-kimchi(a)googlegroups.com>
https://groups.google.com/forum/#!forum/project-kimchi
---
You received this message because you are subscribed to the Google Groups "project-kimchi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-kimchi+unsubscribe(a)googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
1
0
Include create storage pool UI and translation files.
zhoumeina (2):
Add UI support of iscsi
Add the ISCSI translation po files
po/en_US.po | 31 +++++++++++++----
po/kimchi.pot | 30 ++++++++++++----
po/pt_BR.po | 31 +++++++++++++----
po/zh_CN.po | 31 +++++++++++++----
ui/js/src/kimchi.storagepool_add_main.js | 54 ++++++++++++++++++++++++-----
ui/pages/i18n.html.tmpl | 7 ++--
ui/pages/storagepool-add.html.tmpl | 19 ++++++++++
7 files changed, 158 insertions(+), 45 deletions(-)
mode change 100644 => 100755 po/kimchi.pot
1
2
Re: [Kimchi-devel] [project-kimchi] [PATCH v1 2/4] storagepool: rename and consolidate arguments of creating (back end)
by Zhou Zheng Sheng 19 Dec '13
by Zhou Zheng Sheng 19 Dec '13
19 Dec '13
于 2013年12月18日 09:47, Shu Ming 写道:
> 于 2013/12/16 16:01, Zhou Zheng Sheng 写道:
>> As we are adding support to new type of storage pool, the current naming
>> scheme of the storage pool creating arguments should be rearranged to be
>> more extendable. This patch renames some arguments and consolidates the
>> argument of the same purposes as follow.
>>
>> nfsserver -> srcHost
>> This is because in future patches, iSCSI pool can use this srcHost as
>> well. Other network backed storage pool can also make use of this
>> argument.
>>
>> nfspath -> srcPath
>> This is because other netfs pool can also make use of this argument.
>>
>> devices -> srcDevices
>> To differentiate source arguments from the target arguments, we can add
>> a "src" prefix to source arguments.
>> ---
>> docs/API.md | 19 +++++++++++--------
>> src/kimchi/model.py | 17 +++++++++--------
>> 2 files changed, 20 insertions(+), 16 deletions(-)
>>
>> diff --git a/docs/API.md b/docs/API.md
>> index 74bc1b5..dd3d7f1 100644
>> --- a/docs/API.md
>> +++ b/docs/API.md
>> @@ -180,15 +180,18 @@ Represents a snapshot of the Virtual Machine's
>> primary monitor.
>>
>> * **GET**: Retrieve a summarized list of all defined Storage Pools
>> * **POST**: Create a new Storage Pool
>> - * name: The name of the Storage Pool
>> - * path: The path of the defined Storage Pool,
>> + * name: The name of the Storage Pool.
>> + * type: The type of the defined Storage Pool.
>> + Supported types: 'dir', 'kimchi-iso', 'netfs', 'logical'
>> + * path: The path of the defined Storage Pool.
>> For 'kimchi-iso' pool refers to targeted deep scan path.
>> - * type: The type of the defined Storage Pool,
>> - Supported types: 'dir', 'kimchi-iso', 'netfs'
>> - * nfsserver: IP or hostname of NFS server to create NFS pool.
>> - * nfspath: export path on nfs server for NFS pool.
>> - * devices: Array of devices to be used in the Storage Pool
>> - Exclusive to the 'logical' storage pool type.
>> + Pool types: 'dir', 'kimchi-iso'.
>> + * srcHost: IP or hostname of server for a pool backed from a
>> remote host.
>> + Pool types: 'nfs'.
>
> Let's keep the name style as the others in this file, like "src_host" or
> "srchost".
>
>> + * srcPath: Export path on NFS server for NFS pool.
>> + Pool types: 'nfs'.
> The same as the above.
>
>> + * srcDevices: Array of devices to be used in the Storage Pool
>> + Pool types: 'logical'.
>
> The same as the above.
>
Thanks for the suggestion. I will change the style.
>>
>> ### Resource: Storage Pool
>>
>> diff --git a/src/kimchi/model.py b/src/kimchi/model.py
>> index d22e02d..af0d728 100644
>> --- a/src/kimchi/model.py
>> +++ b/src/kimchi/model.py
>> @@ -1424,8 +1424,8 @@ def _get_netfs_storagepool_xml(poolArgs):
>> # Required parameters
>> # name:
>> # type:
>> - # nfsserver:
>> - # nfspath:
>> + # srcHost:
>> + # srcPath:
>> path = '/var/lib/kimchi/nfs_mount/' + poolArgs['name']
>> if not os.path.exists(path):
>> os.makedirs(path)
>> @@ -1434,8 +1434,8 @@ def _get_netfs_storagepool_xml(poolArgs):
>> <pool type='%(type)s'>
>> <name>%(name)s</name>
>> <source>
>> - <host name='%(nfsserver)s'/>
>> - <dir path='%(nfspath)s'/>
>> + <host name='%(srcHost)s'/>
>> + <dir path='%(srcPath)s'/>
>> </source>
>> <target>
>> <path>%(path)s</path>
>> @@ -1449,22 +1449,23 @@ def _get_logical_storagepool_xml(poolArgs):
>> # Required parameters
>> # name:
>> # type:
>> - # devices:
>> + # srcDevices:
>> path = '/var/lib/kimchi/logical_mount/' + poolArgs['name']
>> if not os.path.exists(path):
>> os.makedirs(path)
>>
>> devices = []
>> - for device_path in poolArgs['devices']:
>> + for device_path in poolArgs['srcDevices']:
>> devices.append('<device path="%s" />' % device_path)
>>
>> - poolArgs.update({'devices': ''.join(devices),
>> + poolArgs.update({'srcDevices': ''.join(devices),
>> 'path': path})
>> +
>> xml = """
>> <pool type='%(type)s'>
>> <name>%(name)s</name>
>> <source>
>> - %(devices)s
>> + %(srcDevices)s
>> </source>
>> <target>
>> <path>%(path)s</path>
>
--
Thanks and best regards!
Zhou Zheng Sheng / 周征晟
E-mail: zhshzhou(a)linux.vnet.ibm.com
Telephone: 86-10-82454397
--
project-kimchi mailing list <project-kimchi(a)googlegroups.com>
https://groups.google.com/forum/#!forum/project-kimchi
---
You received this message because you are subscribed to the Google Groups "project-kimchi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-kimchi+unsubscribe(a)googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
1
0
Re: [Kimchi-devel] [project-kimchi] [PATCH v1 1/4] storagepool: dispatch _get_pool_xml() to respective _get_XXX_storagepool_xml() function
by Zhou Zheng Sheng 19 Dec '13
by Zhou Zheng Sheng 19 Dec '13
19 Dec '13
on 2013/12/18/ 09:44, Shu Ming wrote:
> 于 2013/12/16 16:01, Zhou Zheng Sheng 写道:
>> In src/kimchi/model.py, _get_pool_xml() is to generate libvirt storage
>> pool XML definition from arguments provided by the POST data (a JSON
>> dict). It has to support various types of pool such as dir, netfs and
>> logical. Now it uses "if ... elif ... elif" to check the requested type
>> of pool and go in to the respective branch and generates the correct
>> XML.
>>
>> This is OK for now but in future we'll have more types of pool, so in
>> this patch we turn "if ... elif" into a dispatching dict, and put the
>> respective XML generating code to separated functions.
>>
>> Signed-off-by: Zhou Zheng Sheng <zhshzhou(a)linux.vnet.ibm.com>
>> ---
>> src/kimchi/model.py | 121
>> +++++++++++++++++++++++++++++++---------------------
>> 1 file changed, 73 insertions(+), 48 deletions(-)
>>
>> diff --git a/src/kimchi/model.py b/src/kimchi/model.py
>> index 90a9a1b..d22e02d 100644
>> --- a/src/kimchi/model.py
>> +++ b/src/kimchi/model.py
>> @@ -1403,60 +1403,85 @@ class LibvirtVMScreenshot(VMScreenshot):
>> finally:
>> os.close(fd)
>>
>> -def _get_pool_xml(**kwargs):
>> +
>> +def _get_dir_storagepool_xml(poolArgs):
>> # Required parameters
>> # name:
>> # type:
>> # path:
>> + xml = """
>> + <pool type='%(type)s'>
>> + <name>%(name)s</name>
>> + <target>
>> + <path>%(path)s</path>
>> + </target>
>> + </pool>
>> + """ % poolArgs
>> + return xml
>> +
>> +
>> +def _get_netfs_storagepool_xml(poolArgs):
>> + # Required parameters
>> + # name:
>> + # type:
>> + # nfsserver:
>> # nfspath:
>> - if kwargs['type'] == 'dir':
>> - xml = """
>> - <pool type='%(type)s'>
>> - <name>%(name)s</name>
>> - <target>
>> - <path>%(path)s</path>
>> - </target>
>> - </pool>
>> - """
>> - elif kwargs['type'] == 'netfs':
>> - path = '/var/lib/kimchi/nfs_mount/' + kwargs['name'];
>> - if not os.path.exists(path):
>> - os.makedirs(path)
>> - kwargs.update({'path': path})
>> - xml = """
>> - <pool type='%(type)s'>
>> - <name>%(name)s</name>
>> - <source>
>> - <host name='%(nfsserver)s'/>
>> - <dir path='%(nfspath)s'/>
>> - </source>
>> - <target>
>> - <path>%(path)s</path>
>> - </target>
>> - </pool>
>> - """
>> - elif kwargs['type'] == 'logical':
>> - path = '/var/lib/kimchi/logical_mount/' + kwargs['name'];
>> - if not os.path.exists(path):
>> - os.makedirs(path)
>> - xml = """
>> - <pool type='%(type)s'>
>> - <name>%(name)s</name>
>> - <source>
>> - %(devices)s
>> - </source>
>> - <target>
>> - <path>%(path)s</path>
>> - </target>
>> - </pool>
>> - """
>> - devices = ''
>> - for device_path in kwargs['devices']:
>> - devices += "<device path=\""+device_path+"\" />"
>> + path = '/var/lib/kimchi/nfs_mount/' + poolArgs['name']
>> + if not os.path.exists(path):
>> + os.makedirs(path)
>> + poolArgs['path'] = path
>> + xml = """
>> + <pool type='%(type)s'>
>> + <name>%(name)s</name>
>> + <source>
>> + <host name='%(nfsserver)s'/>
> Is it not the intention to replace "nfsserver" with "src_host"?
>
>> + <dir path='%(nfspath)s'/>
>
> Is it not the intention to replace "nfspath" with "src_path"?
>
Yes. This is in another patch, "storagepool: rename and consolidate
arguments of creating (back end)"
>> + </source>
>> + <target>
>> + <path>%(path)s</path>
>> + </target>
>> + </pool>
>> + """ % poolArgs
>> + return xml
>> +
>> +
>> +def _get_logical_storagepool_xml(poolArgs):
>> + # Required parameters
>> + # name:
>> + # type:
>> + # devices:
>> + path = '/var/lib/kimchi/logical_mount/' + poolArgs['name']
> Can we make the path configurable? and make
>
> '/var/lib/kimchi/*" as the default setting.
>
>
I agree, let's do it in another patch.
>> + if not os.path.exists(path):
>> + os.makedirs(path)
>> +
>> + devices = []
>> + for device_path in poolArgs['devices']:
>> + devices.append('<device path="%s" />' % device_path)
>> +
>> + poolArgs.update({'devices': ''.join(devices),
>> + 'path': path})
>> + xml = """
>> + <pool type='%(type)s'>
>> + <name>%(name)s</name>
>> + <source>
>> + %(devices)s
>> + </source>
>> + <target>
>> + <path>%(path)s</path>
>> + </target>
>> + </pool>
>> + """ % poolArgs
>> + return xml
>> +
>> +
>> +def _get_pool_xml(**kwargs):
>> + getPoolXml = {
>> + 'dir': _get_dir_storagepool_xml,
>> + 'netfs': _get_netfs_storagepool_xml,
>> + 'logical': _get_logical_storagepool_xml,
>> + }[kwargs['type']]
>> + return getPoolXml(kwargs)
>>
>> - kwargs.update({'devices': devices})
>> - kwargs.update({'path': path})
>> - return xml % kwargs
>>
>> def _get_volume_xml(**kwargs):
>> # Required parameters
>
--
Thanks and best regards!
Zhou Zheng Sheng / 周征晟
E-mail: zhshzhou(a)linux.vnet.ibm.com
Telephone: 86-10-82454397
--
project-kimchi mailing list <project-kimchi(a)googlegroups.com>
https://groups.google.com/forum/#!forum/project-kimchi
---
You received this message because you are subscribed to the Google Groups "project-kimchi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-kimchi+unsubscribe(a)googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
1
0
Hi all,
As I mentioned during the last scrum meeting we have a new mail list now.
For developers: kimchi-devel(a)ovirt.org <mailto:kimchi-devel@ovirt.org>
For users feedback: kimchi-users(a)ovirt.org <mailto:kimchi-users@ovirt.org>
Subscribe to mail lists:
kimchi-devel: http://lists.ovirt.org/mailman/listinfo/kimchi-devel
kimchi-users: http://lists.ovirt.org/mailman/listinfo/kimchi-users
All outstanding patches will need to be resent to kimchi-devel(a)ovirt.org
<mailto:kimchi-devel@ovirt.org> for consideration.
This Google Groups mail will be discontinued.
See you there ;-)
Thanks,
Aline Manera
--
project-kimchi mailing list <project-kimchi(a)googlegroups.com>
https://groups.google.com/forum/#!forum/project-kimchi
---
You received this message because you are subscribed to the Google Groups "project-kimchi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-kimchi+unsubscribe(a)googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
1
0
On behalf of everyone who has worked hard on this release, I am pleased
to announce the availability of Kimchi 1.1! This release adds many new
features including:
* Easy Template creation from Remote ISO images
* Template customization
* Basic guest customization
* NSF and logical Pool support
* Basic network management
* Basic host management
* Support for Kimchi plugins
We have worked hard to ensure that Kimchi runs well on the most popular
Linux distributions including: Fedora 19, Ubuntu 13.10, openSUSE 13.1,
and RHEL 6.5. Kimchi uses standard Linux interfaces so it should run
well on many other distributions too.
You can easily grab this release in tarball format or via git:
*https://github.com/kimchi-project/kimchi/archive/kimchi-1.1.0.tar.gz
* git clonehttps://github.com/kimchi-project/kimchi.git
Go ahead and give it a try and let us know what you think!
Regards,
----
Aline Manera
--
project-kimchi mailing list <project-kimchi(a)googlegroups.com>
https://groups.google.com/forum/#!forum/project-kimchi
---
You received this message because you are subscribed to the Google Groups "project-kimchi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-kimchi+unsubscribe(a)googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
1
0
Follow this rule:
1) Import common modules
import ...
import ...
from ... import ...
from ... import ...
2) Import kimchi modules
import kimchi.<mod>
import kimchi.<mod>
from kimchi import ...
from kimchi import ...
Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo(a)linux.vnet.ibm.com>
---
plugins/sample/__init__.py | 8 ++++++--
plugins/sample/model.py | 2 +-
src/kimchi/model.py | 4 ++--
src/kimchi/server.py | 2 +-
src/kimchi/sslcert.py | 2 +-
src/kimchi/websocket.py | 17 ++++++++++++++---
src/kimchi/websockify.py | 15 +++++++++++++--
tests/test_exception.py | 6 ++++--
tests/test_mockmodel.py | 5 +++--
tests/test_model.py | 18 ++++++++++--------
tests/test_networkxml.py | 4 +++-
tests/test_osinfo.py | 3 +++
tests/test_plugin.py | 4 +++-
tests/test_rest.py | 10 +++++++---
tests/test_server.py | 4 ++--
tests/test_vmtemplate.py | 2 ++
tests/utils.py | 15 +++++++++------
17 files changed, 84 insertions(+), 37 deletions(-)
diff --git a/plugins/sample/__init__.py b/plugins/sample/__init__.py
index a20f5e6..7064904 100644
--- a/plugins/sample/__init__.py
+++ b/plugins/sample/__init__.py
@@ -22,12 +22,16 @@
import json
import os
+
+
from cherrypy import expose
-from kimchi.controller import Resource, Collection
+
+
+from kimchi.controller import Collection, Resource
from model import Model
-model = Model()
+model = Model()
class Drawings(Resource):
def __init__(self):
diff --git a/plugins/sample/model.py b/plugins/sample/model.py
index f6da5d0..9a2f22f 100644
--- a/plugins/sample/model.py
+++ b/plugins/sample/model.py
@@ -20,7 +20,7 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-from kimchi.exception import NotFoundError, InvalidOperation
+from kimchi.exception import InvalidOperation, NotFoundError
class Model(object):
diff --git a/src/kimchi/model.py b/src/kimchi/model.py
index 73c18ac..1e4dac2 100644
--- a/src/kimchi/model.py
+++ b/src/kimchi/model.py
@@ -42,9 +42,9 @@ import time
import uuid
-from collections import defaultdict
from cherrypy.process.plugins import BackgroundTask
from cherrypy.process.plugins import SimplePlugin
+from collections import defaultdict
from xml.etree import ElementTree
@@ -69,7 +69,7 @@ from kimchi.networkxml import to_network_xml
from kimchi.objectstore import ObjectStore
from kimchi.scan import Scanner
from kimchi.screenshot import VMScreenshot
-from kimchi.utils import kimchi_log, is_digit, get_enabled_plugins
+from kimchi.utils import get_enabled_plugins, is_digit, kimchi_log
from kimchi.vmtemplate import VMTemplate
diff --git a/src/kimchi/server.py b/src/kimchi/server.py
index 6ff6fa0..114a3a0 100644
--- a/src/kimchi/server.py
+++ b/src/kimchi/server.py
@@ -33,7 +33,7 @@ from kimchi import config
from kimchi import model
from kimchi import mockmodel
from kimchi.root import Root
-from kimchi.utils import import_class, get_enabled_plugins
+from kimchi.utils import get_enabled_plugins, import_class
LOGGING_LEVEL = {"debug": logging.DEBUG,
diff --git a/src/kimchi/sslcert.py b/src/kimchi/sslcert.py
index 70441f2..529699d 100644
--- a/src/kimchi/sslcert.py
+++ b/src/kimchi/sslcert.py
@@ -28,7 +28,7 @@
import time
-from M2Crypto import X509, EVP, RSA, ASN1
+from M2Crypto import ASN1, EVP, RSA, X509
class SSLCert(object):
diff --git a/src/kimchi/websocket.py b/src/kimchi/websocket.py
index a98fc6d..945676d 100644
--- a/src/kimchi/websocket.py
+++ b/src/kimchi/websocket.py
@@ -16,9 +16,20 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
'''
-import os, sys, time, errno, signal, socket, traceback, select
-import array, struct
-from base64 import b64encode, b64decode
+import array
+import errno
+import os
+import select
+import signal
+import socket
+import struct
+import sys
+import time
+import traceback
+
+
+from base64 import b64decode, b64encode
+
# Imports that vary by python version
diff --git a/src/kimchi/websockify.py b/src/kimchi/websockify.py
index 1154d92..2857e7c 100755
--- a/src/kimchi/websockify.py
+++ b/src/kimchi/websockify.py
@@ -11,15 +11,26 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
'''
-import signal, socket, optparse, time, os, sys, subprocess
+import optparse
+import os
+import signal
+import socket
+import subprocess
+import sys
+import time
+
+
from select import select
-import websocket
try:
from urllib.parse import parse_qs, urlparse
except:
from cgi import parse_qs
from urlparse import urlparse
+
+import websocket
+
+
class WebSocketProxy(websocket.WebSocketServer):
"""
Proxy traffic to and from a WebSockets client to a normal TCP
diff --git a/tests/test_exception.py b/tests/test_exception.py
index 9b5355a..cb60995 100644
--- a/tests/test_exception.py
+++ b/tests/test_exception.py
@@ -20,14 +20,16 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-import unittest
-import os
import json
+import os
+import unittest
+
import kimchi.mockmodel
import kimchi.server
from utils import *
+
test_server = None
model = None
host = None
diff --git a/tests/test_mockmodel.py b/tests/test_mockmodel.py
index b819172..0830a24 100644
--- a/tests/test_mockmodel.py
+++ b/tests/test_mockmodel.py
@@ -20,16 +20,17 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-import unittest
import cherrypy
import json
import os
+import unittest
+
import kimchi.mockmodel
import kimchi.controller
-
from utils import *
+
#utils.silence_server()
test_server = None
model = None
diff --git a/tests/test_model.py b/tests/test_model.py
index fb7d6dd..07b68b1 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -21,21 +21,23 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-import unittest
-import threading
import os
-import time
-import tempfile
-import psutil
import platform
+import psutil
+import tempfile
+import threading
+import time
+import unittest
import uuid
+
+import iso_gen
import kimchi.model
import kimchi.objectstore
-from kimchi.exception import *
-from kimchi import netinfo
import utils
-import iso_gen
+from kimchi import netinfo
+from kimchi.exception import *
+
class ModelTests(unittest.TestCase):
def setUp(self):
diff --git a/tests/test_networkxml.py b/tests/test_networkxml.py
index 4eeeaa2..3073bce 100644
--- a/tests/test_networkxml.py
+++ b/tests/test_networkxml.py
@@ -20,10 +20,12 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+import ipaddr
import unittest
+
+
import kimchi.networkxml as nxml
from kimchi.xmlutils import xpath_get_text
-import ipaddr
class NetworkXmlTests(unittest.TestCase):
diff --git a/tests/test_osinfo.py b/tests/test_osinfo.py
index f92567d..1dcfdaf 100644
--- a/tests/test_osinfo.py
+++ b/tests/test_osinfo.py
@@ -21,8 +21,11 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import unittest
+
+
from kimchi.osinfo import *
+
class OSInfoTests(unittest.TestCase):
def test_default_lookup(self):
name, entry = lookup(None, None)
diff --git a/tests/test_plugin.py b/tests/test_plugin.py
index 20cc598..f12b11f 100644
--- a/tests/test_plugin.py
+++ b/tests/test_plugin.py
@@ -20,17 +20,19 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-import unittest
import json
import os
import sys
+import unittest
from functools import partial
+
import kimchi.mockmodel
import kimchi.server
import utils
from kimchi import config
+
test_server = None
model = None
host = None
diff --git a/tests/test_rest.py b/tests/test_rest.py
index 73b5243..61369c3 100644
--- a/tests/test_rest.py
+++ b/tests/test_rest.py
@@ -20,16 +20,20 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-import unittest
import json
-import time
import os
+import time
+import unittest
+
+
from functools import partial
+
import kimchi.mockmodel
import kimchi.server
-from utils import *
from kimchi.asynctask import AsyncTask
+from utils import *
+
test_server = None
model = None
diff --git a/tests/test_server.py b/tests/test_server.py
index 9bb0034..734a618 100644
--- a/tests/test_server.py
+++ b/tests/test_server.py
@@ -20,12 +20,12 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-import unittest
import json
import os
+import unittest
-import utils
+import utils
import kimchi.mockmodel
#utils.silence_server()
diff --git a/tests/test_vmtemplate.py b/tests/test_vmtemplate.py
index 81382c7..92c7385 100644
--- a/tests/test_vmtemplate.py
+++ b/tests/test_vmtemplate.py
@@ -23,9 +23,11 @@
import unittest
import uuid
+
from kimchi.vmtemplate import *
from kimchi.xmlutils import xpath_get_text
+
class VMTemplateTests(unittest.TestCase):
def test_minimal_construct(self):
fields = (('name', 'test'), ('os_distro', 'unknown'),
diff --git a/tests/utils.py b/tests/utils.py
index c114813..a7596e8 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -21,16 +21,19 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
-import httplib
+import base64
import cherrypy
-import threading
-import time
+import httplib
import os
-import sys
import socket
-from contextlib import closing
+import sys
+import threading
+import time
import unittest
-import base64
+
+
+from contextlib import closing
+
import kimchi.server
import kimchi.model
--
1.8.1.4
--
project-kimchi mailing list <project-kimchi(a)googlegroups.com>
https://groups.google.com/forum/#!forum/project-kimchi
---
You received this message because you are subscribed to the Google Groups "project-kimchi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-kimchi+unsubscribe(a)googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
2
1
18 Dec '13
This patch added some extra translations suggested by Aline and
new translations added in Kimchi.
The change in chinese was done by make command.
--
project-kimchi mailing list <project-kimchi(a)googlegroups.com>
https://groups.google.com/forum/#!forum/project-kimchi
---
You received this message because you are subscribed to the Google Groups "project-kimchi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-kimchi+unsubscribe(a)googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
2
2
[project-kimchi] [PATCH] do Chinese translation for release 1.1
by shaohef@linux.vnet.ibm.com 18 Dec '13
by shaohef@linux.vnet.ibm.com 18 Dec '13
18 Dec '13
From: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>
Update en_US.po, kimchi.pot and zh_CN.po.
Do not touch pt_BR.po
Signed-off-by: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>
---
po/en_US.po | 357 +++++++++++++++++++++++++++++++++++++++------------------
po/kimchi.pot | 271 +++++++++++++++++++++++++++++++------------
po/zh_CN.po | 361 +++++++++++++++++++++++++++++++++++++++-------------------
3 files changed, 690 insertions(+), 299 deletions(-)
diff --git a/po/en_US.po b/po/en_US.po
index 86d7c81..7b6b1d7 100644
--- a/po/en_US.po
+++ b/po/en_US.po
@@ -6,14 +6,14 @@ msgid ""
msgstr ""
"Project-Id-Version: kimchi 0.1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-11-25 16:33+0800\n"
+"POT-Creation-Date: 2013-12-18 23:43+0800\n"
"PO-Revision-Date: 2013-07-11 17:32-0400\n"
"Last-Translator: Crístian Viana <vianac(a)linux.vnet.ibm.com>\n"
"Language-Team: English\n"
+"Language: en_US\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Language: en_US\n"
"Generated-By: pygettext.py 1.5\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -144,6 +144,24 @@ msgstr "Memory"
msgid "Create"
msgstr "Create"
+msgid "Edit Guest"
+msgstr "Edit Guest"
+
+msgid "Name"
+msgstr "Name"
+
+msgid "CPUs"
+msgstr "CPUs"
+
+msgid "Icon"
+msgstr "Icon"
+
+msgid "Cancel"
+msgstr "Cancel"
+
+msgid "Save"
+msgstr "Save"
+
msgid "Reset"
msgstr "Reset"
@@ -156,20 +174,8 @@ msgstr "Stop"
msgid "Actions"
msgstr "Actions"
-msgid "Name"
-msgstr "Name"
-
-msgid "CPU"
-msgstr "CPU"
-
-msgid "Network I/O"
-msgstr "Network I/O"
-
-msgid "Disk I/O"
-msgstr "Disk I/O"
-
-msgid "Livetile"
-msgstr "Livetile"
+msgid "Edit"
+msgstr "Edit"
msgid "Failed to list the template"
msgstr "Failed to list the template"
@@ -213,6 +219,9 @@ msgstr "Failed to list guests"
msgid "Failed to create template"
msgstr "Failed to create template"
+msgid "Create template successfully"
+msgstr "Create template successfully"
+
msgid "No iso found"
msgstr "No iso found"
@@ -228,8 +237,70 @@ msgstr "Delete Confirmation"
msgid "OK"
msgstr "OK"
-msgid "Cancel"
-msgstr "Cancel"
+msgid "Max:"
+msgstr "Max:"
+
+msgid "Utilization"
+msgstr "Utilization"
+
+msgid "Available"
+msgstr "Available"
+
+msgid "Read Rate"
+msgstr "Read Rate"
+
+msgid "Write Rate"
+msgstr "Write Rate"
+
+msgid "Received"
+msgstr "Received"
+
+msgid "Sent"
+msgstr "Sent"
+
+msgid "Confirm"
+msgstr "Confirm"
+
+msgid ""
+"Debug report will be removed permanently and can't be recovered. Do you want "
+"to continue?"
+msgstr ""
+"Debug report will be removed permanently and can't be recovered. Do you want "
+"to continue?"
+
+msgid "Debug Reports"
+msgstr "Debug Reports"
+
+msgid "File Path"
+msgstr "File Path"
+
+msgid "Generated Time"
+msgstr "Generated Time"
+
+msgid "Generate"
+msgstr "Generate"
+
+msgid "Generating..."
+msgstr "Generating..."
+
+msgid "Rename"
+msgstr "Rename"
+
+msgid "Remove"
+msgstr "Remove"
+
+msgid "Download"
+msgstr "Download"
+
+msgid "Some VM(s) are running!"
+msgstr "Some VM(s) are running!"
+
+msgid ""
+"Shutting down or restarting host will cause unsaved work lost. Continue to "
+"shut down/restarting?"
+msgstr ""
+"Shutting down or restarting host will cause unsaved work lost. Continue to "
+"shut down/restarting?"
msgid ""
"This will delete the VM and its virtual disks. This operation cannot be "
@@ -298,6 +369,12 @@ msgstr "The storage pool is not active now."
msgid "Failed to delete template."
msgstr "Failed to delete template."
+msgid "unavailable"
+msgstr "unavailable"
+
+msgid "Network"
+msgstr "Network"
+
msgid "isolated"
msgstr "isolated"
@@ -323,15 +400,16 @@ msgstr "Create a network"
msgid "Warning"
msgstr "Warning"
+msgid ""
+"It will format your disk and you will loose any data in there, are you sure "
+"to continue? "
+msgstr ""
+"It will format your disk and you will loose any data in there, are you sure "
+"to continue? "
+
msgid "Log out"
msgstr "Log out"
-msgid "unavailable"
-msgstr "unavailable"
-
-msgid "Network"
-msgstr "Network"
-
msgid "Log In"
msgstr "Log In"
@@ -341,9 +419,136 @@ msgstr "Name"
msgid "Password"
msgstr "Password"
+msgid "Generate a New Debug Report"
+msgstr "Generate a New Debug Report"
+
+msgid "Report Name"
+msgstr "Report Name"
+
+msgid ""
+"The name used to identify the report. If omitted, a name will be chosen "
+"based on current time. Name can contain: letters, digits, \"-\", \"_\", or "
+"\".\"."
+msgstr ""
+"The name used to identify the report. If omitted, a name will be chosen "
+"based on current time. Name can contain: letters, digits, \"-\", \"_\", or "
+"\".\"."
+
+msgid "Define a New Storage Pool"
+msgstr "Define a New Storage Pool"
+
+msgid "Storage Pool Name"
+msgstr "Storage Pool Name"
+
+msgid ""
+"The name used to identify the storage pools, and it should not be empty."
+msgstr ""
+"The name used to identify the storage pools, and it should not be empty."
+
+msgid "Storage Pool Type"
+msgstr "Storage Pool Type"
+
+msgid "Storage Path"
+msgstr "Storage Path"
+
+msgid ""
+"The path of the Storage Pool. Each Storage Pool must have a unique path."
+msgstr ""
+"The path of the Storage Pool. Each Storage Pool must have a unique path."
+
+msgid "NFS server IP"
+msgstr "NFS server IP"
+
+msgid "NFS server IP or hostname. It should not be empty."
+msgstr "NFS server IP or hostname. It should not be empty."
+
+msgid "NFS Path"
+msgstr "NFS Path"
+
+msgid "The nfs exported path on nfs server"
+msgstr "The nfs exported path on nfs server"
+
+msgid "Device Path"
+msgstr "Device Path"
+
+msgid "CPU"
+msgstr "CPU"
+
+msgid "Network I/O"
+msgstr "Network I/O"
+
+msgid "Disk I/O"
+msgstr "Disk I/O"
+
+msgid "Livetile"
+msgstr "Livetile"
+
+msgid "No guests found."
+msgstr "No guests found."
+
+msgid "Shut down"
+msgstr "Shut down"
+
+msgid "Restart"
+msgstr "Restart"
+
+msgid "Connect"
+msgstr "Connect"
+
+msgid "Basic Information"
+msgstr "Basic Information"
+
+msgid "OS Distro"
+msgstr "OS Distro"
+
+msgid "OS Code Name"
+msgstr "OS Code Name"
+
+msgid "Processor"
+msgstr "Processor"
+
+msgid "System Statistics"
+msgstr "System Statistics"
+
+msgid "Collecting data after leaving this page"
+msgstr "Collecting data after leaving this page"
+
+msgid "Network Name"
+msgstr "Network Name"
+
msgid "State"
msgstr "State"
+msgid "Network Type"
+msgstr "Network Type"
+
+msgid "Interface"
+msgstr "Interface"
+
+msgid "Address Space"
+msgstr "Address Space"
+
+msgid "Alphanumeric and '_' characters only."
+msgstr "Alphanumeric and '_' characters only."
+
+msgid "Isolated: no physical network connection"
+msgstr "Isolated: no physical network connection"
+
+msgid "NAT: outbound physical network connection only"
+msgstr "NAT: outbound physical network connection only"
+
+msgid "Bridged: VMs are connected to physical network directly"
+msgstr "Bridged: VMs are connected to physical network directly"
+
+msgid "Destination"
+msgstr "Destination"
+
+msgid "No templates found."
+msgstr "No templates found."
+
+msgid "Version"
+msgstr "Version"
+
msgid "Location"
msgstr "Location"
@@ -377,25 +582,6 @@ msgstr "Format"
msgid "Allocation"
msgstr "Allocation"
-msgid "Define a New Storage Pool"
-msgstr "Define a New Storage Pool"
-
-msgid "Storage Pool Name"
-msgstr "Storage Pool Name"
-
-msgid ""
-"The name used to identify the storage pools, and it should not be empty."
-msgstr ""
-"The name used to identify the storage pools, and it should not be empty."
-
-msgid "Storage Path"
-msgstr "Storage Path"
-
-msgid ""
-"The path of the Storage Pool. Each Storage Pool must have a unique path."
-msgstr ""
-"The path of the Storage Pool. Each Storage Pool must have a unique path."
-
msgid "Add Template"
msgstr "Add Template"
@@ -408,6 +594,12 @@ msgstr "Local ISO Image"
msgid "Remote ISO Image"
msgstr "Remote ISO Image"
+msgid "Search ISOs"
+msgstr "Search ISOs"
+
+msgid "Please, wait..."
+msgstr "Please, wait..."
+
msgid "The following ISOs are available:"
msgstr "The following ISOs are available:"
@@ -423,6 +615,9 @@ msgstr "Version: "
msgid "Size: "
msgstr "Size: "
+msgid "Search more ISOs"
+msgstr "Search more ISOs"
+
msgid "Create Templates from Selected ISO"
msgstr "Create Templates from Selected ISO"
@@ -438,74 +633,14 @@ msgstr "Edit Template"
msgid "Vendor"
msgstr "Vendor"
-msgid "Version"
-msgstr "Version"
-
-msgid "Icon"
-msgstr "Icon"
-
-msgid "Default"
-msgstr "Default"
-
-msgid "Background Color"
-msgstr "Background Color"
-
-msgid "Overlay Type"
-msgstr "Overlay Type"
+msgid "CPU Number"
+msgstr "CPU Number"
-msgid "Overlay Type Icon"
-msgstr "Overlay Type Icon"
+msgid "Disk (GB)"
+msgstr "Disk (GB)"
-msgid "Overlay Type Text"
-msgstr "Overlay Type Text"
+msgid "CDROM"
+msgstr "CDROM"
-msgid "Button Finish Style"
-msgstr "Button Finish Style"
-
-msgid "Button Finish Style Gloss"
-msgstr "Button Finish Style Gloss"
-
-msgid "Button Finish Style Gradient"
-msgstr "Button Finish Style Gradient"
-
-msgid "Save"
-msgstr "Save"
-
-msgid "No templates found."
-msgstr "No templates found."
-
-msgid "Edit"
-msgstr "Edit"
-
-msgid "Default Settings"
-msgstr "Default Settings"
-
-msgid "CPUs"
-msgstr "CPUs"
-
-msgid "Network Name"
-msgstr "Network Name"
-
-msgid "Network Type"
-msgstr "Network Type"
-
-msgid "Interface"
-msgstr "Interface"
-
-msgid "Address Space"
-msgstr "Address Space"
-
-msgid "Alphanumeric and '_' characters only."
-msgstr "Alphanumeric and '_' characters only."
-
-msgid "Isolated: no physical network connection"
-msgstr "Isolated: no physical network connection"
-
-msgid "NAT: outbound physical network connection only"
-msgstr "NAT: outbound physical network connection only"
-
-msgid "Bridged: VMs are connected to physical network directly"
-msgstr "Bridged: VMs are connected to physical network directly"
-
-msgid "Destination"
-msgstr "Destination"
+msgid "Storage Pool"
+msgstr "Storage Pool"
diff --git a/po/kimchi.pot b/po/kimchi.pot
index 1d2c688..c83f089 100644
--- a/po/kimchi.pot
+++ b/po/kimchi.pot
@@ -8,10 +8,11 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-11-25 16:33+0800\n"
+"POT-Creation-Date: 2013-12-18 23:43+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL(a)li.org>\n"
+"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -141,31 +142,37 @@ msgstr ""
msgid "Create"
msgstr ""
-msgid "Reset"
+msgid "Edit Guest"
msgstr ""
-msgid "Start"
+msgid "Name"
msgstr ""
-msgid "Stop"
+msgid "CPUs"
msgstr ""
-msgid "Actions"
+msgid "Icon"
msgstr ""
-msgid "Name"
+msgid "Cancel"
msgstr ""
-msgid "CPU"
+msgid "Save"
msgstr ""
-msgid "Network I/O"
+msgid "Reset"
msgstr ""
-msgid "Disk I/O"
+msgid "Start"
msgstr ""
-msgid "Livetile"
+msgid "Stop"
+msgstr ""
+
+msgid "Actions"
+msgstr ""
+
+msgid "Edit"
msgstr ""
msgid "Failed to list the template"
@@ -210,6 +217,9 @@ msgstr ""
msgid "Failed to create template"
msgstr ""
+msgid "Create template successfully"
+msgstr ""
+
msgid "No iso found"
msgstr ""
@@ -225,7 +235,65 @@ msgstr ""
msgid "OK"
msgstr ""
-msgid "Cancel"
+msgid "Max:"
+msgstr ""
+
+msgid "Utilization"
+msgstr ""
+
+msgid "Available"
+msgstr ""
+
+msgid "Read Rate"
+msgstr ""
+
+msgid "Write Rate"
+msgstr ""
+
+msgid "Received"
+msgstr ""
+
+msgid "Sent"
+msgstr ""
+
+msgid "Confirm"
+msgstr ""
+
+msgid ""
+"Debug report will be removed permanently and can't be recovered. Do you want "
+"to continue?"
+msgstr ""
+
+msgid "Debug Reports"
+msgstr ""
+
+msgid "File Path"
+msgstr ""
+
+msgid "Generated Time"
+msgstr ""
+
+msgid "Generate"
+msgstr ""
+
+msgid "Generating..."
+msgstr ""
+
+msgid "Rename"
+msgstr ""
+
+msgid "Remove"
+msgstr ""
+
+msgid "Download"
+msgstr ""
+
+msgid "Some VM(s) are running!"
+msgstr ""
+
+msgid ""
+"Shutting down or restarting host will cause unsaved work lost. Continue to "
+"shut down/restarting?"
msgstr ""
msgid ""
@@ -290,6 +358,12 @@ msgstr ""
msgid "Failed to delete template."
msgstr ""
+msgid "unavailable"
+msgstr ""
+
+msgid "Network"
+msgstr ""
+
msgid "isolated"
msgstr ""
@@ -313,13 +387,12 @@ msgstr ""
msgid "Warning"
msgstr ""
-msgid "Log out"
-msgstr ""
-
-msgid "unavailable"
+msgid ""
+"It will format your disk and you will loose any data in there, are you sure "
+"to continue? "
msgstr ""
-msgid "Network"
+msgid "Log out"
msgstr ""
msgid "Log In"
@@ -331,169 +404,223 @@ msgstr ""
msgid "Password"
msgstr ""
-msgid "State"
+msgid "Generate a New Debug Report"
msgstr ""
-msgid "Location"
+msgid "Report Name"
msgstr ""
-msgid "Type"
+msgid ""
+"The name used to identify the report. If omitted, a name will be chosen "
+"based on current time. Name can contain: letters, digits, \"-\", \"_\", or "
+"\".\"."
msgstr ""
-msgid "Capacity"
+msgid "Define a New Storage Pool"
msgstr ""
-msgid "Allocated"
+msgid "Storage Pool Name"
msgstr ""
-msgid "active"
+msgid ""
+"The name used to identify the storage pools, and it should not be empty."
msgstr ""
-msgid "inactive"
+msgid "Storage Pool Type"
msgstr ""
-msgid "Deactivate"
+msgid "Storage Path"
msgstr ""
-msgid "Activate"
+msgid ""
+"The path of the Storage Pool. Each Storage Pool must have a unique path."
msgstr ""
-msgid "Undefine"
+msgid "NFS server IP"
msgstr ""
-msgid "Format"
+msgid "NFS server IP or hostname. It should not be empty."
msgstr ""
-msgid "Allocation"
+msgid "NFS Path"
msgstr ""
-msgid "Define a New Storage Pool"
+msgid "The nfs exported path on nfs server"
msgstr ""
-msgid "Storage Pool Name"
+msgid "Device Path"
msgstr ""
-msgid ""
-"The name used to identify the storage pools, and it should not be empty."
+msgid "CPU"
msgstr ""
-msgid "Storage Path"
+msgid "Network I/O"
msgstr ""
-msgid ""
-"The path of the Storage Pool. Each Storage Pool must have a unique path."
+msgid "Disk I/O"
msgstr ""
-msgid "Add Template"
+msgid "Livetile"
msgstr ""
-msgid "Where is the source media for this template? "
+msgid "No guests found."
msgstr ""
-msgid "Local ISO Image"
+msgid "Shut down"
msgstr ""
-msgid "Remote ISO Image"
+msgid "Restart"
msgstr ""
-msgid "The following ISOs are available:"
+msgid "Connect"
msgstr ""
-msgid "All"
+msgid "Basic Information"
msgstr ""
-msgid "OS: "
+msgid "OS Distro"
msgstr ""
-msgid "Version: "
+msgid "OS Code Name"
msgstr ""
-msgid "Size: "
+msgid "Processor"
msgstr ""
-msgid "Create Templates from Selected ISO"
+msgid "System Statistics"
msgstr ""
-msgid "I want to use a specific ISO file"
+msgid "Collecting data after leaving this page"
msgstr ""
-msgid "I want to use a custom URL"
+msgid "Network Name"
msgstr ""
-msgid "Edit Template"
+msgid "State"
msgstr ""
-msgid "Vendor"
+msgid "Network Type"
+msgstr ""
+
+msgid "Interface"
+msgstr ""
+
+msgid "Address Space"
+msgstr ""
+
+msgid "Alphanumeric and '_' characters only."
+msgstr ""
+
+msgid "Isolated: no physical network connection"
+msgstr ""
+
+msgid "NAT: outbound physical network connection only"
+msgstr ""
+
+msgid "Bridged: VMs are connected to physical network directly"
+msgstr ""
+
+msgid "Destination"
+msgstr ""
+
+msgid "No templates found."
msgstr ""
msgid "Version"
msgstr ""
-msgid "Icon"
+msgid "Location"
msgstr ""
-msgid "Default"
+msgid "Type"
msgstr ""
-msgid "Background Color"
+msgid "Capacity"
msgstr ""
-msgid "Overlay Type"
+msgid "Allocated"
msgstr ""
-msgid "Overlay Type Icon"
+msgid "active"
msgstr ""
-msgid "Overlay Type Text"
+msgid "inactive"
msgstr ""
-msgid "Button Finish Style"
+msgid "Deactivate"
msgstr ""
-msgid "Button Finish Style Gloss"
+msgid "Activate"
msgstr ""
-msgid "Button Finish Style Gradient"
+msgid "Undefine"
msgstr ""
-msgid "Save"
+msgid "Format"
msgstr ""
-msgid "No templates found."
+msgid "Allocation"
msgstr ""
-msgid "Edit"
+msgid "Add Template"
msgstr ""
-msgid "Default Settings"
+msgid "Where is the source media for this template? "
msgstr ""
-msgid "CPUs"
+msgid "Local ISO Image"
msgstr ""
-msgid "Network Name"
+msgid "Remote ISO Image"
msgstr ""
-msgid "Network Type"
+msgid "Search ISOs"
msgstr ""
-msgid "Interface"
+msgid "Please, wait..."
msgstr ""
-msgid "Address Space"
+msgid "The following ISOs are available:"
msgstr ""
-msgid "Alphanumeric and '_' characters only."
+msgid "All"
msgstr ""
-msgid "Isolated: no physical network connection"
+msgid "OS: "
msgstr ""
-msgid "NAT: outbound physical network connection only"
+msgid "Version: "
msgstr ""
-msgid "Bridged: VMs are connected to physical network directly"
+msgid "Size: "
msgstr ""
-msgid "Destination"
+msgid "Search more ISOs"
+msgstr ""
+
+msgid "Create Templates from Selected ISO"
+msgstr ""
+
+msgid "I want to use a specific ISO file"
+msgstr ""
+
+msgid "I want to use a custom URL"
+msgstr ""
+
+msgid "Edit Template"
+msgstr ""
+
+msgid "Vendor"
+msgstr ""
+
+msgid "CPU Number"
+msgstr ""
+
+msgid "Disk (GB)"
+msgstr ""
+
+msgid "CDROM"
+msgstr ""
+
+msgid "Storage Pool"
msgstr ""
diff --git a/po/zh_CN.po b/po/zh_CN.po
index 2a34051..6d13f4e 100644
--- a/po/zh_CN.po
+++ b/po/zh_CN.po
@@ -20,14 +20,14 @@ msgid ""
msgstr ""
"Project-Id-Version: kimchi 0.1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-11-25 16:33+0800\n"
+"POT-Creation-Date: 2013-12-18 23:43+0800\n"
"PO-Revision-Date: 2013-06-27 10:48+0000\n"
"Last-Translator: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>\n"
"Language-Team: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>\n"
+"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Language: zh_CN\n"
"Generated-By: pygettext.py 1.5\n"
"X-Poedit-Country: CHINA\n"
"X-Poedit-Language: Chinese\n"
@@ -158,6 +158,24 @@ msgstr "内存"
msgid "Create"
msgstr "创建"
+msgid "Edit Guest"
+msgstr "修改客户机"
+
+msgid "Name"
+msgstr "名称"
+
+msgid "CPUs"
+msgstr "中央处理器"
+
+msgid "Icon"
+msgstr "图标"
+
+msgid "Cancel"
+msgstr "取消"
+
+msgid "Save"
+msgstr "保存"
+
msgid "Reset"
msgstr "重置"
@@ -170,20 +188,8 @@ msgstr "停止"
msgid "Actions"
msgstr "操作"
-msgid "Name"
-msgstr "名称"
-
-msgid "CPU"
-msgstr "处理器"
-
-msgid "Network I/O"
-msgstr "网络I/O"
-
-msgid "Disk I/O"
-msgstr "磁盘I/O"
-
-msgid "Livetile"
-msgstr "屏幕"
+msgid "Edit"
+msgstr "编辑"
msgid "Failed to list the template"
msgstr "展示模板列表失败"
@@ -227,6 +233,9 @@ msgstr "客户机列表加载失败"
msgid "Failed to create template"
msgstr "模板创建失败"
+msgid "Create template successfully"
+msgstr "创建模板成功"
+
msgid "No iso found"
msgstr "没有发现ISO文件"
@@ -242,8 +251,66 @@ msgstr "删除确认"
msgid "OK"
msgstr "确定"
-msgid "Cancel"
-msgstr "取消"
+msgid "Max:"
+msgstr "最大:"
+
+msgid "Utilization"
+msgstr "利用率"
+
+msgid "Available"
+msgstr "可利用的"
+
+msgid "Read Rate"
+msgstr "读速率"
+
+msgid "Write Rate"
+msgstr "写速率"
+
+msgid "Received"
+msgstr "接收"
+
+msgid "Sent"
+msgstr "发送"
+
+msgid "Confirm"
+msgstr "确认"
+
+msgid ""
+"Debug report will be removed permanently and can't be recovered. Do you want "
+"to continue?"
+msgstr "诊断报告将被永久删除,并且不能恢复。继续?"
+
+msgid "Debug Reports"
+msgstr "主机诊断报告"
+
+msgid "File Path"
+msgstr "文件路径"
+
+msgid "Generated Time"
+msgstr "生成时间"
+
+msgid "Generate"
+msgstr "生成"
+
+msgid "Generating..."
+msgstr "正在生成..."
+
+msgid "Rename"
+msgstr "重命名"
+
+msgid "Remove"
+msgstr "删除"
+
+msgid "Download"
+msgstr "下载"
+
+msgid "Some VM(s) are running!"
+msgstr "有虚拟机在运行!"
+
+msgid ""
+"Shutting down or restarting host will cause unsaved work lost. Continue to "
+"shut down/restarting?"
+msgstr "关闭或者重启主机会导致没有保存的工作丢失。继续关机/重启?"
msgid ""
"This will delete the VM and its virtual disks. This operation cannot be "
@@ -263,7 +330,7 @@ msgid "This is not a valid url."
msgstr "这不是一个有效的URL"
msgid "This is not a valid ISO file."
-msgstr ""
+msgstr "这不是一个有效的ISO文件"
msgid ""
"This will permanently delete the Storage Pool. Would you like to continue?"
@@ -276,10 +343,10 @@ msgid "The storage pool path can not be blank."
msgstr "存储池的路径不能为空"
msgid "NFS server can not be blank."
-msgstr ""
+msgstr "NFS服务器不能为空"
msgid "NFS server mount path can not be blank."
-msgstr ""
+msgstr "NFS服务器挂载路径不能为空"
msgid ""
"Invalid Storage Pool name. It may only contain letters, numbers, "
@@ -290,10 +357,10 @@ msgid "This is not a real linux path."
msgstr "这不是一个符合要求的LINUX路径"
msgid "This is not a valid NFS server."
-msgstr ""
+msgstr "这不是一个有效的NFS服务器"
msgid "Invalid nfs mount path."
-msgstr ""
+msgstr "无效的nfs挂载路径"
msgid "This storage pool is empty."
msgstr "这个存储池为空"
@@ -305,7 +372,13 @@ msgid "The storage pool is not active now."
msgstr "存储池没有被启用"
msgid "Failed to delete template."
-msgstr ""
+msgstr "删除模版失败"
+
+msgid "unavailable"
+msgstr "无法获取"
+
+msgid "Network"
+msgstr "网络"
msgid "isolated"
msgstr "隔离"
@@ -330,15 +403,15 @@ msgstr "创建一个网络"
msgid "Warning"
msgstr "警告"
+msgid ""
+"It will format your disk and you will loose any data in there, are you sure "
+"to continue? "
+msgstr ""
+"你的磁盘将会格式化,磁盘上的数据会丢失,你确定要继续吗?"
+
msgid "Log out"
msgstr "登出"
-msgid "unavailable"
-msgstr "无法获取"
-
-msgid "Network"
-msgstr "网络"
-
msgid "Log In"
msgstr "登录"
@@ -348,9 +421,133 @@ msgstr "用户名"
msgid "Password"
msgstr "密码"
+msgid "Generate a New Debug Report"
+msgstr "产生一个新的诊断报告"
+
+msgid "Report Name"
+msgstr "诊断报告名"
+
+msgid ""
+"The name used to identify the report. If omitted, a name will be chosen "
+"based on current time. Name can contain: letters, digits, \"-\", \"_\", or "
+"\".\"."
+msgstr ""
+"该名称用来唯一标识报告,如果不指定,将会基于当前时间自动生成一个名称。名称可"
+"以包含: 字母、数字、\"-\"、\"_\" 或者 \".\""
+
+msgid "Define a New Storage Pool"
+msgstr "定义一个新的存储池"
+
+msgid "Storage Pool Name"
+msgstr "存储池名称"
+
+msgid ""
+"The name used to identify the storage pools, and it should not be empty."
+msgstr "该名称用来唯一标识存储池,该名称不能为空。"
+
+msgid "Storage Pool Type"
+msgstr "存储池类型"
+
+msgid "Storage Path"
+msgstr "存储路径"
+
+msgid ""
+"The path of the Storage Pool. Each Storage Pool must have a unique path."
+msgstr "存储池的路径.每个存储池的路径是唯一的。"
+
+msgid "NFS server IP"
+msgstr "NFS服务器IP"
+
+msgid "NFS server IP or hostname. It should not be empty."
+msgstr "NFS服务器IP或者主机名, 不能为空。"
+
+msgid "NFS Path"
+msgstr "NFS 路径"
+
+msgid "The nfs exported path on nfs server"
+msgstr "nfs服务器上导出的路径"
+
+msgid "Device Path"
+msgstr "设备路径"
+
+msgid "CPU"
+msgstr "处理器"
+
+msgid "Network I/O"
+msgstr "网络I/O"
+
+msgid "Disk I/O"
+msgstr "磁盘I/O"
+
+msgid "Livetile"
+msgstr "屏幕"
+
+msgid "No guests found."
+msgstr "没有发现客户机"
+
+msgid "Shut down"
+msgstr "关机"
+
+msgid "Restart"
+msgstr "重启"
+
+msgid "Connect"
+msgstr "连接到"
+
+msgid "Basic Information"
+msgstr "基本信息"
+
+msgid "OS Distro"
+msgstr "操作系统发布版本"
+
+msgid "OS Code Name"
+msgstr "操作系统代码"
+
+msgid "Processor"
+msgstr "处理器"
+
+msgid "System Statistics"
+msgstr "系统统计信息"
+
+msgid "Collecting data after leaving this page"
+msgstr "离开该页面继续收集数据"
+
+msgid "Network Name"
+msgstr "网络名称"
+
msgid "State"
msgstr "状态"
+msgid "Network Type"
+msgstr "网络类型"
+
+msgid "Interface"
+msgstr "网络接口"
+
+msgid "Address Space"
+msgstr "地址空间"
+
+msgid "Alphanumeric and '_' characters only."
+msgstr "字母,数字和下划线。"
+
+msgid "Isolated: no physical network connection"
+msgstr "隔离: 同物理网络不连通"
+
+msgid "NAT: outbound physical network connection only"
+msgstr "NAT: 从虚拟机到物理网络单向连接"
+
+msgid "Bridged: VMs are connected to physical network directly"
+msgstr "桥接: 虚拟机直接接入物理网络"
+
+msgid "Destination"
+msgstr "目标设备"
+
+msgid "No templates found."
+msgstr "没有发现模板"
+
+msgid "Version"
+msgstr "版本"
+
msgid "Location"
msgstr "路径"
@@ -384,23 +581,6 @@ msgstr "格式"
msgid "Allocation"
msgstr "分配"
-msgid "Define a New Storage Pool"
-msgstr "定义一个新的存储池"
-
-msgid "Storage Pool Name"
-msgstr "存储池名称"
-
-msgid ""
-"The name used to identify the storage pools, and it should not be empty."
-msgstr "该名称用来唯一标识存储池,如果不指定,将会基于模板自动生成一个名称。"
-
-msgid "Storage Path"
-msgstr "存储路径"
-
-msgid ""
-"The path of the Storage Pool. Each Storage Pool must have a unique path."
-msgstr "存储池的路径.每个存储池的路径是唯一的。"
-
msgid "Add Template"
msgstr "创建模板"
@@ -413,6 +593,12 @@ msgstr "本地ISO镜像"
msgid "Remote ISO Image"
msgstr "远程ISO镜像"
+msgid "Search ISOs"
+msgstr "搜索ISO"
+
+msgid "Please, wait..."
+msgstr "请等待..."
+
msgid "The following ISOs are available:"
msgstr "可用ISO文件如下"
@@ -428,11 +614,14 @@ msgstr "版本: "
msgid "Size: "
msgstr "大小:"
+msgid "Search more ISOs"
+msgstr "搜索更多ISO"
+
msgid "Create Templates from Selected ISO"
msgstr "从选中的ISO中创建模板"
msgid "I want to use a specific ISO file"
-msgstr ""
+msgstr "指定一个ISO文件"
msgid "I want to use a custom URL"
msgstr "我想用一个自定义的URL"
@@ -443,74 +632,14 @@ msgstr "编辑模板"
msgid "Vendor"
msgstr "厂商"
-msgid "Version"
-msgstr "版本"
-
-msgid "Icon"
-msgstr "图标"
-
-msgid "Default"
-msgstr "默认"
-
-msgid "Background Color"
-msgstr "背景颜色"
-
-msgid "Overlay Type"
-msgstr "叠层类型"
-
-msgid "Overlay Type Icon"
-msgstr "叠层型图标"
-
-msgid "Overlay Type Text"
-msgstr "叠层型文本"
-
-msgid "Button Finish Style"
-msgstr "完成按钮样式"
-
-msgid "Button Finish Style Gloss"
-msgstr "纯色"
-
-msgid "Button Finish Style Gradient"
-msgstr "渐变色"
-
-msgid "Save"
-msgstr "保存"
-
-msgid "No templates found."
-msgstr "没有发现模板"
-
-msgid "Edit"
-msgstr "编辑"
-
-msgid "Default Settings"
-msgstr "默认设置"
-
-msgid "CPUs"
-msgstr "中央处理器"
+msgid "CPU Number"
+msgstr "CPU个数"
-msgid "Network Name"
-msgstr "网络名称"
+msgid "Disk (GB)"
+msgstr "磁盘(GB)"
-msgid "Network Type"
-msgstr "网络类型"
-
-msgid "Interface"
-msgstr "网络接口"
-
-msgid "Address Space"
-msgstr "地址空间"
+msgid "CDROM"
+msgstr "光驱"
-msgid "Alphanumeric and '_' characters only."
-msgstr "字母,数字和下划线。"
-
-msgid "Isolated: no physical network connection"
-msgstr "隔离: 同物理网络不连通"
-
-msgid "NAT: outbound physical network connection only"
-msgstr "NAT: 从虚拟机到物理网络单向连接"
-
-msgid "Bridged: VMs are connected to physical network directly"
-msgstr "桥接: 虚拟机直接接入物理网络"
-
-msgid "Destination"
-msgstr "目标设备"
+msgid "Storage Pool"
+msgstr "存储池"
--
1.7.11.7
--
project-kimchi mailing list <project-kimchi(a)googlegroups.com>
https://groups.google.com/forum/#!forum/project-kimchi
---
You received this message because you are subscribed to the Google Groups "project-kimchi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-kimchi+unsubscribe(a)googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
3
2
Hi all,
I find block device information in sysfs is accurate and complete.
Firstly run
lsblk -bPo NAME,MAJ:MIN
It shows
NAME="vda" MAJ:MIN="253:0"
NAME="vda1" MAJ:MIN="253:1"
When there is multipath, it shows
NAME="35000cca68ac8e2fd" MAJ:MIN="253:0"
NAME="35000cca68ac8e2fdp1" MAJ:MIN="253:1"
Then we parse the major and min, and cat the related sysfs uevent file
cat /sys/dev/block/253\:0/uevent
It shows
MAJOR=253
MINOR=0
DEVNAME=vda
DEVTYPE=disk
When there is multipath, it shows
MAJOR=253
MINOR=0
DEVNAME=dm-0
DEVTYPE=disk
The "DEVNAME=vda" or "DEVNAME=dm-0" is the device node name under the
"/dev/"
So there can be
/dev/vda
or
/dev/dm-0
In fact, on my machine, the multipath device node is a link to dm-0
/dev/mapper/35000cca68ac8e2fd -> ../dm-0
So we can show in front-end like following.
vda1 (/dev/vda1)
35000cca68ac8e2fd (/dev/dm-0)
The use mostly cares the name, the path is just supplement information.
User just asks front-end to use "vda1" or "35000cca68ac8e2fd" to create
pool, then we map the names to path.
In this way we don't need to blkid to determine the path, and can drop
the check on" blkid path being not None". Test on Fedora and Ubuntu.
Generally speaking, I also can accept a temp solution just assuming all
NAME listed by lsblk can be mapped to "/dev/NAME". We just need to check
if "/dev/NAME" does exist or not, if not, skip this device.
I'm afraid I have no time to finish a second version of the patch. Maybe
Aline or Daniel can help me. I feel very sorry to not have time to
finish the patch I started. Thanks very much!
--
Thanks and best regards!
Zhou Zheng Sheng / 周征晟
E-mail: zhshzhou(a)linux.vnet.ibm.com
Telephone: 86-10-82454397
--
project-kimchi mailing list <project-kimchi(a)googlegroups.com>
https://groups.google.com/forum/#!forum/project-kimchi
---
You received this message because you are subscribed to the Google Groups "project-kimchi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-kimchi+unsubscribe(a)googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
1
0
18 Dec '13
Make this file PEP 8 clean
Signed-off-by: Zhou Zheng Sheng <zhshzhou(a)linux.vnet.ibm.com>
---
Makefile.am | 1 +
src/kimchi/disks.py | 37 ++++++++++++++++++++-----------------
2 files changed, 21 insertions(+), 17 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index b79919c..e57d3b6 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -40,6 +40,7 @@ EXTRA_DIST = \
PEP8_WHITELIST = \
src/kimchi/asynctask.py \
src/kimchi/config.py.in \
+ src/kimchi/disks.py \
src/kimchi/server.py \
plugins/__init__.py \
plugins/sample/__init__.py \
diff --git a/src/kimchi/disks.py b/src/kimchi/disks.py
index 93d5dbc..874f669 100644
--- a/src/kimchi/disks.py
+++ b/src/kimchi/disks.py
@@ -18,26 +18,26 @@
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-import collections
-import os
import re
import subprocess
-import sys
from kimchi.exception import OperationFailed
+
def get_partitions_paths():
try:
""" Returns all available partitions path of the host """
- blkid = subprocess.Popen(["blkid", "-o", "device" ],
- stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ blkid = subprocess.Popen(
+ ["blkid", "-o", "device"],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
dev_paths = blkid.communicate()[0].rstrip("\n").split("\n")
except:
raise OperationFailed("Unable to retrieve partitions' full path.")
return dev_paths
+
def get_partition_path(name, dev_paths):
""" Returns device path given a partition name """
dev_path = None
@@ -49,11 +49,13 @@ def get_partition_path(name, dev_paths):
if dev_path:
return dev_path
+
def get_partitions_names():
try:
""" Returns all the names of available partitions """
- lsblk = subprocess.Popen(["lsblk", "-Pbo","NAME,TYPE" ],
- stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ lsblk = subprocess.Popen(
+ ["lsblk", "-Pbo", "NAME,TYPE"],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output = lsblk.communicate()[0]
lines_output = output.rstrip("\n").split("\n")
# Will be used later to double check the partition
@@ -67,11 +69,12 @@ def get_partitions_names():
expression = r"%s=\".*?\"" % key
match = re.search(expression, line)
field = match.group()
- k, v = field.split('=',1)
+ k, v = field.split('=', 1)
d[k.lower()] = v[1:-1]
- if d['type'] not in ['part','lvm']:
+ if d['type'] not in ['part', 'lvm']:
continue
- # split()[0] to avoid the second part of the name, after the whiteline
+ # split()[0] to avoid the second part of the name,
+ # after the whiteline
name = d['name'].split()[0]
# There are special cases where lsblk reports
# a partition that does not exist in blkid and fdisk (Extended
@@ -84,18 +87,18 @@ def get_partitions_names():
except:
raise OperationFailed("Unable to retrieve partitions' full path.")
+
def get_partition_details(name):
try:
# Find device path
- dev_path = get_partition_path(name,
- get_partitions_paths())
+ dev_path = get_partition_path(name, get_partitions_paths())
# Couldn't find dev_path.
if not dev_path:
return
# Executing lsblk to get partition/disk info
- lsblk = subprocess.Popen(["lsblk", "-Pbo",
- "TYPE,FSTYPE,SIZE,MOUNTPOINT", dev_path ],
- stdout=subprocess.PIPE,stderr=subprocess.PIPE)
+ lsblk = subprocess.Popen(
+ ["lsblk", "-Pbo", "TYPE,FSTYPE,SIZE,MOUNTPOINT", dev_path],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# single line output
output = lsblk.communicate()[0].rstrip("\n")
# output is on format key="value", where key = NAME, TYPE, FSTYPE,
@@ -106,7 +109,7 @@ def get_partition_details(name):
expression = r"%s=\".*?\"" % key
match = re.search(expression, output)
field = match.group()
- k, v = field.split('=',1)
+ k, v = field.split('=', 1)
d[k.lower()] = v[1:-1]
if d['mountpoint']:
# Sometimes the mountpoint comes with [SWAP] or other
--
1.7.11.7
--
project-kimchi mailing list <project-kimchi(a)googlegroups.com>
https://groups.google.com/forum/#!forum/project-kimchi
---
You received this message because you are subscribed to the Google Groups "project-kimchi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-kimchi+unsubscribe(a)googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
2
4
From: Royce Lv <lvroyce(a)linux.vnet.ibm.com>
v1>v2, move nfs checking to util to avoid misunderstanding.
This patchset solves following problem:
1. libvirt uses local directory when nfs target path is not accessible,
this patch denies this unaccessible path instead.
2. libvirt storage subsystem hang for a long period, with a quick checking,
storage pool operation will not hang if pre-checked.
3. Future access control check will do in prevalidation,
this prevents qemu manipulate storage pool of no read access to make early warning.
Royce Lv (2):
utils: Add nfs prevalication
Integrate nfs path check before create nfs pool
src/kimchi/model.py | 7 ++++++-
src/kimchi/utils.py | 38 ++++++++++++++++++++++++++++++++++++++
2 files changed, 44 insertions(+), 1 deletion(-)
--
1.8.1.2
--
project-kimchi mailing list <project-kimchi(a)googlegroups.com>
https://groups.google.com/forum/#!forum/project-kimchi
---
You received this message because you are subscribed to the Google Groups "project-kimchi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-kimchi+unsubscribe(a)googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
2
4
Re: [Kimchi-devel] [project-kimchi][PATCH 1/3] utils: Add a helper function to parse cmd result
by Royce Lv 18 Dec '13
by Royce Lv 18 Dec '13
18 Dec '13
On 2013年12月17日 20:50, Ramon Medeiros wrote:
> On 12/17/2013 10:10 AM, lvroyce0210(a)gmail.com wrote:
>> From: Royce Lv <lvroyce(a)linux.vnet.ibm.com>
>>
>> Abstract a helper function to parse cmd result.
>> Usage:
>> (1)get cmd result with subprocess.call or subprocess.Popen
>> (2) call pass_cmd_output to get formated outputs
>> Example:
>> blkid = subprocess.Popen(["cat", "/proc/mounts"],
>> stdout=subprocess.PIPE, stderr=subprocess.PIPE)
>> outs = blkid.communicate()[0]
>> output_items= ['path', 'mnt_point', 'type', 'option']
>> utils.pass_cmd_output(outs, output_items)
>> Sample output:
>> [{'path': '/dev/sda8', 'type': 'ext4',
>> 'option': 'rw,relatime,data=ordered', 'mnt_point': '/home'},
>> {'path': 'localhost:/home/royce/isorepo', 'type': 'nfs4',
>> 'option': 'rw...addr=127.0.0.1', 'mnt_point': '/mnt'}]
>>
>> Signed-off-by: Royce Lv <lvroyce(a)linux.vnet.ibm.com>
>> ---
>> src/kimchi/utils.py | 7 +++++++
>> 1 file changed, 7 insertions(+)
>>
>> diff --git a/src/kimchi/utils.py b/src/kimchi/utils.py
>> index f7eda93..1890a28 100644
>> --- a/src/kimchi/utils.py
>> +++ b/src/kimchi/utils.py
>> @@ -84,3 +84,10 @@ def import_class(class_path):
>>
>> def import_module(module_name):
>> return __import__(module_name, fromlist=[''])
>> +
>> +def parse_cmd_output(output, output_items):
>> + res = []
>> + for line in output.split("\n"):
> can you get the Popen result and use readlines, to get a array with
> the lines, instead of striping it?
Hi Ramon,
Thanks for your review, I think for file read result it is OK, but seems
for popen.communicate just pull out the raw output , and only stdout
provide readlines interface, which official doc does not recommend
because of deadlock problem.
(http://docs.python.org/3.3/library/subprocess.html#subprocess.Popen.communi…)
>> + res.append(dict(zip(output_items, line.split())))
>> +
>> + return res
>
>
--
project-kimchi mailing list <project-kimchi(a)googlegroups.com>
https://groups.google.com/forum/#!forum/project-kimchi
---
You received this message because you are subscribed to the Google Groups "project-kimchi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-kimchi+unsubscribe(a)googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
3
4
[project-kimchi] Re: [PATCH] [Issue #287] Confirm box will be hidden by the window dialog
by Aline Manera 18 Dec '13
by Aline Manera 18 Dec '13
18 Dec '13
Applied. Thanks.
Regards,
Aline Manera
--
project-kimchi mailing list <project-kimchi(a)googlegroups.com>
https://groups.google.com/forum/#!forum/project-kimchi
---
You received this message because you are subscribed to the Google Groups "project-kimchi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-kimchi+unsubscribe(a)googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
1
0
[project-kimchi] Re: [PATCH V2] Add confirm box to create logical pool, and modify device path number
by Aline Manera 18 Dec '13
by Aline Manera 18 Dec '13
18 Dec '13
Applied. Thanks.
Regards,
Aline Manera
--
project-kimchi mailing list <project-kimchi(a)googlegroups.com>
https://groups.google.com/forum/#!forum/project-kimchi
---
You received this message because you are subscribed to the Google Groups "project-kimchi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-kimchi+unsubscribe(a)googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
1
0
[project-kimchi] [PATCH] Use spacewalk repo to get python-ethtool on suse.
by shaohef@linux.vnet.ibm.com 18 Dec '13
by shaohef@linux.vnet.ibm.com 18 Dec '13
18 Dec '13
From: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>
Discuss with aline, use spacewalk to get python-ethtool on suse.
Signed-off-by: Aline Manera <alinefm(a)br.ibm.com>
Signed-off-by: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>
---
docs/README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/README.md b/docs/README.md
index cf190ab..e7599ec 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -78,8 +78,8 @@ for more information on how to configure your system to access this repository.
*Note for openSUSE users*: Some of the above packages are located in the openSUSE
Systems Management repository. See
-[this FAQ](http://download.opensuse.org/repositories/systemsmanagement/) to get
-the correct repository based on your openSUSE version. And
+[this FAQ](http://download.opensuse.org/repositories/systemsmanagement:/spacewalk…
+to get the correct repository based on your openSUSE version. And
[this FAQ](http://en.opensuse.org/SDB:Add_package_repositories) for more
information on how configure your system to access this repository.
--
1.7.11.7
--
project-kimchi mailing list <project-kimchi(a)googlegroups.com>
https://groups.google.com/forum/#!forum/project-kimchi
---
You received this message because you are subscribed to the Google Groups "project-kimchi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-kimchi+unsubscribe(a)googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
2
2