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");
56 if (typeof str == "object") str = JSON.stringify(str,null, 2);
57 let $line = $("<text>").text(str);
58 $("#debug").append($line);
69 * Scale an image up or down until it's larger than or equal to the viewport
72 var adjustBackground = function ($img) {
73 var viewportWidth = screen.width;
74 var viewportHeight = screen.height;
75 var viewportAspect = viewportWidth/viewportHeight;
76 var imgWidth = $img.width();
77 var imgHeight = $img.height();
78 var imgAspect = imgWidth/imgHeight;
80 /* First determine what is
81 the limiting factor (ie. if the image is wider, then the height is
82 is the limiting factor and needs to be adjustested */
83 if (imgAspect < viewportAspect) {
84 /* The view port is wider compared to its height than
85 the image is compared to the image height meaning
86 the width is the limiting dimension. Therefore we
87 set image width = view ports width use the aspect
88 ratio to set the correct height */
89 $img.width(viewportWidth);
90 $img.height(viewportWidth/imgAspect);
92 /* The image is wider than it is tall compared to the
93 viewport so we adjust the to fit */
94 $img.height(viewportHeight);
95 $img.width(viewportHeight*imgAspect);
97 this.centerImage($img);
100 var centerImage = function($img) {
101 var overlapWidth = $img.width() - screen.width;
102 var overlapHeight = $img.height() - screen.height;
105 // image overlaps viewport, move the image back
106 // half the length of the overlap
108 position: "relative",
109 right: overlapWidth/2,
110 bottom: overlapHeight/2
116 this.use_splash = true;
117 $(document).ready(() => {
123 this.lightdm = typeof (lightdm) == "undefined" ? {} : lightdm;
125 if (this.use_splash) {
126 this.splash = new SplashScreen();
128 $(this).trigger("ready");
132 auth(username, password, callback) {
133 // lightdm must have each of
134 let req = ["select_user", "is_authenticated", "authenticate"];
135 if (!req.every((x) => this.lightdm.hasOwnProperty(x) )) {
136 log.warn("Cannot attempt login because lightdm is missing the " +
137 "required fields. Please note that lightdm is not explicitly " +
138 "instantiated in a browser session.");
140 // call async so that events can be binded in cascade
141 setTimeout(() => $(this).trigger("deny"));
145 username = username || this.lightdm.select_user;
146 password = password || "";
147 // session_key = session_key || lightdm.sessions[0].key;
149 let auth_cb = () => {
150 this.lightdm.respond(password);
152 let auth_complete_cb = () => {
153 if (typeof callback == "function")
154 callback(this.lightdm.is_authenticated);
156 $(this).trigger(this.lightdm.is_authenticated ? "grant" : "deny");
159 window.show_prompt = auth_cb;
160 window.authentication_complete = auth_complete_cb;
161 this.lightdm.authenticate(username);
165 if (!this.lightdm.sessions.find(x => x.key == session_key)) {
166 log.error("Attempting to login without a valid session.");
170 if (!this.lightdm.is_authenticated) {
171 log.error("Attempting to login without authentication.");
174 this.lightdm.start_session_sync(session_key);
176 authenticateWithSelected($user_input, $password_input) {
180 fillUserSelect($el) {
181 if (!Array.isArray(this.lightdm.users)) {
182 log.warn("Cannot fill empty user list in lightdm.");
187 for (let s of this.lightdm.users)
188 $el.append("<option value=" + s.username + ">" + s.display_name + "</option>");
192 fillSessionSelect($el) {
193 if (!Array.isArray(this.lightdm.sessions)) {
194 log.warn("Cannot fill empty session list in lightdm.");
200 for (let s of this.lightdm.sessions)
201 $el.append("<option value=" + s.key + ">" + s.name + "</option>");
208 return this.lightdm.users;
212 return this.lightdm.sessions;
218 this.$el = $("#splash-screen");
219 this.$content = $("#splash-screen-content");
220 this.options = this.getUserOptions();
221 this.is_open = false;
222 this.last_active = 0;
223 this.active_timeout = 15;
225 if (!this.$el.length)
226 log.error("Missing-screen element.");
228 // fit background image to sreen size and center
229 this.$img = $(".splash-screen-img");
230 if (!this.$img.length)
231 log.warn("No background images supplied for splash screen.");
232 this.$img.each((i, v) => adjustBackground($(v)));
234 let options = this.options; // shorthand
235 if (typeof options == "object") {
236 // initilize global values if specfied in the config
237 this.is_open = false;
240 if (typeof options["active-timeout"] == "number")
241 this.active_timeout = options["active-timeout"];
242 if (options.filter == true)
243 this.$img.addClass("filter");
244 if (options.vignette == true)
245 this.$vignette = $("#vignette");
246 this.$vignette.show();
247 if (typeof options.content == "object")
248 this.initContent(options.content);
251 /******************** Event Listeners ********************/
252 this.clock = setInterval(() => {
253 $(this).trigger("tick");
255 if (!this.isActive())
256 $(this).trigger("inactive");
259 // update last active time
260 $(this).on("active", () => this.last_active = moment());
262 $(document).keyup((e) => {
263 // handle events in seperate method
264 this.keyHandler.call(this, e);
265 }).keypress((e) => this.keyHandler.call(this, e));
267 this.$el.click(() => {
269 }).mousemove((e) => {
270 if (!this.isActive())
271 $(this).trigger("active", e)
273 setTimeout(() => $(this).trigger("active"), 1);
277 * Loops through the user specified content and adds them to the DOM in order
279 initContent(content) {
280 for (let content_type in content) {
281 if (content_type == "clock")
282 this.initClock(content[content_type]);
283 else if (content_type == "html")
284 this.initHTML(content[content_type]);
286 log.warn("Specified content " + content_type + " is not valid.");
292 $.extend(true, options, DEF_OPT);
293 $.extend(true, options, {});
298 * open and close will toggle the screen and animate it opening and closing
299 * adds a resetTimeout function to automatically close after a period of user
306 }, time, "easeInCubic", () => {
308 clearTimeout(this.resetTimeout);
312 clearTimeout(this.resetTimeout);
313 let reset_duration = 60*1000;
317 this.resetTimeout = setTimeout(this.reset, reset_duration);
322 }, time, "easeInCubic", () => {
324 // close the screen after 1 minute of inactivty
325 this.resetTimeout = setTimeout(() => this.reset, reset_duration);
329 if (this.is_open == true) {
332 $(this).trigger("timeout");
337 * handles the key events for the splash
346 if (this.is_open) this.close();
350 if (e.keyCode != 82 && e.keyCode != 17) // for testing
355 // stop reset timeout since there has been user activity
357 clearTimeout(this.resetTimeout);
359 if (!this.isActive())
360 $(this).trigger("active", e);
364 if (moment().diff(this.last_active, "seconds", true) > 30) {
371 * Creates clock elements based on the usr config
374 if (typeof opts != "object") {
375 log.error("Unable to initialize clock thats not an object");
378 // handle arrays and a single clock object
379 if (!Array.isArray(opts))
382 for (let i in opts) {
383 this.$clock = $("<div id='clock-" + i + "' class='clock'></div>");
384 this.$content.append(this.$clock);
385 this.startClock(this.$clock, opts[i]);
390 * Applys the css specfied in the argument opts to the jQuery oboject $clock.
391 * Subscribes the clock to a tick event
393 startClock($clock, opts) {
394 if (typeof opts != "object") {
395 log.error("Clock opts is not a valid object");
398 // handle multiple formats for multiple clocks on the same line
399 if(typeof opts.format == "string")
400 opts.format = [opts.format];
402 // ensure the format is now an array
403 if(!Array.isArray(opts.format)) {
404 log.error(`Specfied clock format is not a valid type.
405 Type can be a single string or Array.`);
409 if(!Array.isArray(opts.css))
410 opts.css = [opts.css];
412 for (let i in opts.format) {
414 let $format = $("<sub></sub>");
415 // create text field in clock
416 $clock.append($format);
418 if (i < opts.css.length && typeof opts.css[i] == "object")
419 $format.css(opts.css[i]);
422 $format.text(moment().format(opts.format[i]));
423 $(this).on("tick", () => {
424 $format.text(moment().format(opts.format[i]));
428 if (typeof opts["parent-css"] == "object")
429 $clock.css(opts["parent-css"]);
435 * Inserts HTML specified in the user config into the splash screen
436 * accepts plain strings and objects. String literals are interpreted as
437 * normal text element. Objects are set using the jQuery API
440 // handle single objects and strings
441 if (!Array.isArray(opts)) {
445 for (let el of opts) {
446 if (typeof el == "string") {
447 let $el = $("<text>");
449 // create simple text element
450 this.$content.append($el);
451 } else if (typeof el == "object") {
452 // let user specify element properites in object el.
453 let $el = $("<div>");
454 for (let prop in el) {
457 this.$content.append($el);
460 log.warn("Splash screen html element is invalid type");