Commit f9f842f0 authored by liuzheng712's avatar liuzheng712

feat: some assets

parent 8b367ca2
/*
* Copyright (c) 2015 Sylvain Peyrefitte
*
* This file is part of mstsc.js.
*
* mstsc.js is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
/**
* decompress bitmap from RLE algorithm
* @param bitmap {object} bitmap object of bitmap event of node-rdpjs
*/
function decompress (bitmap) {
var fName = null;
switch (bitmap.bitsPerPixel) {
case 15:
fName = 'bitmap_decompress_15';
break;
case 16:
fName = 'bitmap_decompress_16';
break;
case 24:
fName = 'bitmap_decompress_24';
break;
case 32:
fName = 'bitmap_decompress_32';
break;
default:
throw 'invalid bitmap data format';
}
var input = new Uint8Array(bitmap.data);
var inputPtr = Module._malloc(input.length);
var inputHeap = new Uint8Array(Module.HEAPU8.buffer, inputPtr, input.length);
inputHeap.set(input);
var output_width = bitmap.destRight - bitmap.destLeft + 1;
var output_height = bitmap.destBottom - bitmap.destTop + 1;
var ouputSize = output_width * output_height * 4;
var outputPtr = Module._malloc(ouputSize);
var outputHeap = new Uint8Array(Module.HEAPU8.buffer, outputPtr, ouputSize);
var res = Module.ccall(fName,
'number',
['number', 'number', 'number', 'number', 'number', 'number', 'number', 'number'],
[outputHeap.byteOffset, output_width, output_height, bitmap.width, bitmap.height, inputHeap.byteOffset, input.length]
);
var output = new Uint8ClampedArray(outputHeap.buffer, outputHeap.byteOffset, ouputSize);
Module._free(inputPtr);
Module._free(outputPtr);
return { width : output_width, height : output_height, data : output };
}
/**
* Un compress bitmap are reverse in y axis
*/
function reverse (bitmap) {
return { width : bitmap.width, height : bitmap.height, data : new Uint8ClampedArray(bitmap.data) };
}
/**
* Canvas renderer
* @param canvas {canvas} use for rendering
*/
function Canvas(canvas) {
this.canvas = canvas;
this.ctx = canvas.getContext("2d");
}
Canvas.prototype = {
/**
* update canvas with new bitmap
* @param bitmap {object}
*/
update : function (bitmap) {
var output = null;
if (bitmap.isCompress) {
output = decompress(bitmap);
}
else {
output = reverse(bitmap);
}
// use image data to use asm.js
var imageData = this.ctx.createImageData(output.width, output.height);
imageData.data.set(output.data);
this.ctx.putImageData(imageData, bitmap.destLeft, bitmap.destTop);
}
};
/**
* Module export
*/
Mstsc.Canvas = {
create : function (canvas) {
return new Canvas(canvas);
}
}
})();
/*
* Copyright (c) 2015 Sylvain Peyrefitte
*
* This file is part of mstsc.js.
*
* mstsc.js is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function () {
/**
* Mouse button mapping
* @param button {integer} client button number
*/
function mouseButtonMap(button) {
switch (button) {
case 0:
return 1;
case 2:
return 2;
default:
return 0;
}
}
/**
* Mstsc client
* Input client connection (mouse and keyboard)
* bitmap processing
* @param canvas {canvas} rendering element
*/
function Client(canvas) {
this.canvas = canvas;
// create renderer
this.render = new Mstsc.Canvas.create(this.canvas);
this.socket = null;
this.activeSession = false;
this.install();
}
Client.prototype = {
install: function () {
var self = this;
// bind mouse move event
this.canvas.addEventListener('mousemove', function (e) {
if (!self.socket) return;
var offset = Mstsc.elementOffset(self.canvas);
self.socket.emit('mouse', e.clientX - offset.left, e.clientY - offset.top, 0, false);
e.preventDefault || !self.activeSession();
return false;
});
this.canvas.addEventListener('mousedown', function (e) {
if (!self.socket) return;
var offset = Mstsc.elementOffset(self.canvas);
self.socket.emit('mouse', e.clientX - offset.left, e.clientY - offset.top, mouseButtonMap(e.button), true);
e.preventDefault();
return false;
});
this.canvas.addEventListener('mouseup', function (e) {
if (!self.socket || !self.activeSession) return;
var offset = Mstsc.elementOffset(self.canvas);
self.socket.emit('mouse', e.clientX - offset.left, e.clientY - offset.top, mouseButtonMap(e.button), false);
e.preventDefault();
return false;
});
this.canvas.addEventListener('contextmenu', function (e) {
if (!self.socket || !self.activeSession) return;
var offset = Mstsc.elementOffset(self.canvas);
self.socket.emit('mouse', e.clientX - offset.left, e.clientY - offset.top, mouseButtonMap(e.button), false);
e.preventDefault();
return false;
});
this.canvas.addEventListener('DOMMouseScroll', function (e) {
if (!self.socket || !self.activeSession) return;
var isHorizontal = false;
var delta = e.detail;
var step = Math.round(Math.abs(delta) * 15 / 8);
var offset = Mstsc.elementOffset(self.canvas);
self.socket.emit('wheel', e.clientX - offset.left, e.clientY - offset.top, step, delta > 0, isHorizontal);
e.preventDefault();
return false;
});
this.canvas.addEventListener('mousewheel', function (e) {
if (!self.socket || !self.activeSession) return;
var isHorizontal = Math.abs(e.deltaX) > Math.abs(e.deltaY);
var delta = isHorizontal ? e.deltaX : e.deltaY;
var step = Math.round(Math.abs(delta) * 15 / 8);
var offset = Mstsc.elementOffset(self.canvas);
self.socket.emit('wheel', e.clientX - offset.left, e.clientY - offset.top, step, delta > 0, isHorizontal);
e.preventDefault();
return false;
});
// bind keyboard event
window.addEventListener('keydown', function (e) {
if (!self.socket || !self.activeSession) return;
self.socket.emit('scancode', Mstsc.scancode(e), true);
e.preventDefault();
return false;
});
window.addEventListener('keyup', function (e) {
if (!self.socket || !self.activeSession) return;
self.socket.emit('scancode', Mstsc.scancode(e), false);
e.preventDefault();
return false;
});
return this;
},
/**
* connect
* @param ip {string} ip target for rdp
* @param domain {string} microsoft domain
* @param username {string} session username
* @param password {string} session password
* @param next {function} asynchrone end callback
*/
connect: function (token, socket) {
// compute socket.io path (cozy cloud integration)
var parts = document.location.pathname.split('/')
, base = parts.slice(0, parts.length - 1).join('/') + '/'
, path = base + socket;
// start connection
var self = this;
this.socket = io(window.location.protocol + "//" + window.location.host, {"path": path}).on('rdp-connect', function () {
// this event can be occured twice (RDP protocol stack artefact)
console.log('[mstsc.js] connected');
self.activeSession = true;
}).on('rdp-bitmap', function (bitmap) {
console.log('[mstsc.js] bitmap update bpp : ' + bitmap.bitsPerPixel);
self.render.update(bitmap);
}).on('rdp-close', function () {
// next(null);
console.log('[mstsc.js] close');
self.activeSession = false;
}).on('rdp-error', function (err) {
// next(err);
alert(err.code);
console.log('[mstsc.js] error : ' + err.code + '(' + err.message + ')');
self.activeSession = false;
});
if (token != null) {
// emit infos event
this.socket.emit('infos', {
token: token,
screen: {
width: this.canvas.width,
height: this.canvas.height
},
locale: Mstsc.locale()
});
} else {
alert("No privilege!!!")
}
}
};
Mstsc.client = {
create: function (canvas) {
return new Client(canvas);
}
}
})();
/*
* Copyright (c) 2015 Sylvain Peyrefitte
*
* This file is part of mstsc.js.
*
* mstsc.js is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code
var KeyMap = {
"" : 0x0000,
"Escape" : 0x0001,
"Digit1" : 0x0002,
"Digit2" : 0x0003,
"Digit3" : 0x0004,
"Digit4" : 0x0005,
"Digit5" : 0x0006,
"Digit6" : 0x0007,
"Digit7" : 0x0008,
"Digit8" : 0x0009,
"Digit9" : 0x000A,
"Digit0" : 0x000B,
"Minus" : 0x000C,
"Equal" : 0x000D,
"Backspace" : 0x000E,
"Tab" : 0x000F,
"KeyQ" : 0x0010,
"KeyW" : 0x0011,
"KeyE" : 0x0012,
"KeyR" : 0x0013,
"KeyT" : 0x0014,
"KeyY" : 0x0015,
"KeyU" : 0x0016,
"KeyI" : 0x0017,
"KeyO" : 0x0018,
"KeyP" : 0x0019,
"BracketLeft" : 0x001A,
"BracketRight" : 0x001B,
"Enter" : 0x001C,
"ControlLeft" : 0x001D,
"KeyA" : 0x001E,
"KeyS" : 0x001F,
"KeyD" : 0x0020,
"KeyF" : 0x0021,
"KeyG" : 0x0022,
"KeyH" : 0x0023,
"KeyJ" : 0x0024,
"KeyK" : 0x0025,
"KeyL" : 0x0026,
"Semicolon" : 0x0027,
"Quote" : 0x0028,
"Backquote" : 0x0029,
"ShiftLeft" : 0x002A,
"Backslash" : 0x002B,
"KeyZ" : 0x002C,
"KeyX" : 0x002D,
"KeyC" : 0x002E,
"KeyV" : 0x002F,
"KeyB" : 0x0030,
"KeyN" : 0x0031,
"KeyM" : 0x0032,
"Comma" : 0x0033,
"Period" : 0x0034,
"Slash" : 0x0035,
"ShiftRight" : 0x0036,
"NumpadMultiply" : 0x0037,
"AltLeft" : 0x0038,
"Space" : 0x0039,
"CapsLock" : 0x003A,
"F1" : 0x003B,
"F2" : 0x003C,
"F3" : 0x003D,
"F4" : 0x003E,
"F5" : 0x003F,
"F6" : 0x0040,
"F7" : 0x0041,
"F8" : 0x0042,
"F9" : 0x0043,
"F10" : 0x0044,
"Pause" : 0x0045,
"ScrollLock" : 0x0046,
"Numpad7" : 0x0047,
"Numpad8" : 0x0048,
"Numpad9" : 0x0049,
"NumpadSubtract" : 0x004A,
"Numpad4" : 0x004B,
"Numpad5" : 0x004C,
"Numpad6" : 0x004D,
"NumpadAdd" : 0x004E,
"Numpad1" : 0x004F,
"Numpad2" : 0x0050,
"Numpad3" : 0x0051,
"Numpad0" : 0x0052,
"NumpadDecimal" : 0x0053,
"PrintScreen" : 0x0054,
"IntlBackslash" : 0x0056,
"F11" : 0x0057,
"F12" : 0x0058,
"NumpadEqual" : 0x0059,
"F13" : 0x0064,
"F14" : 0x0065,
"F15" : 0x0066,
"F16" : 0x0067,
"F17" : 0x0068,
"F18" : 0x0069,
"F19" : 0x006A,
"F20" : 0x006B,
"F21" : 0x006C,
"F22" : 0x006D,
"F23" : 0x006E,
"KanaMode" : 0x0070,
"Lang2" : 0x0071,
"Lang1" : 0x0072,
"IntlRo" : 0x0073,
"F24" : 0x0076,
"Convert" : 0x0079,
"NonConvert" : 0x007B,
"IntlYen" : 0x007D,
"NumpadComma" : 0x007E,
"MediaTrackPrevious" : 0xE010,
"MediaTrackNext" : 0xE019,
"NumpadEnter" : 0xE01C,
"ControlRight" : 0xE01D,
"VolumeMute" : 0xE020,
"LaunchApp2" : 0xE021,
"MediaPlayPause" : 0xE022,
"MediaStop" : 0xE024,
"VolumeDown" : 0xE02E,
"VolumeUp" : 0xE030,
"BrowserHome" : 0xE032,
"NumpadDivide" : 0xE035,
"PrintScreen" : 0xE037,
"AltRight" : 0xE038,
"NumLock" : 0xE045,
"Pause" : 0xE046,
"Home" : 0xE047,
"ArrowUp" : 0xE048,
"PageUp" : 0xE049,
"ArrowLeft" : 0xE04B,
"ArrowRight" : 0xE04D,
"End" : 0xE04F,
"ArrowDown" : 0xE050,
"PageDown" : 0xE051,
"Insert" : 0xE052,
"Delete" : 0xE053,
"OSLeft" : 0xE05B,
"OSRight" : 0xE05C,
"ContextMenu" : 0xE05D,
"Power" : 0xE05E,
"BrowserSearch" : 0xE065,
"BrowserFavorites" : 0xE066,
"BrowserRefresh" : 0xE067,
"BrowserStop" : 0xE068,
"BrowserForward" : 0xE069,
"BrowserBack" : 0xE06A,
"LaunchApp1" : 0xE06B,
"LaunchMail" : 0xE06C,
"MediaSelect" : 0xE06D
};
var UnicodeToCodeFirefox_FR = {
27 : "Escape",
112 : "F1",
113 : "F2",
114 : "F3",
115 : "F4",
116 : "F5",
117 : "F6",
118 : "F7",
119 : "F8",
120 : "F9",
121 : "F10",
122 : "F11",
123 : "F12",
0 : "Backquote",
49 : "Digit1",
50 : "Digit2",
51 : "Digit3",
52 : "Digit4",
53 : "Digit5",
54 : "Digit6",
55 : "Digit7",
56 : "Digit8",
57 : "Digit9",
48 : "Digit0",
169 : "Minus",
61 : "Equal",
8 : "Backspace",
9 : "Tab",
65 : "KeyQ",
90 : "KeyW",
69 : "KeyE",
82 : "KeyR",
84 : "KeyT",
89 : "KeyY",
85 : "KeyU",
73 : "KeyI",
79 : "KeyO",
80 : "KeyP",
160 : "BracketLeft",
164 : "BracketRight",
13 : "Enter",
20 : "CapsLock",
20 : "CapsLock",
81 : "KeyA",
83 : "KeyS",
68 : "KeyD",
70 : "KeyF",
71 : "KeyG",
72 : "KeyH",
74 : "KeyJ",
75 : "KeyK",
76 : "KeyL",
77 : "Semicolon",
165 : "Quote",
170 : "Backslash",
16 : "ShiftLeft",
60 : "IntlBackslash",
87 : "KeyZ",
88 : "KeyX",
67 : "KeyC",
86 : "KeyV",
66 : "KeyB",
78 : "KeyN",
188 : "KeyM",
59 : "Comma",
58 : "Period",
161 : "Slash",
16 : "ShiftRight",
17 : "ControlLeft",
91 : "OSLeft",
18 : "AltLeft",
32 : "Space",
17 : "ControlLeft",
18 : "AltRight",
91 : "OSRight",
93 : "ContextMenu",
17 : "ControlRight",
37 : "ArrowLeft",
38 : "ArrowUp",
40 : "ArrowDown",
39 : "ArrowRight",
144 : "NumLock",
144 : "NumLock",
111 : "NumpadDivide",
106 : "NumpadMultiply",
109 : "NumpadSubtract",
103 : "Numpad7",
104 : "Numpad8",
105 : "Numpad9",
107 : "NumpadAdd",
100 : "Numpad4",
101 : "Numpad5",
102 : "Numpad6",
97 : "Numpad1",
98 : "Numpad2",
99 : "Numpad3",
96 : "Numpad0",
110 : "NumpadDecimal",
13 : "NumpadEnter",
17 : "ControlLeft",
67 : "KeyC",
17 : "ControlLeft"
};
var UnicodeToCodeChrome_FR = {
27 : "Escape",
112 : "F1",
113 : "F2",
114 : "F3",
115 : "F4",
116 : "F5",
117 : "F6",
118 : "F7",
119 : "F8",
120 : "F9",
121 : "F10",
122 : "F11",
123 : "F12",
0 : "Backquote",
49 : "Digit1",
50 : "Digit2",
51 : "Digit3",
52 : "Digit4",
53 : "Digit5",
54 : "Digit6",
55 : "Digit7",
56 : "Digit8",
57 : "Digit9",
48 : "Digit0",
219 : "Minus",
187 : "Equal",
8 : "Backspace",
9 : "Tab",
65 : "KeyQ",
90 : "KeyW",
69 : "KeyE",
82 : "KeyR",
84 : "KeyT",
89 : "KeyY",
85 : "KeyU",
73 : "KeyI",
79 : "KeyO",
80 : "KeyP",
221 : "BracketLeft",
186 : "BracketRight",
13 : "Enter",
20 : "CapsLock",
20 : "CapsLock",
81 : "KeyA",
83 : "KeyS",
68 : "KeyD",
70 : "KeyF",
71 : "KeyG",
72 : "KeyH",
74 : "KeyJ",
75 : "KeyK",
76 : "KeyL",
77 : "Semicolon",
192 : "Quote",
220 : "Backslash",
16 : "ShiftLeft",
60 : "IntlBackslash",
87 : "KeyZ",
88 : "KeyX",
67 : "KeyC",
86 : "KeyV",
66 : "KeyB",
78 : "KeyN",
188 : "KeyM",
190 : "Comma",
191 : "Period",
223 : "Slash",
16 : "ShiftRight",
17 : "ControlLeft",
91 : "OSLeft",
18 : "AltLeft",
32 : "Space",
17 : "ControlLeft",
18 : "AltRight",
91 : "OSRight",
93 : "ContextMenu",
17 : "ControlRight",
37 : "ArrowLeft",
38 : "ArrowUp",
40 : "ArrowDown",
39 : "ArrowRight",
144 : "NumLock",
144 : "NumLock",
111 : "NumpadDivide",
106 : "NumpadMultiply",
109 : "NumpadSubtract",
103 : "Numpad7",
104 : "Numpad8",
105 : "Numpad9",
107 : "NumpadAdd",
100 : "Numpad4",
101 : "Numpad5",
102 : "Numpad6",
97 : "Numpad1",
98 : "Numpad2",
99 : "Numpad3",
96 : "Numpad0",
110 : "NumpadDecimal",
13 : "NumpadEnter",
17 : "ControlLeft",
67 : "KeyC",
17 : "ControlLeft"
};
var UnicodeToCode_EN = {
27 : "Escape",
112 : "F1",
113 : "F2",
114 : "F3",
115 : "F4",
116 : "F5",
117 : "F6",
118 : "F7",
119 : "F8",
120 : "F9",
121 : "F10",
122 : "F11",
123 : "F12",
192 : "Backquote",
49 : "Digit1",
50 : "Digit2",
51 : "Digit3",
52 : "Digit4",
53 : "Digit5",
54 : "Digit6",
55 : "Digit7",
56 : "Digit8",
57 : "Digit9",
48 : "Digit0",
173 : "Minus",
61 : "Equal",
8 : "Backspace",
9 : "Tab",
81 : "KeyQ",
87 : "KeyW",
69 : "KeyE",
82 : "KeyR",
84 : "KeyT",
89 : "KeyY",
85 : "KeyU",
73 : "KeyI",
79 : "KeyO",
80 : "KeyP",
219 : "BracketLeft",
221 : "BracketRight",
13 : "Enter",
20 : "CapsLock",
65 : "KeyA",
83 : "KeyS",
68 : "KeyD",
70 : "KeyF",
71 : "KeyG",
72 : "KeyH",
74 : "KeyJ",
75 : "KeyK",
76 : "KeyL",
59 : "Semicolon",
222 : "Quote",
220 : "Backslash",
16 : "ShiftLeft",
220 : "IntlBackslash",
90 : "KeyZ",
88 : "KeyX",
67 : "KeyC",
86 : "KeyV",
66 : "KeyB",
78 : "KeyN",
77 : "KeyM",
188 : "Comma",
190 : "Period",
191 : "Slash",
16 : "ShiftRight",
17 : "ControlLeft",
18 : "AltLeft",
91 : "OSLeft",
32 : "Space",
18 : "AltRight",
91 : "OSRight",
93 : "ContextMenu",
17 : "ControlRight",
37 : "ArrowLeft",
38 : "ArrowUp",
40 : "ArrowDown",
39 : "ArrowRight",
144 : "NumLock",
144 : "NumLock",
111 : "NumpadDivide",
106 : "NumpadMultiply",
109 : "NumpadSubtract",
103 : "Numpad7",
104 : "Numpad8",
105 : "Numpad9",
107 : "NumpadAdd",
100 : "Numpad4",
101 : "Numpad5",
102 : "Numpad6",
97 : "Numpad1",
98 : "Numpad2",
99 : "Numpad3",
13 : "NumpadEnter",
96 : "Numpad0",
110 : "NumpadDecimal",
17 : "ControlLeft"
};
var UnicodeToCode = {
'firefox' : {
'fr' : UnicodeToCodeFirefox_FR,
'en' : UnicodeToCode_EN
},
'chrome' : {
'fr' : UnicodeToCodeChrome_FR,
'en' : UnicodeToCode_EN
}
}
/**
* Scancode of keyevent
* @param e {keyboardevent}
* @return {integer} scancode
*/
function scancode (e) {
var locale = Mstsc.locale();
locale = (['fr', 'en'].indexOf(locale) > 0 && locale) || 'en';
return KeyMap[e.code || UnicodeToCode[Mstsc.browser() || 'firefox'][locale][e.keyCode]];
}
Mstsc.scancode = scancode;
})();
\ No newline at end of file
/*
* Copyright (c) 2015 Sylvain Peyrefitte
*
* This file is part of mstsc.js.
*
* mstsc.j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
/**
* Use for domain declaration
*/
Mstsc = function () {
};
Mstsc.prototype = {
// shortcut
$ : function (id) {
return document.getElementById(id);
},
/**
* Compute screen offset for a target element
* @param el {DOM element}
* @return {top : {integer}, left {integer}}
*/
elementOffset : function (el) {
var x = 0;
var y = 0;
while (el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop )) {
x += el.offsetLeft - el.scrollLeft;
y += el.offsetTop - el.scrollTop;
el = el.offsetParent;
}
return { top: y, left: x };
},
/**
* Try to detect browser
* @returns {String} [firefox|chrome|ie]
*/
browser : function () {
if (typeof InstallTrigger !== 'undefined') {
return 'firefox';
}
if (!!window.chrome) {
return 'chrome';
}
if (!!document.docuemntMode) {
return 'ie';
}
return null;
},
/**
* Try to detect language
* @returns
*/
locale : function () {
return window.navigator.userLanguage || window.navigator.language;
}
}
})();
this.Mstsc = new Mstsc();
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment