├── test.sh ├── img └── bg.jpg ├── screenshots └── screenshot_1.gif ├── LICENSE ├── index.html ├── PKGBUILD ├── css ├── default.css └── normalize.css ├── README.MD └── js ├── mock.js ├── main.js └── commands.js /test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | dm-tool add-nested-seat --screen 1366x768 -------------------------------------------------------------------------------- /img/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eNzyOfficial/lightdm-tty/HEAD/img/bg.jpg -------------------------------------------------------------------------------- /screenshots/screenshot_1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eNzyOfficial/lightdm-tty/HEAD/screenshots/screenshot_1.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 |
12 |

13 | $ 14 | 15 |

16 |
17 |
18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: Sarah Kraßnigg 2 | _pkgname=lightdm-tty 3 | pkgname=lightdm-webkit2-theme-tty-git 4 | pkgver=VERSION 5 | pkgrel=1 6 | pkgdesc="A simple terminal style theme for lightdm-webkit2-greeter" 7 | arch=('any') 8 | url="https://github.com/eNzyOfficial/lightdm-tty" 9 | license=('WTFPL') 10 | depends=('lightdm-webkit2-greeter') 11 | makedepends=('git') 12 | source=("lightdm-tty::git+https://github.com/eNzyOfficial/${_pkgname}.git") 13 | md5sums=('SKIP') 14 | 15 | pkgver() { 16 | cd "$srcdir/${_pkgname}" 17 | printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)" 18 | } 19 | 20 | package() { 21 | install -dm755 "$pkgdir/usr/share/lightdm-webkit/themes/tty" 22 | cp -r "$srcdir/${_pkgname}/"* "$pkgdir/usr/share/lightdm-webkit/themes/tty/" 23 | } 24 | -------------------------------------------------------------------------------- /css/default.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: url('../img/bg.jpg') no-repeat center fixed; 3 | background-size: cover; 4 | } 5 | 6 | ::-webkit-scrollbar { 7 | width: 0px; 8 | background: transparent; /* make scrollbar transparent */ 9 | } 10 | 11 | * { 12 | font-size:12px; 13 | line-height: 16px; 14 | font-family: monospace; 15 | cursor:none; 16 | } 17 | 18 | #wrapper { 19 | position: absolute; 20 | margin: auto; 21 | padding: 10px; 22 | top: 0; 23 | right: 0; 24 | bottom: 0; 25 | left: 0; 26 | width: 600px; 27 | height: 400px; 28 | background-color: #404552; 29 | border: 3px solid #30BC6D; 30 | font-size:13px; 31 | color:#e1e5ea; 32 | } 33 | 34 | #terminal { 35 | overflow: auto; 36 | width: 100%; 37 | height: 100%; 38 | } 39 | 40 | #stdin { 41 | outline:0; 42 | border:0; 43 | background:transparent; 44 | color: #30BC6D; 45 | font-family: monospace; 46 | } 47 | 48 | #stdout { 49 | color: #30BC6D; 50 | } 51 | 52 | #prompt { 53 | display:inline-block; 54 | } 55 | 56 | p { 57 | margin:0; 58 | } 59 | 60 | .stdout-green { 61 | color:#30BC6D; 62 | } 63 | 64 | .stdout-white { 65 | color:#E1E5EA; 66 | } 67 | 68 | .stdout-off-white { 69 | color:#B3BDCB; 70 | } 71 | 72 | .stdout-red { 73 | color:#CC575D; 74 | } -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # lightdm-tty 2 | 3 | ## About 4 | 5 | A simple terminal style theme for lightdm-webkit2-greeter 6 | 7 | [Live demo](https://enzyofficial.github.io/lightdm-tty/index.html) 8 | 9 | ## Screenshot 10 | 11 | ![Screenshot](https://github.com/eNzyOfficial/lightdm-tty/blob/master/screenshots/screenshot_1.gif) 12 | 13 | ## Installation 14 | 15 | * `cd /usr/share/lightdm-webkit/themes/` 16 | * `git clone https://github.com/eNzyOfficial/lightdm-tty.git tty` 17 | * `vim /etc/lightdm/lightdm-webkit2-greeter.conf` 18 | * Find `greeter` section and set `webkit-theme` to tty 19 | 20 | You can run `./test.sh` to try it out if you like! 21 | 22 | ### Arch Linux 23 | lightdm-tty is available from the [Arch User Repository (AUR)](https://aur.archlinux.org/packages/lightdm-webkit2-theme-tty-git/). Install it with your favourite AUR helper, for example: 24 | 25 | `pacaur -S lightdm-webkit2-theme-tty-git` 26 | 27 | If you don't want to use an AUR helper, you can always build the package manually from the PKGBUILD included in the repository. 28 | 29 | ## Features 30 | 31 | * Command history using ↑ and ↓ 32 | * Clear input using `ctrl+c` 33 | * Autocomplete using `TAB` 34 | 35 | You can lock by using `dm-tool switch-to-greeter` or `dm-tool lock` 36 | 37 | ## Commands 38 | 39 | * `login [user]` - Attempts to login as user 40 | * `passwd` - Attempts to login to locked/current session 41 | * `users` = Lists users 42 | * `ls` - Lists available sessions eg. i3 43 | * `poweroff` - Shuts down machine 44 | * `reboot` - Restarts machine 45 | * `suspend` - Suspends machine 46 | * `clear` - Clears output 47 | * `help` - Lists available commands 48 | * `man [command]` - Shows manual for command 49 | * `motd` - Shows MOTD 50 | 51 | ## TODO 52 | 53 | * Make it more customizable 54 | * Languages 55 | * Layouts 56 | 57 | ## Contributions 58 | 59 | Feel free to make contributions! Suggestions, bug reports and pull requests are welcome. 60 | 61 | Thanks for [Tardog](https://github.com/Tardog) for adding it to the AUR 62 | 63 | ## License 64 | 65 | This work is free. You can redistribute it and/or modify it under the terms of the WTFPL (Do What The Fuck You Want To Public License), Version 2, as published by Sam Hocevar. See http://www.wtfpl.net/ for more details. 66 | -------------------------------------------------------------------------------- /css/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}/*# sourceMappingURL=normalize.min.css.map */ -------------------------------------------------------------------------------- /js/mock.js: -------------------------------------------------------------------------------- 1 | /* 2 | Mock data for testing your LightDM theme in the browser 3 | */ 4 | if (!('lightdm' in window)) { 5 | window.lightdm = {}; 6 | lightdm.hostname ="test-host"; 7 | lightdm.in_authentication = false; 8 | lightdm.languages = [ 9 | { 10 | code: "en_US", 11 | name: "English(US)", 12 | territory: "USA" 13 | }, 14 | { 15 | code: "en_UK", 16 | name: "English(UK)", 17 | territory: "UK" 18 | } 19 | ]; 20 | lightdm.default_language = lightdm.languages[0]; 21 | lightdm.layouts = [ 22 | { 23 | name: "Keyboard Layout 1", 24 | short_description: "A keyboard layout", 25 | long_description: "This is a layout of the keyboard" 26 | } 27 | ]; 28 | lightdm.default_layout = lightdm.layouts[0]; 29 | lightdm.layout = lightdm.layouts[0]; 30 | lightdm.sessions = [ 31 | { 32 | key: "key1", 33 | name: "session 1", 34 | comment: "no comment" 35 | }, 36 | { 37 | key: "key2", 38 | name: "session 2", 39 | comment: "no comment" 40 | } 41 | ]; 42 | 43 | lightdm.default_session = lightdm.sessions[0]; 44 | lightdm.authentication_user = null; 45 | lightdm.is_authenticated = false; 46 | lightdm.can_suspend = true; 47 | lightdm.can_hibernate = true; 48 | lightdm.can_restart = true; 49 | lightdm.can_shutdown = true; 50 | lightdm.lock_hint = false; 51 | 52 | lightdm.users = [ 53 | { 54 | name: "clarkk", 55 | real_name: "Superman", 56 | display_name: "Clark Kent", 57 | image: "http://uk.omg.li/VDXV/1756295270.jpg.x160.jpg", 58 | language: "en_US", 59 | layout: null, 60 | session: null, 61 | logged_in: false 62 | }, 63 | { 64 | name: "brucew", 65 | real_name: "Batman", 66 | display_name: "Bruce Wayne", 67 | image: "http://uk.omg.li/VDHr/OW-blog-Batman.jpg", 68 | language: "en_US", 69 | layout: null, 70 | session: null, 71 | logged_in: false 72 | }, 73 | { 74 | name: "peterp", 75 | real_name:"Spiderman", 76 | display_name: "Peter Parker", 77 | image: "", 78 | language: "en_US", 79 | layout: null, 80 | session: null, 81 | logged_in: true 82 | } 83 | ]; 84 | 85 | lightdm.num_users = lightdm.users.length; 86 | lightdm.timed_login_delay = 0; // increase to simulate timed_login_delay 87 | lightdm.timed_login_user = 88 | lightdm.timed_login_delay > 0 ? lightdm.users[0] : null; 89 | 90 | lightdm.get_string_property = function () {}; 91 | lightdm.get_integer_property = function () {}; 92 | lightdm.get_boolean_property = function () {}; 93 | lightdm.cancel_timed_login = function () { 94 | _lightdm_mock_check_argument_length(arguments, 0); 95 | 96 | lightdm._timed_login_cancelled= true; 97 | }; 98 | 99 | lightdm.provide_secret = function (secret) { 100 | if (typeof lightdm._username == 'undefined' || !lightdm._username) { 101 | throw "must call start_authentication first"; 102 | } 103 | _lightdm_mock_check_argument_length(arguments, 1); 104 | 105 | var user = _lightdm_mock_get_user(lightdm._username); 106 | 107 | // That's right, passwords are the same as the username's! 108 | if (user && secret == lightdm._username) { 109 | lightdm.is_authenticated = true; 110 | lightdm.authentication_user = user; 111 | lightdm.in_authentication = false; 112 | } else { 113 | lightdm.is_authenticated = false; 114 | lightdm.authentication_user = null; 115 | lightdm._username = null; 116 | } 117 | 118 | authentication_complete(); 119 | }; 120 | 121 | lightdm.start_session = function (session) { 122 | console.log(this._username); 123 | lightdm.login(lightdm.authentication_user, session); 124 | }; 125 | 126 | lightdm.start_authentication = function (username) { 127 | _lightdm_mock_check_argument_length(arguments, 1); 128 | 129 | if (lightdm._username) { 130 | throw "Already authenticating!"; 131 | } 132 | var user = _lightdm_mock_get_user(username); 133 | 134 | if (!user) { 135 | console.log(username + " is an invalid user"); 136 | } 137 | // show_prompt("Password: "); 138 | lightdm._username = username; 139 | lightdm.in_authentication = true; 140 | }; 141 | 142 | lightdm.cancel_authentication = function () { 143 | _lightdm_mock_check_argument_length(arguments, 0); 144 | lightdm._username = null; 145 | }; 146 | 147 | lightdm.respond = function (response) { 148 | if (false == lightdm.in_authentication) { 149 | lightdm.start_authentication(response); 150 | } else { 151 | lightdm.provide_secret(response); 152 | } 153 | }; 154 | 155 | lightdm.suspend = function () { 156 | alert("System Suspended. Bye Bye"); 157 | document.location.reload(true); 158 | }; 159 | 160 | lightdm.hibernate = function () { 161 | alert("System Hibernated. Bye Bye"); 162 | document.location.reload(true); 163 | }; 164 | 165 | lightdm.restart = function () { 166 | alert("System restart. Bye Bye"); 167 | document.location.reload(true); 168 | }; 169 | 170 | lightdm.shutdown = function () { 171 | alert("System Shutdown. Bye Bye"); 172 | document.location.reload(true); 173 | }; 174 | 175 | lightdm.login = function (user, session) { 176 | _lightdm_mock_check_argument_length(arguments, 2); 177 | 178 | if (!lightdm.is_authenticated) { 179 | throw "The system is not authenticated"; 180 | } 181 | if (user !== lightdm.authentication_user) { 182 | throw user+" is not authenticated"; 183 | } 184 | 185 | alert("logged '"+user.name+"' in to '"+session+"' successfully!"); 186 | document.location.reload(true); 187 | }; 188 | 189 | if (lightdm.timed_login_delay > 0) { 190 | setTimeout( 191 | function () { 192 | if (!lightdm._timed_login_cancelled()) timed_login(); 193 | }, 194 | lightdm.timed_login_delay 195 | ); 196 | } 197 | } 198 | 199 | // Helper functions 200 | var _lightdm_mock_check_argument_length = function (args, length) { 201 | if (args.length != length) { 202 | throw "incorrect number of arguments in function call"; 203 | } 204 | }; 205 | 206 | var _lightdm_mock_get_user = function (username) { 207 | var user = null; 208 | for (var i = 0; i < lightdm.users.length; ++i) { 209 | if (lightdm.users[i].name == username) { 210 | user= lightdm.users[i]; 211 | break; 212 | } 213 | } 214 | return user; 215 | }; -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | class tty { 2 | constructor(commands, debug = false) { 3 | // Set up history 4 | this.history = []; 5 | this.history_index = 0; 6 | this.previous = null; 7 | this.session = lightdm.default_session; 8 | 9 | // Set up input handlers and focus 10 | this.input = document.getElementById('stdin'); 11 | this.input.addEventListener('keypress', this._keypress.bind(this)); 12 | this.input.addEventListener('keydown', this._keydown.bind(this)); 13 | this.input.addEventListener('keyup', this._keyup.bind(this)); 14 | this.input.focus(); 15 | 16 | // Misc stuff 17 | this.debug = true; 18 | this.utils = new utils(); 19 | 20 | // Check logged in user 21 | let user = this.utils.arrayOfObjectsHasKeyValue(lightdm.users, 'logged_in', true); 22 | 23 | // Set up output handlers 24 | this.output = document.getElementById('stdout'); 25 | this.default_prompt = user ? `${user.name}@${lightdm.hostname} $\xa0` : lightdm.hostname + " $\xa0" 26 | 27 | // Setup prompt 28 | this.prompt = document.getElementById('prompt'); 29 | this.prompt.innerHTML = this.default_prompt; 30 | 31 | 32 | // Make sure we always focus the input 33 | window.addEventListener('click', function (e) { 34 | this.input.focus(); 35 | }.bind(this)); 36 | 37 | window.authentication_complete = this._authentication_complete.bind(this); 38 | 39 | if (this.utils.isEmpty(commands) || typeof commands != 'object') { 40 | this.utils.except("Commands must be an object and must not be empty!"); 41 | } 42 | 43 | // Init command handler 44 | this.commands = new commandHandler(commands, this.utils); 45 | 46 | if (this.commands.exists('motd')) { 47 | this.stdout(this.commands.get('motd').callback()); 48 | } 49 | } 50 | 51 | call(command) { 52 | // Add to history 53 | this.history.unshift(command); 54 | this.stdout(`${this.prompt.innerHTML} ${command}
`); 55 | 56 | // Separate command from args 57 | let args = command.split(' '); 58 | command = args.shift(); 59 | 60 | // Check command exists, otherwise bottom out 61 | if (!this.commands.exists(command)) { 62 | this.stderr(`bash: command not found: ${command}`); 63 | return false; 64 | } 65 | 66 | // Call the command 67 | let cmd = this.commands.get(command); 68 | let callback = cmd.callback.bind(this); 69 | let response = callback(args); 70 | 71 | // Check for errors 72 | if (response === false) { 73 | return response; 74 | } 75 | 76 | // return if we don't require password 77 | if (this.utils.hasProperty(cmd, 'password') && this.utils.isFunction(cmd.password)) { 78 | this.previous = { 79 | command: command, 80 | response: response 81 | } 82 | 83 | // this.stdout("password:
"); 84 | this.prompt.innerHTML = 'password:'; 85 | this.input.type = 'password'; 86 | } 87 | 88 | if (response !== true) { 89 | this.stdout(response); 90 | } 91 | 92 | return true; 93 | } 94 | 95 | password(password) { 96 | let cmd = this.commands.get(this.previous.command); 97 | 98 | let callback = cmd.password.bind(this); 99 | let password_response = callback(password, this.previous.response); 100 | 101 | this.input.type = 'text'; 102 | this.previous = null; 103 | this.prompt.innerHTML = this.default_prompt; 104 | 105 | if (password_response == false) { 106 | this.stderr("incorrect password"); 107 | return false; 108 | } 109 | 110 | return true; 111 | } 112 | 113 | stdin() { 114 | return this.input.value; 115 | } 116 | 117 | stdout(msg) { 118 | this.output.innerHTML += msg; 119 | } 120 | 121 | stderr(msg) { 122 | this.output.innerHTML += `${msg}
`; 123 | } 124 | 125 | clear() { 126 | this.input.value = ''; 127 | this.history_index = 0; 128 | } 129 | 130 | autocomplete(input) { 131 | let keys = this.commands.keys(); 132 | let suggestions = []; 133 | 134 | keys.forEach(function (key) { 135 | if (key.substr(0, input.length) == input) { 136 | suggestions.push(key); 137 | } 138 | }); 139 | 140 | return suggestions; 141 | } 142 | 143 | set_history() { 144 | this.input.value = this.history_index == 0 ? '' : this.history[this.history_index - 1]; 145 | this.input.focus(); 146 | } 147 | 148 | _keypress(e) { 149 | if (e.which == 13) { // Enter 150 | let stdin = this.stdin(); 151 | (this.input.type == 'password') ? this.password(stdin): this.call(stdin); 152 | this.clear(); 153 | 154 | let wrapper = document.getElementById('terminal'); 155 | wrapper.scrollTop = wrapper.scrollHeight; 156 | 157 | e.preventDefault(); 158 | } 159 | } 160 | 161 | _keydown(e) { 162 | if (e.which == 9) { // Tab 163 | // TODO: Autocomplete 164 | let suggestions = this.autocomplete(this.stdin()); 165 | 166 | if (!this.utils.isEmpty(suggestions)) { 167 | this.input.value = suggestions[0]; 168 | } 169 | 170 | e.preventDefault(); 171 | } 172 | 173 | if (e.ctrlKey && e.keyCode == 67) { 174 | this.input.value = ''; 175 | this.history_index = 0; 176 | this.input.type = 'text'; 177 | this.previous = null; 178 | e.preventDefault(); 179 | } 180 | } 181 | 182 | _keyup(e) { 183 | if (e.keyCode == 38) { // Key up 184 | if (this.history_index < this.history.length) { 185 | this.history_index++; 186 | this.set_history(); 187 | } 188 | 189 | e.preventDefault(); 190 | } else if (e.keyCode == 40) { // Key down 191 | if (this.history_index >= 1) { 192 | this.history_index--; 193 | this.set_history(); 194 | } 195 | 196 | e.preventDefault(); 197 | } 198 | } 199 | 200 | _authentication_complete() { 201 | // TODO: Check if session exists 202 | if (lightdm.is_authenticated) { 203 | lightdm.start_session(this.session); 204 | } else { 205 | this.stderr("incorrect password"); 206 | } 207 | } 208 | } 209 | 210 | class commandHandler { 211 | constructor(commands, utils = null) { 212 | this.commands = commands; 213 | this.utils = (utils == null) ? new utils() : utils; 214 | } 215 | 216 | keys() { 217 | return Object.keys(this.commands); 218 | } 219 | 220 | exists(property) { 221 | return this.utils.hasProperty(this.commands, property) && 222 | this.utils.hasProperty(this.commands[property], 'callback') && 223 | this.utils.isFunction(this.commands[property].callback); 224 | } 225 | 226 | get(property) { 227 | return this.commands[property]; 228 | } 229 | 230 | set(property, object) { 231 | if (this.utils.isEmpty(object)) { 232 | this.utils.except('Can not set empty'); 233 | } 234 | 235 | if (!this.utils.hasProperty(object, 'callback') || !this.utils.isFunction(object.callback)) { 236 | this.utils.except('Can not set without a callback function'); 237 | } 238 | 239 | this.commands[property] = object; 240 | } 241 | 242 | remove(property) { 243 | delete this.commands[property]; 244 | } 245 | } 246 | 247 | class utils { 248 | constructor() { 249 | this.logger = document.getElementById('log'); 250 | } 251 | 252 | isEmpty(value) { 253 | if (['', null, 'null', undefined, 'undefined'].includes(value)) { 254 | return true; 255 | } 256 | 257 | if (value instanceof Array) { 258 | return value.length === 0; 259 | } else if (value.constructor.name == 'object') { 260 | for (var property in value) { 261 | if (value.hasOwnProperty(property)) { 262 | return false; 263 | } 264 | } 265 | 266 | return true; 267 | } 268 | } 269 | 270 | isFunction(value) { 271 | return (typeof value == 'function'); 272 | } 273 | 274 | hasProperty(object, property) { 275 | if (this.isEmpty(object)) { 276 | return false; 277 | } 278 | 279 | return object.hasOwnProperty(property); 280 | } 281 | 282 | arrayOfObjectsHasKeyValue(arrayOfObjects, key, value) { 283 | let result = arrayOfObjects.filter(function (obj) { 284 | return obj[key] == value; 285 | }); 286 | 287 | if (result.length > 0) { 288 | return result[0]; 289 | } 290 | 291 | return false; 292 | } 293 | 294 | log(msg, log = false) { 295 | console.log(msg); 296 | 297 | if (log) { 298 | this.debug(msg); 299 | } 300 | } 301 | 302 | debug(msg) { 303 | if (!this.isEmpty(this.logger)) { 304 | this.logger.innerHTML += `

