├── .gitignore ├── pubspec.yaml ├── lib ├── node.dart ├── app.dart ├── browser_window.dart └── electron.dart ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .buildlog 2 | .DS_Store 3 | .idea 4 | .pub/ 5 | build/ 6 | packages 7 | pubspec.lock 8 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: electron 2 | version: 0.0.2 3 | author: Richard Lincoln 4 | description: A Dart library for Electron desktop apps 5 | homepage: https://github.com/rwl/electron 6 | dependencies: 7 | barback: '>=0.15.2+4 <0.16.0' 8 | -------------------------------------------------------------------------------- /lib/node.dart: -------------------------------------------------------------------------------- 1 | library electron.node; 2 | 3 | import 'dart:js'; 4 | 5 | final String dirname = context['nodejs']['__dirname']; 6 | final _Process process = new _Process(); 7 | 8 | class _Process { 9 | String get platform => context['process']['platform']; 10 | } 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # electron 2 | 3 | A Dart library for [Electron][] desktop apps. 4 | 5 | [Electron]: http://electron.atom.io/ 6 | 7 | ## Usage 8 | 9 | main() { 10 | app.onReady.listen((_) { 11 | var mainWindow = new BrowserWindow(width: 800, height: 600); 12 | 13 | mainWindow.loadUrl('file://$dirname/index.html'); 14 | 15 | mainWindow.onClosed.listen((_) { 16 | mainWindow = null; 17 | }); 18 | }); 19 | } 20 | 21 | ## Example 22 | 23 | A simple hello world app can be found at: https://github.com/rwl/electron_app 24 | -------------------------------------------------------------------------------- /lib/app.dart: -------------------------------------------------------------------------------- 1 | library electron.app; 2 | 3 | import 'dart:js'; 4 | import 'dart:async'; 5 | 6 | JsObject _app = context['nodejs'].callMethod('require', ['app']); 7 | 8 | final app = new _App(); 9 | 10 | class _App { 11 | StreamController _readyCtrl = new StreamController.broadcast(sync: true); 12 | StreamController _windowAllClosedCtrl = new StreamController.broadcast(sync: true); 13 | 14 | _App() { 15 | _app.callMethod('on', ['window-all-closed', (event) { 16 | _windowAllClosedCtrl.add(event); 17 | }]); 18 | _app.callMethod('on', ['ready', (event) { 19 | _readyCtrl.add(event); 20 | }]); 21 | } 22 | 23 | Stream get onReady => _readyCtrl.stream; 24 | Stream get onWindowAllClosed => _windowAllClosedCtrl.stream; 25 | 26 | void quit() { 27 | _app.callMethod('quit'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/browser_window.dart: -------------------------------------------------------------------------------- 1 | library electron.browser_window; 2 | 3 | import 'dart:js'; 4 | import 'dart:async'; 5 | 6 | JsObject _BrowserWindow = 7 | context['nodejs'].callMethod('require', ['browser-window']); 8 | 9 | class BrowserWindow { 10 | final JsObject _window; 11 | StreamController _closedCtrl = new StreamController.broadcast(sync: true); 12 | 13 | BrowserWindow({int height, int width}) : _window = new JsObject( 14 | _BrowserWindow, 15 | [new JsObject.jsify({'height': height, 'width': width})]) { 16 | _window.callMethod('on', [ 17 | 'closed', 18 | (event) { 19 | _closedCtrl.add(event); 20 | } 21 | ]); 22 | } 23 | 24 | Stream get onClosed => _closedCtrl.stream; 25 | 26 | void loadUrl(String url) { 27 | _window.callMethod('loadUrl', [url]); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Richard Lincoln. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /lib/electron.dart: -------------------------------------------------------------------------------- 1 | library electron; 2 | 3 | import 'dart:async'; 4 | import 'package:barback/barback.dart'; 5 | 6 | class ElectronTransformer extends Transformer { 7 | ElectronTransformer.asPlugin(); 8 | 9 | String get allowedExtensions => ".dart.js"; 10 | 11 | Future apply(Transform transform) async { 12 | var content = await transform.primaryInput.readAsString(); 13 | var id = transform.primaryInput.id; 14 | var newContent = pre + d8 + content; 15 | transform.addOutput(new Asset.fromString(id, newContent)); 16 | } 17 | } 18 | 19 | const pre = r''' 20 | GLOBAL.nodejs = {}; 21 | GLOBAL.nodejs.__dirname = __dirname; 22 | GLOBAL.nodejs.__filename = __filename; 23 | GLOBAL.nodejs.exports = exports; 24 | GLOBAL.nodejs.module = module; 25 | GLOBAL.nodejs.require = require; 26 | '''; 27 | 28 | /// From: $(DART_SDK)/lib/_internal/compiler/js_lib/preambles/d8.js 29 | const d8 = r''' 30 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 31 | // for details. All rights reserved. Use of this source code is governed by a 32 | // BSD-style license that can be found in the LICENSE file. 33 | 34 | // Javascript preamble, that lets the output of dart2js run on V8's d8 shell. 35 | 36 | // Node wraps files and provides them with a different `this`. The global 37 | // `this` can be accessed through `global`. 38 | 39 | var self = this; 40 | if (typeof global != "undefined") self = global; // Node.js. 41 | 42 | (function(self) { 43 | // Using strict mode to avoid accidentally defining global variables. 44 | "use strict"; // Should be first statement of this function. 45 | 46 | // Location (Uri.base) 47 | 48 | var workingDirectory; 49 | // TODO(sgjesse): This does not work on Windows. 50 | if (typeof os == "object" && "system" in os) { 51 | // V8. 52 | workingDirectory = os.system("pwd"); 53 | var length = workingDirectory.length; 54 | if (workingDirectory[length - 1] == '\n') { 55 | workingDirectory = workingDirectory.substring(0, length - 1); 56 | } 57 | } else if (typeof process != "undefined" && 58 | typeof process.cwd == "function") { 59 | // Node.js. 60 | workingDirectory = process.cwd(); 61 | } 62 | self.location = { href: "file://" + workingDirectory + "/" }; 63 | 64 | // Event loop. 65 | 66 | // Task queue as cyclic list queue. 67 | var taskQueue = new Array(8); // Length is power of 2. 68 | var head = 0; 69 | var tail = 0; 70 | var mask = taskQueue.length - 1; 71 | function addTask(elem) { 72 | taskQueue[head] = elem; 73 | head = (head + 1) & mask; 74 | if (head == tail) _growTaskQueue(); 75 | } 76 | function removeTask() { 77 | if (head == tail) return; 78 | var result = taskQueue[tail]; 79 | taskQueue[tail] = undefined; 80 | tail = (tail + 1) & mask; 81 | return result; 82 | } 83 | function _growTaskQueue() { 84 | // head == tail. 85 | var length = taskQueue.length; 86 | var split = head; 87 | taskQueue.length = length * 2; 88 | if (split * 2 < length) { // split < length / 2 89 | for (var i = 0; i < split; i++) { 90 | taskQueue[length + i] = taskQueue[i]; 91 | taskQueue[i] = undefined; 92 | } 93 | head += length; 94 | } else { 95 | for (var i = split; i < length; i++) { 96 | taskQueue[length + i] = taskQueue[i]; 97 | taskQueue[i] = undefined; 98 | } 99 | tail += length; 100 | } 101 | mask = taskQueue.length - 1; 102 | } 103 | 104 | // Mapping from timer id to timer function. 105 | // The timer id is written on the function as .$timerId. 106 | // That field is cleared when the timer is cancelled, but it is not returned 107 | // from the queue until its time comes. 108 | var timerIds = {}; 109 | var timerIdCounter = 1; // Counter used to assing ids. 110 | 111 | // Zero-timer queue as simple array queue using push/shift. 112 | var zeroTimerQueue = []; 113 | 114 | function addTimer(f, ms) { 115 | var id = timerIdCounter++; 116 | f.$timerId = id; 117 | timerIds[id] = f; 118 | if (ms == 0) { 119 | zeroTimerQueue.push(f); 120 | } else { 121 | addDelayedTimer(f, ms); 122 | } 123 | return id; 124 | } 125 | 126 | function nextZeroTimer() { 127 | while (zeroTimerQueue.length > 0) { 128 | var action = zeroTimerQueue.shift(); 129 | if (action.$timerId !== undefined) return action; 130 | } 131 | } 132 | 133 | function nextEvent() { 134 | var action = removeTask(); 135 | if (action) { 136 | return action; 137 | } 138 | do { 139 | action = nextZeroTimer(); 140 | if (action) break; 141 | var nextList = nextDelayedTimerQueue(); 142 | if (!nextList) { 143 | return; 144 | } 145 | var newTime = nextList.shift(); 146 | advanceTimeTo(newTime); 147 | zeroTimerQueue = nextList; 148 | } while (true) 149 | var id = action.$timerId; 150 | clearTimerId(action, id); 151 | return action; 152 | } 153 | 154 | // Mocking time. 155 | var timeOffset = 0; 156 | var now = function() { 157 | // Install the mock Date object only once. 158 | // Following calls to "now" will just use the new (mocked) Date.now 159 | // method directly. 160 | installMockDate(); 161 | now = Date.now; 162 | return Date.now(); 163 | }; 164 | var originalDate = Date; 165 | var originalNow = originalDate.now; 166 | function advanceTimeTo(time) { 167 | timeOffset = time - originalNow(); 168 | } 169 | function installMockDate() { 170 | var NewDate = function Date(Y, M, D, h, m, s, ms) { 171 | if (this instanceof Date) { 172 | // Assume a construct call. 173 | switch (arguments.length) { 174 | case 0: return new originalDate(originalNow() + timeOffset); 175 | case 1: return new originalDate(Y); 176 | case 2: return new originalDate(Y, M); 177 | case 3: return new originalDate(Y, M, D); 178 | case 4: return new originalDate(Y, M, D, h); 179 | case 5: return new originalDate(Y, M, D, h, m); 180 | case 6: return new originalDate(Y, M, D, h, m, s); 181 | default: return new originalDate(Y, M, D, h, m, s, ms); 182 | } 183 | } 184 | return new originalDate(originalNow() + timeOffset).toString(); 185 | }; 186 | NewDate.UTC = originalDate.UTC; 187 | NewDate.parse = originalDate.parse; 188 | NewDate.now = function now() { return originalNow() + timeOffset; }; 189 | NewDate.prototype = originalDate.prototype; 190 | originalDate.prototype.constructor = NewDate; 191 | Date = NewDate; 192 | } 193 | 194 | // Heap priority queue with key index. 195 | // Each entry is list of [timeout, callback1 ... callbackn]. 196 | var timerHeap = []; 197 | var timerIndex = {}; 198 | function addDelayedTimer(f, ms) { 199 | var timeout = now() + ms; 200 | var timerList = timerIndex[timeout]; 201 | if (timerList == null) { 202 | timerList = [timeout, f]; 203 | timerIndex[timeout] = timerList; 204 | var index = timerHeap.length; 205 | timerHeap.length += 1; 206 | bubbleUp(index, timeout, timerList); 207 | } else { 208 | timerList.push(f); 209 | } 210 | } 211 | 212 | function nextDelayedTimerQueue() { 213 | if (timerHeap.length == 0) return null; 214 | var result = timerHeap[0]; 215 | var last = timerHeap.pop(); 216 | if (timerHeap.length > 0) { 217 | bubbleDown(0, last[0], last); 218 | } 219 | return result; 220 | } 221 | 222 | function bubbleUp(index, key, value) { 223 | while (index != 0) { 224 | var parentIndex = (index - 1) >> 1; 225 | var parent = timerHeap[parentIndex]; 226 | var parentKey = parent[0]; 227 | if (key > parentKey) break; 228 | timerHeap[index] = parent; 229 | index = parentIndex; 230 | } 231 | timerHeap[index] = value; 232 | } 233 | 234 | function bubbleDown(index, key, value) { 235 | while (true) { 236 | var leftChildIndex = index * 2 + 1; 237 | if (leftChildIndex >= timerHeap.length) break; 238 | var minChildIndex = leftChildIndex; 239 | var minChild = timerHeap[leftChildIndex]; 240 | var minChildKey = minChild[0]; 241 | var rightChildIndex = leftChildIndex + 1; 242 | if (rightChildIndex < timerHeap.length) { 243 | var rightChild = timerHeap[rightChildIndex]; 244 | var rightKey = rightChild[0]; 245 | if (rightKey < minChildKey) { 246 | minChildIndex = rightChildIndex; 247 | minChild = rightChild; 248 | minChildKey = rightKey; 249 | } 250 | } 251 | if (minChildKey > key) break; 252 | timerHeap[index] = minChild; 253 | index = minChildIndex; 254 | } 255 | timerHeap[index] = value; 256 | } 257 | 258 | function addInterval(f, ms) { 259 | var id = timerIdCounter++; 260 | function repeat() { 261 | // Reactivate with the same id. 262 | repeat.$timerId = id; 263 | timerIds[id] = repeat; 264 | addDelayedTimer(repeat, ms); 265 | f(); 266 | } 267 | repeat.$timerId = id; 268 | timerIds[id] = repeat; 269 | addDelayedTimer(repeat, ms); 270 | return id; 271 | } 272 | 273 | function cancelTimer(id) { 274 | var f = timerIds[id]; 275 | if (f == null) return; 276 | clearTimerId(f, id); 277 | } 278 | 279 | function clearTimerId(f, id) { 280 | f.$timerId = undefined; 281 | delete timerIds[id]; 282 | } 283 | 284 | function eventLoop(action) { 285 | while (action) { 286 | try { 287 | action(); 288 | } catch (e) { 289 | if (typeof onerror == "function") { 290 | onerror(e, null, -1); 291 | } else { 292 | throw e; 293 | } 294 | } 295 | action = nextEvent(); 296 | } 297 | } 298 | 299 | // Global properties. "self" refers to the global object, so adding a 300 | // property to "self" defines a global variable. 301 | self.self = self 302 | self.dartMainRunner = function(main, args) { 303 | // Initialize. 304 | var action = function() { main(args); } 305 | eventLoop(action); 306 | }; 307 | self.setTimeout = addTimer; 308 | self.clearTimeout = cancelTimer; 309 | self.setInterval = addInterval; 310 | self.clearInterval = cancelTimer; 311 | self.scheduleImmediate = addTask; 312 | 313 | // Support for deferred loading. 314 | self.dartDeferredLibraryLoader = function(uri, successCallback, errorCallback) { 315 | try { 316 | load(uri); 317 | successCallback(); 318 | } catch (error) { 319 | errorCallback(error); 320 | } 321 | }; 322 | })(self); 323 | '''; --------------------------------------------------------------------------------