├── context.js ├── data ├── icons │ ├── 128.png │ ├── 16.png │ ├── 256.png │ ├── 32.png │ ├── 48.png │ ├── 512.png │ ├── 64.png │ ├── disabled │ │ ├── 16.png │ │ ├── 32.png │ │ └── 48.png │ ├── downloads │ │ ├── active │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ └── 48.png │ │ └── disabled │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ └── 48.png │ └── timer │ │ ├── 16.png │ │ ├── 32.png │ │ └── 48.png └── offscreen │ ├── index.html │ └── index.js ├── downloads.js ├── manifest.json └── worker.js /context.js: -------------------------------------------------------------------------------- 1 | /* global update */ 2 | 3 | const schedule = minutes => { 4 | chrome.alarms.clearAll(() => { 5 | chrome.alarms.create('timeout.' + minutes, { 6 | when: Date.now() + Number(minutes) * 60 * 1000 7 | }); 8 | chrome.storage.local.set({ 9 | enabled: true 10 | }, () => update('timer.set')); // update is required since the extension might be enabled when timer is set 11 | }); 12 | }; 13 | 14 | { 15 | const once = () => chrome.storage.local.get({ 16 | level: 'display', 17 | timeouts: [{ 18 | period: 5, 19 | title: '5 Minutes' 20 | }, { 21 | period: 15, 22 | title: '15 Minutes' 23 | }, { 24 | period: 30, 25 | title: '30 Minutes' 26 | }, { 27 | period: 60, 28 | title: '1 Hour' 29 | }, { 30 | period: 6 * 60, 31 | title: '6 Hours' 32 | }, { 33 | period: 12 * 60, 34 | title: '12 Hours' 35 | }, { 36 | period: 24 * 60, 37 | title: '24 Hours' 38 | }], 39 | downloads: false, 40 | aggressive: 'reportActivity' in chrome.power, 41 | badge: '' 42 | }, prefs => { 43 | chrome.contextMenus.create({ 44 | id: 'downloads', 45 | title: 'Keep Awake while Downloading a File', 46 | contexts: ['action'], 47 | type: 'checkbox', 48 | checked: prefs.downloads 49 | }); 50 | chrome.contextMenus.create({ 51 | id: 'level.system', 52 | title: 'System Level', 53 | type: 'radio', 54 | contexts: ['action'], 55 | checked: prefs.level === 'system' 56 | }); 57 | chrome.contextMenus.create({ 58 | id: 'level.display', 59 | title: 'Display Level', 60 | type: 'radio', 61 | contexts: ['action'], 62 | checked: prefs.level === 'display' && prefs.aggressive === false 63 | }); 64 | chrome.contextMenus.create({ 65 | id: 'level.aggressive', 66 | title: 'Aggressive Level (ChromeOS)', 67 | type: 'radio', 68 | contexts: ['action'], 69 | checked: prefs.level === 'display' && prefs.aggressive === true, 70 | enabled: 'reportActivity' in chrome.power 71 | }); 72 | chrome.contextMenus.create({ 73 | id: 'timeout', 74 | title: 'Enable For', 75 | contexts: ['action'] 76 | }); 77 | prefs.timeouts.forEach(({period, title}) => chrome.contextMenus.create({ 78 | id: period + '', 79 | parentId: 'timeout', 80 | title, 81 | contexts: ['action'] 82 | })); 83 | chrome.contextMenus.create({ 84 | id: 'saddsfsf', 85 | parentId: 'timeout', 86 | type: 'separator', 87 | contexts: ['action'] 88 | }); 89 | chrome.contextMenus.create({ 90 | id: '-1', 91 | parentId: 'timeout', 92 | title: 'Custom Period', 93 | contexts: ['action'] 94 | }); 95 | chrome.contextMenus.create({ 96 | id: 'badge', 97 | title: 'Badge Disabled Symbol', 98 | contexts: ['action'] 99 | }); 100 | chrome.contextMenus.create({ 101 | id: 'badge.0', 102 | title: 'no symbol', 103 | type: 'radio', 104 | contexts: ['action'], 105 | checked: prefs.badge === '', 106 | parentId: 'badge' 107 | }); 108 | chrome.contextMenus.create({ 109 | id: 'badge.d', 110 | title: '"d" symbol', 111 | type: 'radio', 112 | contexts: ['action'], 113 | checked: prefs.badge === 'd', 114 | parentId: 'badge' 115 | }); 116 | chrome.contextMenus.create({ 117 | id: 'badge.×', 118 | title: '"×" symbol', 119 | type: 'radio', 120 | contexts: ['action'], 121 | checked: prefs.badge === '×', 122 | parentId: 'badge' 123 | }); 124 | chrome.contextMenus.create({ 125 | id: 'badge.•', 126 | title: '"•" symbol', 127 | type: 'radio', 128 | contexts: ['action'], 129 | checked: prefs.badge === '•', 130 | parentId: 'badge' 131 | }); 132 | chrome.contextMenus.create({ 133 | id: 'icons', 134 | title: 'Icon Color Codes', 135 | contexts: ['action'] 136 | }); 137 | }); 138 | 139 | chrome.runtime.onStartup.addListener(once); 140 | chrome.runtime.onInstalled.addListener(once); 141 | } 142 | chrome.contextMenus.onClicked.addListener((info, tab) => { 143 | if (info.menuItemId === 'icons') { 144 | chrome.tabs.create({ 145 | url: chrome.runtime.getManifest().homepage_url + '#faq5', 146 | index: tab.index + 1 147 | }); 148 | } 149 | else if (info.menuItemId.startsWith('level.')) { 150 | if (info.menuItemId === 'level.aggressive') { 151 | chrome.storage.local.set({ 152 | level: 'display', 153 | aggressive: true 154 | }); 155 | } 156 | else { 157 | chrome.storage.local.set({ 158 | level: info.menuItemId.replace('level.', ''), 159 | aggressive: false 160 | }); 161 | } 162 | } 163 | else if (info.menuItemId === 'downloads') { 164 | if (info.checked) { 165 | chrome.permissions.request({ 166 | permissions: ['downloads'] 167 | }, granted => { 168 | chrome.storage.local.set({ 169 | downloads: granted 170 | }); 171 | if (granted === false) { 172 | chrome.contextMenus.update('downloads', { 173 | checked: false 174 | }); 175 | } 176 | }); 177 | } 178 | else { 179 | chrome.storage.local.set({ 180 | downloads: false 181 | }); 182 | } 183 | } 184 | else if (info.menuItemId.startsWith('badge.')) { 185 | chrome.storage.local.set({ 186 | 'badge': info.menuItemId === 'badge.0' ? '' : info.menuItemId.at(-1) 187 | }); 188 | } 189 | else { 190 | if (info.menuItemId === '-1') { 191 | chrome.offscreen.createDocument({ 192 | url: '/data/offscreen/index.html', 193 | reasons: ['IFRAME_SCRIPTING'], 194 | justification: 'Ask custom timer period from user' 195 | }); 196 | } 197 | else { 198 | schedule(info.menuItemId); 199 | } 200 | } 201 | }); 202 | 203 | chrome.runtime.onMessage.addListener((request, sender, response) => { 204 | if (request.method === 'custom.timeout') { 205 | if (request.active) { 206 | schedule(request.minutes); 207 | } 208 | else { 209 | chrome.alarms.clearAll(() => chrome.storage.local.set({ 210 | enabled: false 211 | })); 212 | } 213 | response(true); 214 | } 215 | }); 216 | -------------------------------------------------------------------------------- /data/icons/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandler-stimson/caffeine/1dcd3b699923eb9691a86330f92e384d2d642f2c/data/icons/128.png -------------------------------------------------------------------------------- /data/icons/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandler-stimson/caffeine/1dcd3b699923eb9691a86330f92e384d2d642f2c/data/icons/16.png -------------------------------------------------------------------------------- /data/icons/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandler-stimson/caffeine/1dcd3b699923eb9691a86330f92e384d2d642f2c/data/icons/256.png -------------------------------------------------------------------------------- /data/icons/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandler-stimson/caffeine/1dcd3b699923eb9691a86330f92e384d2d642f2c/data/icons/32.png -------------------------------------------------------------------------------- /data/icons/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandler-stimson/caffeine/1dcd3b699923eb9691a86330f92e384d2d642f2c/data/icons/48.png -------------------------------------------------------------------------------- /data/icons/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandler-stimson/caffeine/1dcd3b699923eb9691a86330f92e384d2d642f2c/data/icons/512.png -------------------------------------------------------------------------------- /data/icons/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandler-stimson/caffeine/1dcd3b699923eb9691a86330f92e384d2d642f2c/data/icons/64.png -------------------------------------------------------------------------------- /data/icons/disabled/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandler-stimson/caffeine/1dcd3b699923eb9691a86330f92e384d2d642f2c/data/icons/disabled/16.png -------------------------------------------------------------------------------- /data/icons/disabled/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandler-stimson/caffeine/1dcd3b699923eb9691a86330f92e384d2d642f2c/data/icons/disabled/32.png -------------------------------------------------------------------------------- /data/icons/disabled/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandler-stimson/caffeine/1dcd3b699923eb9691a86330f92e384d2d642f2c/data/icons/disabled/48.png -------------------------------------------------------------------------------- /data/icons/downloads/active/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandler-stimson/caffeine/1dcd3b699923eb9691a86330f92e384d2d642f2c/data/icons/downloads/active/16.png -------------------------------------------------------------------------------- /data/icons/downloads/active/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandler-stimson/caffeine/1dcd3b699923eb9691a86330f92e384d2d642f2c/data/icons/downloads/active/32.png -------------------------------------------------------------------------------- /data/icons/downloads/active/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandler-stimson/caffeine/1dcd3b699923eb9691a86330f92e384d2d642f2c/data/icons/downloads/active/48.png -------------------------------------------------------------------------------- /data/icons/downloads/disabled/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandler-stimson/caffeine/1dcd3b699923eb9691a86330f92e384d2d642f2c/data/icons/downloads/disabled/16.png -------------------------------------------------------------------------------- /data/icons/downloads/disabled/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandler-stimson/caffeine/1dcd3b699923eb9691a86330f92e384d2d642f2c/data/icons/downloads/disabled/32.png -------------------------------------------------------------------------------- /data/icons/downloads/disabled/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandler-stimson/caffeine/1dcd3b699923eb9691a86330f92e384d2d642f2c/data/icons/downloads/disabled/48.png -------------------------------------------------------------------------------- /data/icons/timer/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandler-stimson/caffeine/1dcd3b699923eb9691a86330f92e384d2d642f2c/data/icons/timer/16.png -------------------------------------------------------------------------------- /data/icons/timer/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandler-stimson/caffeine/1dcd3b699923eb9691a86330f92e384d2d642f2c/data/icons/timer/32.png -------------------------------------------------------------------------------- /data/icons/timer/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandler-stimson/caffeine/1dcd3b699923eb9691a86330f92e384d2d642f2c/data/icons/timer/48.png -------------------------------------------------------------------------------- /data/offscreen/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /data/offscreen/index.js: -------------------------------------------------------------------------------- 1 | const h = prompt('Enter custom time in hours for Caffeine to be enabled', 5); 2 | 3 | const minutes = Number(h) * 60; 4 | chrome.runtime.sendMessage({ 5 | method: 'custom.timeout', 6 | minutes, 7 | active: isNaN(h) || !h ? false : (minutes >= 1) 8 | }, () => close()); 9 | -------------------------------------------------------------------------------- /downloads.js: -------------------------------------------------------------------------------- 1 | /* global update */ 2 | 3 | { 4 | const observe = () => chrome.storage.local.get({ 5 | downloads: false 6 | }, prefs => chrome.downloads.search({state: 'in_progress'}).then(ds => { 7 | chrome.storage.session.set({ 8 | 'secondary-enabled': ds.length !== 0 && prefs.downloads 9 | }, () => update('active.downloads')); 10 | })); 11 | const register = () => chrome.downloads && chrome.storage.local.get({ 12 | downloads: false 13 | }, prefs => { 14 | if (chrome.downloads) { 15 | chrome.downloads.onChanged.removeListener(observe); 16 | if (prefs.downloads) { 17 | chrome.downloads.onChanged.addListener(observe); 18 | } 19 | observe(); 20 | } 21 | }); 22 | register(); 23 | chrome.storage.onChanged.addListener(ps => ps.downloads && register()); 24 | } 25 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "version": "0.2.8", 4 | "name": "Caffeine - Keep Awake", 5 | "description": "Prevent screen dimming or activating a screen saver by overriding power-saving settings to keep the system or display awake", 6 | "permissions": [ 7 | "storage", 8 | "power", 9 | "idle", 10 | "contextMenus", 11 | "alarms", 12 | "offscreen" 13 | ], 14 | "optional_permissions": [ 15 | "downloads" 16 | ], 17 | "icons": { 18 | "16": "/data/icons/16.png", 19 | "32": "/data/icons/32.png", 20 | "48": "/data/icons/48.png", 21 | "64": "/data/icons/64.png", 22 | "128": "/data/icons/128.png", 23 | "256": "/data/icons/256.png" 24 | }, 25 | "background": { 26 | "service_worker": "worker.js" 27 | }, 28 | "action": { 29 | "default_icon": { 30 | "16": "/data/icons/disabled/16.png", 31 | "32": "/data/icons/disabled/32.png", 32 | "48": "/data/icons/disabled/48.png" 33 | } 34 | }, 35 | "homepage_url": "https://webextension.org/listing/caffeine.html", 36 | "commands": { 37 | "_execute_action": {} 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /worker.js: -------------------------------------------------------------------------------- 1 | self.importScripts('context.js'); 2 | self.importScripts('downloads.js'); 3 | 4 | { 5 | const once = () => { 6 | if (once.done) { 7 | return; 8 | } 9 | once.done = true; 10 | chrome.action.setBadgeBackgroundColor({ 11 | color: '#666' 12 | }); 13 | }; 14 | chrome.runtime.onInstalled.addListener(once); 15 | chrome.runtime.onStartup.addListener(once); 16 | } 17 | 18 | const update = async reason => { 19 | try { 20 | const p1 = await chrome.storage.local.get({ 21 | 'enabled': false, 22 | 'level': 'display', 23 | 'badge': '' 24 | }); 25 | const p2 = await chrome.storage.session.get({ 26 | 'secondary-enabled': false 27 | }); 28 | 29 | console.info('updating: ', reason); 30 | 31 | const as = await chrome.alarms.getAll(); 32 | 33 | let b = p1.enabled ? (as.length ? 'timer' : '') : 'disabled'; 34 | if (p2['secondary-enabled']) { 35 | b = p1.enabled ? 'downloads/active' : 'downloads/disabled'; 36 | } 37 | chrome.action.setIcon({ 38 | path: { 39 | '16': '/data/icons/' + b + '/16.png', 40 | '32': '/data/icons/' + b + '/32.png', 41 | '48': '/data/icons/' + b + '/48.png' 42 | } 43 | }); 44 | chrome.action.setBadgeText({ 45 | text: b === 'disabled' ? p1.badge : '' 46 | }); 47 | 48 | await chrome.power.releaseKeepAwake(); 49 | 50 | if (p1.enabled || p2['secondary-enabled']) { 51 | let title = 'Caffeine is Enabled at "[level]" level'; 52 | if (p1.enabled === false) { 53 | title = title.replace('Enabled', 'only Enabled while Downloading'); 54 | } 55 | if (as.length) { 56 | title += ' for [timeout] minutes'; 57 | } 58 | 59 | await chrome.power.requestKeepAwake(p1.level); 60 | 61 | chrome.action.setTitle({ 62 | title: title.replace('[level]', p1.level).replace('[timeout]', as[0]?.name.replace('timeout.', '')) 63 | }); 64 | } 65 | else { 66 | chrome.action.setTitle({ 67 | title: `Caffeine is Disabled` 68 | }); 69 | chrome.alarms.clearAll(); 70 | } 71 | chrome.action.setBadgeText({ 72 | text: '' 73 | }); 74 | } 75 | catch (e) { 76 | console.error(e); 77 | chrome.action.setTitle({ 78 | title: e.message 79 | }); 80 | chrome.action.setBadgeText({ 81 | text: 'E' 82 | }); 83 | } 84 | }; 85 | 86 | chrome.action.onClicked.addListener(async () => { 87 | const prefs = await chrome.storage.local.get({ 88 | enabled: false 89 | }); 90 | chrome.storage.local.set({ 91 | enabled: prefs.enabled === false 92 | }); 93 | }); 94 | 95 | chrome.storage.onChanged.addListener(ps => { 96 | if (ps.level || ps.enabled || ps.badge) { 97 | update('prefs.changed'); 98 | } 99 | }); 100 | chrome.runtime.onStartup.addListener(() => update('on.startup')); 101 | chrome.runtime.onInstalled.addListener(() => update('on.installed')); 102 | 103 | chrome.alarms.onAlarm.addListener(() => { 104 | chrome.storage.local.set({ 105 | enabled: false 106 | }); 107 | }); 108 | 109 | // aggressive mode for ChromeOS 110 | if ('reportActivity' in chrome.power) { 111 | chrome.idle.onStateChanged.addListener(state => { 112 | if (state === 'active' ) { // make sure timers are working 113 | const now = Date.now(); 114 | chrome.alarms.getAll(alarms => { 115 | for (const o of alarms) { 116 | if (o.scheduledTime < now) { 117 | console.info('missed timer', o); 118 | chrome.alarms.create(o.name, { 119 | when: now + Math.round(Math.random() * 1000) 120 | }); 121 | } 122 | } 123 | }); 124 | } 125 | else { 126 | chrome.storage.local.get({ 127 | enabled: false, 128 | aggressive: true 129 | }, prefs => { 130 | if (prefs.enabled && prefs.aggressive) { 131 | chrome.power.reportActivity(() => { 132 | console.log('report activity'); 133 | }); 134 | } 135 | }); 136 | } 137 | }); 138 | } 139 | 140 | /* FAQs & Feedback */ 141 | { 142 | const {management, runtime: {onInstalled, setUninstallURL, getManifest}, storage, tabs} = chrome; 143 | if (navigator.webdriver !== true) { 144 | const {homepage_url: page, name, version} = getManifest(); 145 | onInstalled.addListener(({reason, previousVersion}) => { 146 | management.getSelf(({installType}) => installType === 'normal' && storage.local.get({ 147 | 'faqs': true, 148 | 'last-update': 0 149 | }, prefs => { 150 | if (reason === 'install' || (prefs.faqs && reason === 'update')) { 151 | const doUpdate = (Date.now() - prefs['last-update']) / 1000 / 60 / 60 / 24 > 45; 152 | if (doUpdate && previousVersion !== version) { 153 | tabs.query({active: true, lastFocusedWindow: true}, tbs => tabs.create({ 154 | url: page + '?version=' + version + (previousVersion ? '&p=' + previousVersion : '') + '&type=' + reason, 155 | active: reason === 'install', 156 | ...(tbs && tbs.length && {index: tbs[0].index + 1}) 157 | })); 158 | storage.local.set({'last-update': Date.now()}); 159 | } 160 | } 161 | })); 162 | }); 163 | setUninstallURL(page + '?rd=feedback&name=' + encodeURIComponent(name) + '&version=' + version); 164 | } 165 | } 166 | --------------------------------------------------------------------------------