${msg}

`; 305 | } 306 | } 307 | 308 | except(msg, log = true) { 309 | throw msg; 310 | 311 | if (log) { 312 | this.debug(msg); 313 | } 314 | } 315 | } 316 | 317 | document.addEventListener("DOMContentLoaded", function () { 318 | let terminal = new tty(commands); 319 | }); -------------------------------------------------------------------------------- /js/commands.js: -------------------------------------------------------------------------------- 1 | let commands = { 2 | login: { 3 | help: function(args) { 4 | return '
NAME
'+ 5 | '    login

'+ 6 | 'SYNOPSIS
'+ 7 | '    login [USER]

'+ 8 | 'DESCRIPTION
'+ 9 | '    Login using the given username.

'; 10 | }, 11 | 12 | callback: function (args) { 13 | let user = this.utils.arrayOfObjectsHasKeyValue(lightdm.users, 'name', args[0]); 14 | 15 | if (!user) { 16 | this.stderr(`bash: no such user: ${args[0]}`); 17 | return false; 18 | } 19 | 20 | if(lightdm.in_authentication) { 21 | lightdm.cancel_authentication(); 22 | } 23 | 24 | this.session = user.session !== null && user.session === this.session ? user.session : this.session; 25 | lightdm.start_authentication(user.name); 26 | return true; 27 | }, 28 | 29 | password: function(password, response) { 30 | if (lightdm.in_authentication) { 31 | setTimeout(function(){ 32 | lightdm.respond(password); 33 | }, 200); 34 | 35 | return null; 36 | } 37 | 38 | return `call login [user]
`; 39 | } 40 | }, 41 | passwd: { 42 | help: function(args) { 43 | return '
NAME
'+ 44 | '    passwd

'+ 45 | 'SYNOPSIS
'+ 46 | '    passwd

'+ 47 | 'DESCRIPTION
'+ 48 | '    Login to an existing session.

'; 49 | }, 50 | 51 | callback: function (args) { 52 | let user = this.utils.arrayOfObjectsHasKeyValue(lightdm.users, 'logged_in', true); 53 | 54 | if (!user) { 55 | this.stderr(`bash: no sessions exist`); 56 | return false; 57 | } 58 | 59 | if(lightdm.in_authentication) { 60 | lightdm.cancel_authentication(); 61 | } 62 | 63 | this.session = user.session !== null ? user.session : lightdm.default_session; 64 | lightdm.start_authentication(user[0].name); 65 | return true; 66 | }, 67 | 68 | password: function(password, response) { 69 | if (lightdm.in_authentication) { 70 | setTimeout(function(){ 71 | lightdm.respond(password); 72 | }, 200); 73 | 74 | return null; 75 | } 76 | 77 | return `call login [user]
`; 78 | } 79 | }, 80 | users: { 81 | help: function(args) { 82 | return '
NAME
'+ 83 | '    users

'+ 84 | 'SYNOPSIS
'+ 85 | '    users

'+ 86 | 'DESCRIPTION
'+ 87 | '    List out all available users.

'; 88 | }, 89 | 90 | callback: function(args) { 91 | let users = ''; 92 | 93 | lightdm.users.forEach(function(user) { 94 | users += '' + user.name + "
"; 95 | }); 96 | 97 | return users; 98 | } 99 | }, 100 | ls: { 101 | help: function(args) { 102 | return '
NAME
'+ 103 | '    ls

'+ 104 | 'SYNOPSIS
'+ 105 | '    ls

'+ 106 | 'DESCRIPTION
'+ 107 | '    List out all available sessions.

'; 108 | }, 109 | 110 | callback: function(args) { 111 | sessions = ''; 112 | 113 | lightdm.sessions.forEach(function(session) { 114 | sessions += '' + session.key + "
"; 115 | }); 116 | 117 | return sessions; 118 | } 119 | }, 120 | session: { 121 | help: function(args) { 122 | return '
NAME
'+ 123 | '    session

'+ 124 | 'SYNOPSIS
'+ 125 | '    session [NAME]

'+ 126 | 'DESCRIPTION
'+ 127 | '    Set the session to login to.

'; 128 | }, 129 | 130 | callback: function(args) { 131 | // TODO: Verify session 132 | let session = args[0]; 133 | session = this.utils.arrayOfObjectsHasKeyValue(lightdm.sessions, 'key', session); 134 | 135 | if (!session) { 136 | this.stderr(`bash: no such session: ${session}`); 137 | return false; 138 | } 139 | 140 | this.session = session.key; 141 | return true; 142 | } 143 | }, 144 | poweroff: { 145 | help: function(args) { 146 | return '
NAME
'+ 147 | '    poweroff

'+ 148 | 'SYNOPSIS
'+ 149 | '    poweroff

'+ 150 | 'DESCRIPTION
'+ 151 | '    May be used to power off the machine.

'; 152 | }, 153 | 154 | callback: function(args) { 155 | lightdm.shutdown(); 156 | return true; 157 | } 158 | }, 159 | reboot: { 160 | help: function(args) { 161 | return '
NAME
'+ 162 | '    reboot

'+ 163 | 'SYNOPSIS
'+ 164 | '    reboot

'+ 165 | 'DESCRIPTION
'+ 166 | '    May be used to restart the machine.

'; 167 | }, 168 | 169 | callback: function(args) { 170 | lightdm.restart(); 171 | return true; 172 | } 173 | }, 174 | suspend: { 175 | help: function(args) { 176 | return '
NAME
'+ 177 | '    suspend

'+ 178 | 'SYNOPSIS
'+ 179 | '    suspend

'+ 180 | 'DESCRIPTION
'+ 181 | '    May be used to suspend the machine.

'; 182 | }, 183 | 184 | callback: function(args) { 185 | lightdm.suspend(); 186 | return true; 187 | } 188 | }, 189 | clear: { 190 | help: function(args) { 191 | return '
NAME
'+ 192 | '    clear

'+ 193 | 'SYNOPSIS
'+ 194 | '    clear

'+ 195 | 'DESCRIPTION
'+ 196 | '    Clear the terminal screen.

'; 197 | }, 198 | 199 | callback: function(args) { 200 | this.output.innerHTML = ''; 201 | return true; 202 | } 203 | }, 204 | help: { 205 | callback: function(args) { 206 | let keys = this.commands.keys(); 207 | let stdout = ''; 208 | 209 | for (var i in keys) { 210 | stdout += `${keys[i]}   `; 211 | }; 212 | 213 | return stdout + "
"; 214 | } 215 | }, 216 | man: { 217 | help: function(args) { 218 | return '
NAME
'+ 219 | '    man

'+ 220 | 'SYNOPSIS
'+ 221 | '    man [COMMAND]

'+ 222 | 'DESCRIPTION
'+ 223 | '    A refence manual to give information about a specific command.

'; 224 | }, 225 | 226 | callback: function(args) { 227 | let keys = this.commands.keys(); 228 | let stdin = args[0]; 229 | let stdout = ''; 230 | let stderr = `No manual entry for ${stdin}
`; 231 | 232 | if(this.utils.isEmpty(stdin)) { 233 | return 'What manual page do you want?
'; 234 | } 235 | 236 | // If command not in keys 237 | 238 | 239 | if (this.commands.exists(stdin)) { 240 | let command = this.commands.get(stdin); 241 | 242 | if (this.utils.hasProperty(command, 'help')) { 243 | stdout += command['help'](); 244 | } 245 | else { 246 | console.log("man"); 247 | return stderr; 248 | } 249 | } else { 250 | return stderr; 251 | } 252 | 253 | return stdout; 254 | } 255 | }, 256 | motd: { 257 | help: function(args) { 258 | return '
NAME
'+ 259 | '    motd

'+ 260 | 'SYNOPSIS
'+ 261 | '    motd

'+ 262 | 'DESCRIPTION
'+ 263 | '    Display the current motd

'; 264 | }, 265 | 266 | callback: function(args) { 267 | return "      .o.       ooooooooo.     .oooooo.   ooooo   ooooo 
"+ 268 | "     .888.      `888   `Y88.  d8P'  `Y8b  `888'   `888' 
"+ 269 | "    .8\"888.      888   .d88' 888           888     888  
"+ 270 | "   .8' `888.     888ooo88P'  888           888ooooo888  
"+ 271 | "  .88ooo8888.    888`88b.    888           888     888  
"+ 272 | " .8'     `888.   888  `88b.  `88b    ooo   888     888  
"+ 273 | "o88o     o8888o o888o  o888o  `Y8bood8P'  o888o   o888o

"; 274 | } 275 | } 276 | } --------------------------------------------------------------------------------