10 "format": "dddd, MMMM Do",
15 "margin-top": "calc(20vh - 70pt)",
16 "text-align": "center",
18 "font-family": "Noto Sans",
19 "font-weight": "lighter",
20 "text-shadow": "rgba(0, 0, 0, 0.8) 0px 7px 10px",
23 "format": ["h:mm", "A"],
25 {"font-size": "65pt", "font-weight": 200 },
26 {"font-size": "30pt", "font-weight": "lighter", "margin-left": "10pt"}
31 "font-family": "Noto Sans",
32 "text-align": "center",
33 "text-shadow": "rgba(0, 0, 0, 0.8) 0px 7px 10px",
38 "html":"<text style='display: none' class='active-appear'>Press any key to login</text>",
44 "text-align": "center",
45 "color": "rgba(255, 255, 255, 0.8)"
50 window.onerror = function() {
51 log.error("Error caught");
55 $(document).ready(()=> {
56 $log = $("#console .terminal");
57 $autoscroll = $("#console input");
61 if (typeof str == "object") str = JSON.stringify(str,null, 2);
62 str = "[" + moment().format("hh:mm:ss") + "]: " + str;
63 str = "<text style='color: " + color + "'>" + str + "</text>";
64 return $log.html() + str;
67 $log.html(this._parse(str,"white"));
68 if ($autoscroll.prop('checked'))
69 $log[0].scrollTop = $log[0].scrollHeight;
72 $log.html(this._parse(str,"red"));
73 if ($autoscroll.prop('checked'))
74 $log[0].scrollTop = $log[0].scrollHeight;
77 $log.html(this._parse(str,"yellow"));
78 if ($autoscroll.prop('checked'))
79 $log[0].scrollTop = $log[0].scrollHeight;
82 $log.html(this._parse(str,"lightblue"));
83 if ($autoscroll.prop('checked'))
84 $log[0].scrollTop = $log[0].scrollHeight;
89 * Scale an image up or down until it's larger than or equal to the viewport
92 var adjustBackground = function ($img) {
93 var viewportWidth = screen.width;
94 var viewportHeight = screen.height;
95 var viewportAspect = viewportWidth/viewportHeight;
96 var imgWidth = $img.width();
97 var imgHeight = $img.height();
98 var imgAspect = imgWidth/imgHeight;
100 /* First determine what is
101 the limiting factor (ie. if the image is wider, then the height is
102 is the limiting factor and needs to be adjustested */
103 if (imgAspect < viewportAspect) {
104 /* The view port is wider compared to its height than
105 the image is compared to the image height meaning
106 the width is the limiting dimension. Therefore we
107 set image width = view ports width use the aspect
108 ratio to set the correct height */
109 $img.width(viewportWidth);
110 $img.height(viewportWidth/imgAspect);
112 /* The image is wider than it is tall compared to the
113 viewport so we adjust the to fit */
114 $img.height(viewportHeight);
115 $img.width(viewportHeight*imgAspect);
117 this.centerImage($img);
120 var centerImage = function($img) {
121 var overlapWidth = $img.width() - screen.width;
122 var overlapHeight = $img.height() - screen.height;
125 // image overlaps viewport, move the image back
126 // half the length of the overlap
128 position: "relative",
129 right: overlapWidth/2,
130 bottom: overlapHeight/2
136 this.use_splash = true;
137 $(document).ready(() => {
143 this.lightdm = typeof (lightdm) == "undefined" ? {} : lightdm;
145 if (this.use_splash) {
146 this.splash = new SplashScreen();
148 $(this).trigger("ready");
152 auth(username, password, callback) {
153 // lightdm must have each of
154 let req = ["select_user", "is_authenticated", "authenticate"];
155 if (!req.every((x) => this.lightdm.hasOwnProperty(x) )) {
156 log.warn("Cannot attempt login because lightdm is missing the " +
157 "required fields. Please note that lightdm is not explicitly " +
158 "instantiated in a browser session.");
160 // call async so that events can be binded in cascade
161 setTimeout(() => $(this).trigger("deny"));
165 username = username || this.lightdm.select_user;
166 password = password || "";
167 // session_key = session_key || lightdm.sessions[0].key;
169 let auth_cb = () => {
170 this.lightdm.respond(password);
172 let auth_complete_cb = () => {
173 if (typeof callback == "function")
174 callback(this.lightdm.is_authenticated);
176 $(this).trigger(this.lightdm.is_authenticated ? "grant" : "deny");
179 window.show_prompt = auth_cb;
180 window.authentication_complete = auth_complete_cb;
181 this.lightdm.authenticate(username);
185 if (!this.lightdm.sessions.find(x => x.key == session_key)) {
186 log.error("Attempting to login without a valid session.");
190 if (!this.lightdm.is_authenticated) {
191 log.error("Attempting to login without authentication.");
194 this.lightdm.start_session_sync(session_key);
196 authenticateWithSelected($user_input, $password_input) {
200 fillUserSelect($el) {
201 if (!Array.isArray(this.lightdm.users)) {
202 log.warn("Cannot fill empty user list in lightdm.");
207 for (let s of this.lightdm.users)
208 $el.append("<option value=" + s.username + ">" + s.display_name + "</option>");
212 fillSessionSelect($el) {
213 if (!Array.isArray(this.lightdm.sessions)) {
214 log.warn("Cannot fill empty session list in lightdm.");
220 for (let s of this.lightdm.sessions)
221 $el.append("<option value=" + s.key + ">" + s.name + "</option>");
228 return this.lightdm.users;
232 return this.lightdm.sessions;
238 this.$el = $("#splash-screen");
239 this.$content = $("#splash-screen-content");
240 this.options = this.getUserOptions();
241 this.is_open = false;
242 this.last_active = 0;
243 this.active_timeout = 15;
245 if (!this.$el.length)
246 log.error("Missing-screen element.");
248 // fit background image to sreen size and center
249 this.$img = $(".splash-screen-img");
250 if (!this.$img.length)
251 log.warn("No background images supplied for splash screen.");
252 this.$img.each((i, v) => adjustBackground($(v)));
254 let options = this.options; // shorthand
255 if (typeof options == "object") {
256 // initilize global values if specfied in the config
257 this.is_open = false;
260 if (typeof options["active-timeout"] == "number")
261 this.active_timeout = options["active-timeout"];
262 if (options.filter == true)
263 this.$img.addClass("filter");
264 if (options.vignette == true)
265 this.$vignette = $("#vignette");
266 this.$vignette.show();
267 if (typeof options.content == "object")
268 this.initContent(options.content);
271 /******************** Event Listeners ********************/
272 this.clock = setInterval(() => {
273 $(this).trigger("tick");
275 if (!this.isActive())
276 $(this).trigger("inactive");
279 // update last active time
280 $(this).on("active", () => this.last_active = moment());
282 $(document).keyup((e) => {
283 // handle events in seperate method
284 this.keyHandler.call(this, e);
285 }).keypress((e) => this.keyHandler.call(this, e));
287 this.$el.click(() => {
289 }).mousemove((e) => {
290 if (!this.isActive())
291 $(this).trigger("active", e)
293 setTimeout(() => $(this).trigger("active"), 1);
297 * Loops through the user specified content and adds them to the DOM in order
299 initContent(content) {
300 for (let content_type in content) {
301 if (content_type == "clock")
302 this.initClock(content[content_type]);
303 else if (content_type == "html")
304 this.initHTML(content[content_type]);
306 log.warn("Specified content " + content_type + " is not valid.");
312 $.extend(true, options, DEF_OPT);
313 $.extend(true, options, {});
318 * open and close will toggle the screen and animate it opening and closing
319 * adds a resetTimeout function to automatically close after a period of user
326 }, time, "easeInCubic", () => {
328 clearTimeout(this.resetTimeout);
332 clearTimeout(this.resetTimeout);
333 let reset_duration = 60*1000;
337 this.resetTimeout = setTimeout(this.reset, reset_duration);
342 }, time, "easeInCubic", () => {
344 // close the screen after 1 minute of inactivty
345 this.resetTimeout = setTimeout(() => this.reset, reset_duration);
349 if (this.is_open == true) {
352 $(this).trigger("timeout");
357 * handles the key events for the splash
366 if (this.is_open) this.close();
370 if (e.keyCode != 82 && e.keyCode != 17) // for testing
375 // stop reset timeout since there has been user activity
377 clearTimeout(this.resetTimeout);
379 if (!this.isActive())
380 $(this).trigger("active", e);
384 if (moment().diff(this.last_active, "seconds", true) > 30) {
391 * Creates clock elements based on the usr config
394 if (typeof opts != "object") {
395 log.error("Unable to initialize clock thats not an object");
398 // handle arrays and a single clock object
399 if (!Array.isArray(opts))
402 for (let i in opts) {
403 this.$clock = $("<div id='clock-" + i + "' class='clock'></div>");
404 this.$content.append(this.$clock);
405 this.startClock(this.$clock, opts[i]);
410 * Applys the css specfied in the argument opts to the jQuery oboject $clock.
411 * Subscribes the clock to a tick event
413 startClock($clock, opts) {
414 if (typeof opts != "object") {
415 log.error("Clock opts is not a valid object");
418 // handle multiple formats for multiple clocks on the same line
419 if(typeof opts.format == "string")
420 opts.format = [opts.format];
422 // ensure the format is now an array
423 if(!Array.isArray(opts.format)) {
424 log.error(`Specfied clock format is not a valid type.
425 Type can be a single string or Array.`);
429 if(!Array.isArray(opts.css))
430 opts.css = [opts.css];
432 for (let i in opts.format) {
434 let $format = $("<sub></sub>");
435 // create text field in clock
436 $clock.append($format);
438 if (i < opts.css.length && typeof opts.css[i] == "object")
439 $format.css(opts.css[i]);
442 $format.text(moment().format(opts.format[i]));
443 $(this).on("tick", () => {
444 $format.text(moment().format(opts.format[i]));
448 if (typeof opts["parent-css"] == "object")
449 $clock.css(opts["parent-css"]);
455 * Inserts HTML specified in the user config into the splash screen
456 * accepts plain strings and objects. String literals are interpreted as
457 * normal text element. Objects are set using the jQuery API
460 // handle single objects and strings
461 if (!Array.isArray(opts)) {
465 for (let el of opts) {
466 if (typeof el == "string") {
467 let $el = $("<text>");
469 // create simple text element
470 this.$content.append($el);
471 } else if (typeof el == "object") {
472 // let user specify element properites in object el.
473 let $el = $("<div>");
474 for (let prop in el) {
477 this.$content.append($el);
480 log.warn("Splash screen html element is invalid type");