├── .gitignore ├── .gitmodules ├── README.md ├── api ├── pine.env.js └── pine.gamepad.js ├── app ├── api │ ├── achievements.js │ └── index.js ├── index.js └── package.json ├── auto-pine ├── .gitignore ├── README.md ├── bin │ ├── boot_parts.sh │ ├── download.sh │ ├── first_build.sh │ ├── mksd │ ├── mksd-nfs │ ├── reload-nfs.sh │ ├── start-edit-session.sh │ ├── stop-edit-session.sh │ └── unpack.sh ├── defaults │ ├── boot │ │ └── cmdline.txt │ ├── stage-1 │ │ ├── etc │ │ │ ├── init.d │ │ │ │ └── start_pine_daemons.sh │ │ │ └── inittab │ │ └── root │ │ │ ├── .bashrc │ │ │ └── setup │ │ │ └── pine_setup.sh │ └── stage-2 │ │ ├── etc │ │ └── inittab │ │ └── home │ │ └── pine-user │ │ ├── .bashrc │ │ ├── htdocs │ │ ├── css │ │ │ └── app.css │ │ ├── index.html │ │ └── js │ │ │ └── app.js │ │ ├── kill_chrome.sh │ │ └── pine.js ├── distro │ ├── .gitignore │ ├── README.md │ └── clean.sh └── kernel-qemu ├── ui ├── README.md ├── dev-server.js ├── index.html ├── lib │ └── package-meta.js ├── package.json ├── packages │ ├── benchmark │ │ ├── index.html │ │ ├── js │ │ │ └── src │ │ │ │ ├── benchmark.js │ │ │ │ └── framerate.js │ │ └── pine-package.json │ ├── gamepad │ │ ├── index.html │ │ └── pine-package.json │ └── sample_game │ │ ├── css │ │ ├── bootstrap.min.css │ │ └── game.css │ │ ├── index.html │ │ ├── js │ │ └── src │ │ │ └── key-input-demo.js │ │ └── pine-package.json └── public │ ├── css │ ├── bootstrap.css │ ├── bootstrap.min.css │ ├── pine-animations.css │ └── pine.css │ ├── img │ ├── glyphicons-halflings-white.png │ └── glyphicons-halflings.png │ └── js │ ├── app.js │ ├── constants.js │ ├── init.js │ ├── lib │ ├── backbone.js │ ├── bootstrap.js │ ├── bootstrap.min.js │ ├── jquery.js │ ├── minpubsub.js │ ├── pine.keyrouter.js │ ├── qunit │ │ ├── qunit.css │ │ └── qunit.js │ ├── require.js │ ├── test │ │ ├── test.keyboard.html │ │ ├── test.pine.input.html │ │ └── test.pine.keyrouter.html │ └── underscore.js │ ├── plugin │ ├── jquery.v-keys.js │ └── v-keys-helper.js │ └── view │ ├── view.game-init.js │ ├── view.menu-pager.js │ ├── view.menu.js │ └── view.v-keys.js └── util ├── build_kernel.sh ├── install_chromium.sh ├── prep_raspbian_for_pine.sh ├── set_up_raspbian_rootfs.sh └── start_chrome.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | .DS_Store 3 | node_modules 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "dep/tools"] 2 | path = dep/tools 3 | url = https://github.com/raspberrypi/tools.git 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pine 2 | ## An HTML5 game console 3 | 4 | Pine is a project with the goal of creating an HTML5 game console that runs on 5 | Raspberry Pi. The software stack is powered by Linux, NodeJS and Chromium. 6 | Please [look at the wiki](https://github.com/jeremyckahn/pine/wiki) to learn 7 | about Pine and discuss ideas or questions in the [Google 8 | Group](https://groups.google.com/forum/#!forum/pine-discuss). Participation of 9 | any kind is welcomed and encouraged! 10 | 11 | ## Note 12 | 13 | 2013-07-06: This repo is going to be restructured over the next few days; code currently available should be considered deprecated. 14 | -------------------------------------------------------------------------------- /api/pine.env.js: -------------------------------------------------------------------------------- 1 | /*jslint nomen: true, plusplus: true, undef: true, todo: true, white: true, 2 | browser: true, indent: 2, maxlen: 80 */ 3 | ;(function (global) { 4 | 5 | 'use strict'; 6 | 7 | var pine = global.pine = (global.pine || {}); 8 | var env = pine.env = {}; 9 | 10 | 11 | /** 12 | * Adding "dev=1" to the URL query string invokes Developer Mode. 13 | * @type {boolean} 14 | **/ 15 | env.isDev = !!location.toString().match(/\?(.*dev\=1)/) 16 | 17 | 18 | pine.env.exit = function () { 19 | window.location = '/'; 20 | }; 21 | 22 | 23 | if (env.isDev) { 24 | document.documentElement.addEventListener('keydown', function (evt) { 25 | // TODO: Replace this with a constant 26 | if (evt.which === 27) { // esc 27 | pine.env.exit(); 28 | } 29 | }); 30 | } 31 | 32 | } (this)); 33 | -------------------------------------------------------------------------------- /api/pine.gamepad.js: -------------------------------------------------------------------------------- 1 | /*jslint nomen: true, plusplus: true, undef: true, todo: true, white: true, 2 | browser: true, indent: 2, maxlen: 80 */ 3 | /*global Backbone: false, _: false, $: false, KeyRouter: false, publish: false, 4 | subscribe: false */ 5 | 6 | // Pine Gamepad API module 7 | ;(function (global) { 8 | 9 | 'use strict'; 10 | 11 | var pine = global.pine = (global.pine || {}); 12 | pine.gamepad = { 13 | 'downKeys': {} 14 | }; 15 | var downKeys, gamepads; 16 | 17 | function tick () { 18 | 19 | global.webkitRequestAnimationFrame(tick); 20 | pine.gamepad.downKeys = downKeys = {}; 21 | gamepads = navigator.webkitGetGamepads(); 22 | 23 | var len = gamepads.length, buttonsLength, i, j, gamepad, map, id; 24 | for (i = 0; i < len; i++) { 25 | gamepad = gamepads[i]; 26 | 27 | if (!gamepad) { 28 | continue; 29 | } 30 | 31 | // Some controller id strings prepend an invisible unicode character - 32 | // slice it out if it is present. 33 | id = gamepad.id.charCodeAt(0) === 65279 34 | ? gamepad.id.slice(1) 35 | : gamepad.id; 36 | 37 | map = gamepadMap[id]; 38 | buttonsLength = gamepad.buttons.length; 39 | 40 | if (map) { 41 | for (j = 0; j < buttonsLength; j++) { 42 | if (gamepad.buttons[j] && map[j]) { 43 | downKeys[map[j]] = true; 44 | } 45 | } 46 | } 47 | } 48 | 49 | // Users can exit the game by hitting L1 + R1 + START. 50 | if (pine.env && 51 | 'START' in downKeys && 'L1' in downKeys && 'R1' in downKeys) { 52 | pine.env.exit(); 53 | } 54 | } 55 | 56 | global.webkitRequestAnimationFrame(tick); 57 | 58 | var gamepadMap = { 59 | 'Logitech RumblePad 2 USB (STANDARD GAMEPAD Vendor: 046d Product: c218)': { 60 | '0': 'B' 61 | ,'1': 'A' 62 | ,'2': 'Y' 63 | ,'3': 'X' 64 | ,'4': 'L1' 65 | ,'5': 'R1' 66 | ,'6': 'L2' 67 | ,'7': 'R2' 68 | ,'8': 'SELECT' 69 | ,'9': 'START' 70 | ,'10': 'L3' 71 | ,'11': 'R3' 72 | ,'12': 'UP' 73 | ,'13': 'DOWN' 74 | ,'14': 'LEFT' 75 | ,'15': 'RIGHT' 76 | } 77 | }; 78 | 79 | } (this)); 80 | -------------------------------------------------------------------------------- /app/api/achievements.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | 3 | var AchievementSchema = new mongoose.Schema({ 4 | title: String 5 | ,slug: String 6 | ,description: String 7 | ,display: Number 8 | ,icon: String 9 | ,status : { 10 | progress: Number 11 | ,goal: Number 12 | } 13 | }); 14 | 15 | var Achievement = mongoose.model('Achievement', AchievementSchema); 16 | 17 | // Increment an achievement 18 | exports.incr = function (game_id, achieve_id, amount) { 19 | Achievement.findOne({slug: achieve_id}, function (err, doc) { 20 | doc.status.progress++; 21 | doc.save(); 22 | }); 23 | }; -------------------------------------------------------------------------------- /app/api/index.js: -------------------------------------------------------------------------------- 1 | var methods = {}; 2 | 3 | function add_methods (name) { 4 | var module = require('./' + name); 5 | for (var x in module) methods[name + '.' + x] = module[x]; 6 | } 7 | 8 | // Add methods for each api call 9 | add_methods('achievements'); 10 | 11 | exports.init = function (io) { 12 | 13 | // Listen for API calls on each socket as they connect (ex: achievements.incr) 14 | io.sockets.on('connection', function (sock) { 15 | sock.on('api_call', function (name, args) { 16 | methods[name].apply(null, args); 17 | }); 18 | }); 19 | }; -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | // Note: Ports, directories (like the location of the UI files), and such can be 2 | /// configured through environment variables if that makes it easier. 3 | 4 | // Todo: Listen to MongoDB (not sure how it will be set up, once that's figured out we can listen for it here) 5 | 6 | var express = require('express'); 7 | var http = require('http'); 8 | var app = exports.app = express(); 9 | var server = http.createServer(app); 10 | var io = exports.io = require('socket.io').listen(server); 11 | 12 | app.use(express.static(__dirname + '/../ui')); 13 | 14 | server.listen(4444); 15 | 16 | require('./api').init(io); 17 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pine_app", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "express": "3.0.0rc3", 6 | "socket.io": "0.9.10", 7 | "mongoose": "3.0.1" 8 | }, 9 | "main": "index" 10 | } 11 | -------------------------------------------------------------------------------- /auto-pine/.gitignore: -------------------------------------------------------------------------------- 1 | *.~ 2 | *.swp 3 | *.log 4 | pine.sh 5 | -------------------------------------------------------------------------------- /auto-pine/README.md: -------------------------------------------------------------------------------- 1 | # Auto-Pine 2 | 3 | ## Dependencies 4 | 5 | * [aria2c (Cross-platform command line bittorrent client)](http://sourceforge.net/apps/trac/aria2/wiki/Download) 6 | * [QEMU](http://www.qemu.org/) 7 | 8 | 9 | ## Synopsis 10 | 11 | ### Config 12 | 13 | You'll need to configure the ip address of the NFS server if you intend to use one. Skip this step if not using NFS. 14 | 15 | ``` 16 | vim defaults/boot/cmdline.txt # change 192.168.1.1 to the IP address of your NFS server 17 | ``` 18 | 19 | ### Build 20 | 21 | If you already have *this* source image archive: 22 | 23 | [[Source](http://www.raspberrypi.org/downloads)] 2012-07-15-wheezy-raspbian.zip 440MB [[Direct](http://downloads.raspberrypi.org/images/raspbian/2012-07-15-wheezy-raspbian/2012-07-15-wheezy-raspbian.zip)] [[Torrent (Preferred)](http://downloads.raspberrypi.org/images/raspbian/2012-07-15-wheezy-raspbian/2012-07-15-wheezy-raspbian.zip.torrent)] SHA-1:`3947412babbf63f9f022f1b0b22ea6a308bb630c` 24 | 25 | Then: 26 | * Move or copy the image archive into the distro folder. It must be namedd 2012-07-15-wheezy-raspbian.zip 27 | * `sudo bin/unpack.sh` 28 | 29 | Otherwise: `sudo bin/download.sh` 30 | 31 | If you want to time the build process: 32 | 33 | ``` 34 | time sudo bin/unpack.sh 35 | 36 | # or 37 | 38 | time sudo bin/download.sh 39 | ``` 40 | 41 | ### Edit 42 | 43 | 1. `sudo ./start-edit-session.sh` 44 | 1. Edit files under `/mnt/pine-distro-loop/` 45 | 1. If using NFS, copy any files you want to keep from `/opt/pine-distro-nfs/` to `/mnt/pine-distro-loop/` 46 | 1. `sudo ./stop-edit-session.sh` 47 | 1. If using NFS: 48 | 1. `./reload-nfs.sh` 49 | 50 | ### Write to SD Card 51 | 52 | Run one (or both) of the following to generate `pine-sd.img` and/or `pine-sd-nfs.img` in the distro folder. See the [RPi Easy SD Card Setup guide](http://elinux.org/RPi_Easy_SD_Card_Setup) to learn how to write these generated image files to your SD card. 53 | 54 | ##### No NFS 55 | 56 | ``` 57 | ./mksd 58 | ``` 59 | 60 | ##### NFS 61 | 62 | ``` 63 | ./mksd-nfs 64 | ``` 65 | 66 | -------------------------------------------------------------------------------- /auto-pine/bin/boot_parts.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # sanity check 4 | if [[ $UID -ne 0 ]]; then 5 | echo "$0 must be run as root" 6 | echo "" 7 | exit 1 8 | fi 9 | 10 | #config 11 | WD="./distro" 12 | SRC_IMG="${WD}/pine.img" 13 | 14 | BOOT_BS=512 15 | BOOT_START=8192 16 | BOOT_END=122879 17 | BOOT_OFFSET=4194304 # BOOT_BS * BOOT_START 18 | 19 | IMG_BS=512 20 | IMG_START=122880 21 | IMG_END=3788799 22 | IMG_OFFSET=62914560 # IMG_BS * IMG_START 23 | 24 | BOOT_IMGDIR="/mnt/raspbian-boot-loop" # mount point for SD card boot images 25 | 26 | echo "Auto Pine 0.0" 27 | echo "Boot Images" 28 | echo "" 29 | 30 | if [ ! -f $SRC_IMG ]; then 31 | echo "No source image. run unpack.sh or download.sh again." 32 | echo "" 33 | exit 1 34 | fi 35 | 36 | if [ ! -d ${BOOT_IMGDIR} ]; then 37 | mkdir ${BOOT_IMGDIR} 38 | fi 39 | 40 | 41 | echo "extracting original boot partition" 42 | dd if=${SRC_IMG} of=${WD}/sd-boot.part bs=${BOOT_BS} seek=${BOOT_START} count=${BOOT_END} 43 | 44 | echo "" 45 | echo "mounting boot image" 46 | mount -v -o loop,offset=${BOOT_OFFSET} ${SRC_IMG} ${BOOT_IMGDIR} 47 | 48 | echo "" 49 | echo "modifying image" 50 | cp -v ./defaults/boot/cmdline.txt ${BOOT_IMGDIR}/cmdline.txt 51 | 52 | echo "" 53 | echo "finalizing image" 54 | fuser -m ${BOOT_IMGDIR} 55 | umount -v ${BOOT_IMGDIR} 56 | 57 | echo "" 58 | echo "extracting SD+NFS boot partition" 59 | dd if=${SRC_IMG} of=${WD}/sd-nfs-boot.part bs=${BOOT_BS} seek=${BOOT_START} count=${BOOT_END} 60 | 61 | echo "" 62 | echo "cleaning up mount points" 63 | rmdir ${BOOT_IMGDIR} 64 | -------------------------------------------------------------------------------- /auto-pine/bin/download.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # DEPENDS: Cross platform command line bt client: 4 | 5 | #config 6 | aria=`which aria2c` 7 | WD="./distro" 8 | SRCURL="http://downloads.raspberrypi.org/images/raspbian/2012-07-15-wheezy-raspbian/2012-07-15-wheezy-raspbian.zip.torrent" 9 | 10 | echo "Auto Pine 0.0" 11 | echo "Download" 12 | echo "" 13 | echo "Using" 14 | echo " - Aria: ${aria}" 15 | echo " - Workdir: ${WD}" 16 | echo " - Source: ${SRCURL}" 17 | echo "" 18 | 19 | if [ ! -d "${WD}" ]; then 20 | echo "creating distribution folder..." 21 | mkdir ${WD} 22 | fi 23 | 24 | echo "downloading..." 25 | ${aria} -d "${WD}" --seed-time=0 --on-bt-download-complete=./bin/unpack.sh -l "aria.log" --log-level warn ${SRCURL} 26 | -------------------------------------------------------------------------------- /auto-pine/bin/first_build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # sanity check 4 | if [[ $UID -ne 0 ]]; then 5 | echo "$0 must be run as root" 6 | exit 1 7 | fi 8 | 9 | #config 10 | WD="./distro" 11 | SRC_IMG="${WD}/2012-07-15-wheezy-raspbian.img" 12 | DST_IMG="${WD}/pine.img" 13 | 14 | IMG_BS=512 15 | IMG_START=122880 16 | IMG_END=3788799 17 | IMG_OFFSET=62914560 # IMG_BS * IMG_START 18 | 19 | PINE_IMGDIR="/mnt/pine-distro-loop" # pine filesystem mount point 20 | 21 | # all builds 22 | echo "Auto Pine 0.0" 23 | echo "First Build" 24 | echo "" 25 | 26 | if [ ! -f $SRC_IMG ]; then 27 | echo "No source image. run unpack.sh or download.sh again." 28 | echo "" 29 | exit 1 30 | fi 31 | 32 | if [ -f $DST_IMG ]; then 33 | echo "Pine image already exists, aborting. [Delete or rename ${DST_IMG} to start a fresh image]" 34 | echo "" 35 | exit 1 36 | fi 37 | 38 | if [ ! -d ${PINE_IMGDIR} ]; then 39 | mkdir ${PINE_IMGDIR} 40 | fi 41 | 42 | echo "" 43 | echo "Stage: 1 (Install deps)" 44 | echo "" 45 | 46 | echo "mounting source image" 47 | mount -v -o loop,offset=${IMG_OFFSET} ${SRC_IMG} ${PINE_IMGDIR} 48 | 49 | echo "" 50 | echo "modifying filesystem image" 51 | 52 | # stage 1 53 | mv ${PINE_IMGDIR}/etc/inittab ${PINE_IMGDIR}/etc/inittab.orig 54 | rm -v ${PINE_IMGDIR}/etc/profile.d/raspi-config.sh 55 | cp -av ./defaults/stage-1/etc/inittab ${PINE_IMGDIR}/etc/inittab 56 | cp -av ./defaults/stage-1/root/.bashrc ${PINE_IMGDIR}/root/.bashrc 57 | cp -rav ./defaults/stage-1/root/setup ${PINE_IMGDIR}/root 58 | 59 | echo "" 60 | echo "setting permissions" 61 | chown -vR root.root ${PINE_IMGDIR}/root 62 | chown -v root.root ${PINE_IMGDIR}/etc/inittab 63 | 64 | echo "" 65 | echo "finalizing filesystem image" 66 | umount -v ${PINE_IMGDIR} 67 | 68 | echo "" 69 | echo "sleep 2" 70 | sleep 2 71 | 72 | echo "" 73 | echo "creating Pine image" 74 | mv -v ${SRC_IMG} ${DST_IMG} 75 | 76 | # get boot partitions 77 | ./bin/boot_parts.sh 78 | 79 | echo "" 80 | echo "Running first boot, hda=${DST_IMG}" 81 | qemu-system-arm -kernel ./kernel-qemu -cpu arm1136-r2 -M versatilepb -no-reboot -append "root=/dev/sda2 panic=1" -hda ${DST_IMG} 82 | 83 | echo "" 84 | echo "Stage: 2 (Pine Setup)" 85 | echo "" 86 | 87 | echo "mounting source image" 88 | mount -o loop,offset=${IMG_OFFSET} ${DST_IMG} ${PINE_IMGDIR} 89 | 90 | # stage 2 91 | cp -av ./defaults/stage-2/etc/inittab ${PINE_IMGDIR}/etc/inittab 92 | cp -av ./defaults/stage-2/home/* ${PINE_IMGDIR}/home/ 93 | 94 | echo "" 95 | echo "setting permissions" 96 | chown -vR 1001:1002 ${PINE_IMGDIR}/home/pine-user 97 | 98 | echo "" 99 | echo "finalizing filesystem image" 100 | umount ${PINE_IMGDIR} 101 | 102 | echo "" 103 | echo "cleaning up mount points" 104 | rmdir ${PINE_IMGDIR} 105 | 106 | echo "" 107 | echo "All done" 108 | echo "" 109 | echo "To launch the current image:" 110 | echo "" 111 | echo "./pine.sh" 112 | 113 | echo "#!/usr/bin/env bash" > pine.sh 114 | echo "qemu-system-arm -kernel ./kernel-qemu -cpu arm1136-r2 -M versatilepb -append \"root=/dev/sda2 panic=1\" -hda ${DST_IMG}" >> pine.sh 115 | chmod 744 pine.sh 116 | 117 | -------------------------------------------------------------------------------- /auto-pine/bin/mksd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # build sd card 4 | WD="./distro" 5 | 6 | echo "preparing image file" 7 | cp ${WD}/pine.img ${WD}/pine-sd.img 8 | 9 | echo "writing boot partition" 10 | dd if=${WD}/sd-boot.part of=${WD}/pine-sd.img conv=notrunc 11 | 12 | echo "" 13 | echo "Done. You may write ${WD}/pine-sd.img to your SD card now" 14 | echo "" 15 | -------------------------------------------------------------------------------- /auto-pine/bin/mksd-nfs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | WD="./distro" 4 | 5 | echo "preparing image file" 6 | cp ${WD}/pine.img ${WD}/pine-sd-nfs.img 7 | 8 | echo "writing boot partition" 9 | dd if=${WD}/sd-nfs-boot.part of=${WD}/pine-sd-nfs.img conv=notrunc 10 | 11 | echo "" 12 | echo "Done. You may write ${WD}/pine-sd-nfs.img to your SD card now" 13 | echo "" 14 | 15 | -------------------------------------------------------------------------------- /auto-pine/bin/reload-nfs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # sanity check 4 | if [[ $UID -ne 0 ]]; then 5 | echo "$0 must be run as root" 6 | exit 1 7 | fi 8 | 9 | #config 10 | WD="./distro" 11 | SRC_IMG="${WD}/pine.img" 12 | PINE_IMGDIR="/mnt/pine-distro-loop" # pine filesystem mount point 13 | NFS_PINEDIR="/opt/pine-distro-nfs" # nfs server's image 14 | 15 | IMG_BS=512 16 | IMG_START=122880 17 | IMG_END=3788799 18 | IMG_OFFSET=62914560 # IMG_BS * IMG_START 19 | 20 | # all builds 21 | echo "Auto Pine 0.0" 22 | echo "Reload NFS" 23 | echo "" 24 | 25 | if [ ! -f $SRC_IMG ]; then 26 | echo "No source image. run unpack.sh or download.sh again." 27 | echo "" 28 | exit 1 29 | fi 30 | 31 | if [ ! -d ${PINE_IMGDIR} ]; then 32 | mkdir ${PINE_IMGDIR} 33 | fi 34 | 35 | echo "mounting source image" 36 | mount -o loop,offset=${IMG_OFFSET} ${SRC_IMG} ${PINE_IMGDIR} 37 | 38 | echo "" 39 | echo "copying Pine image" 40 | cp -rav ${PINE_IMGDIR}/* ${NFS_PINEDIR}/ 41 | 42 | echo "" 43 | echo "closing Pine image" 44 | umount ${PINE_IMGDIR} 45 | 46 | echo "" 47 | echo "cleaning up mount points" 48 | rmdir ${PINE_IMGDIR} 49 | -------------------------------------------------------------------------------- /auto-pine/bin/start-edit-session.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # sanity check 4 | if [[ $UID -ne 0 ]]; then 5 | echo "$0 must be run as root" 6 | exit 1 7 | fi 8 | 9 | #config 10 | WD="./distro" 11 | SRC_IMG="${WD}/pine.img" 12 | 13 | IMG_BS=512 14 | IMG_START=122880 15 | IMG_END=3788799 16 | IMG_OFFSET=62914560 # IMG_BS * IMG_START 17 | 18 | PINE_IMGDIR="/mnt/pine-distro-loop" # pine filesystem mount point 19 | 20 | echo "Auto Pine 0.0" 21 | echo "Start edit session" 22 | echo "" 23 | 24 | if [ ! -f $SRC_IMG ]; then 25 | echo "No source image. run unpack.sh or download.sh again." 26 | echo "" 27 | exit 1 28 | fi 29 | 30 | if [ ! -d ${PINE_IMGDIR} ]; then 31 | mkdir ${PINE_IMGDIR} 32 | fi 33 | 34 | echo "mounting source image" 35 | mount -vo loop,offset=${IMG_OFFSET} ${SRC_IMG} ${PINE_IMGDIR} 36 | 37 | echo "" 38 | echo "You may now edit the filesystem under ${PINE_IMGDIR}" 39 | echo "" 40 | echo "Don't forget to run stop-edit-session.sh in this folder when finished!" 41 | echo "" 42 | -------------------------------------------------------------------------------- /auto-pine/bin/stop-edit-session.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # sanity check 4 | if [[ $UID -ne 0 ]]; then 5 | echo "$0 must be run as root" 6 | exit 1 7 | fi 8 | 9 | #config 10 | #WD="./distro" 11 | #SRC_IMG="${WD}/pine.img" 12 | PINE_IMGDIR="/mnt/pine-distro-loop" # pine filesystem mount point 13 | 14 | # all builds 15 | echo "Auto Pine 0.0" 16 | echo "Stop Edit Session" 17 | echo "" 18 | 19 | echo "finalizing filesystem image" 20 | umount -v ${PINE_IMGDIR} 21 | 22 | echo "" 23 | echo "cleaning up mount points" 24 | rmdir ${PINE_IMGDIR} 25 | -------------------------------------------------------------------------------- /auto-pine/bin/unpack.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # config 4 | WD="./distro" 5 | 6 | BASEFN="${WD}/2012-07-15-wheezy-raspbian" 7 | ZIPFN="${BASEFN}.zip" 8 | RASPFN="${BASEFN}.img" 9 | PINEFN="${WD}/pine.img" 10 | 11 | echo "Auto Pine 0.0" 12 | echo "Unpack" 13 | echo "" 14 | echo "Using:" 15 | echo " - Workdir: ${WD}" 16 | echo " - Base: ${BASEFN}" 17 | echo " - ZIP file: ${ZIPFN}" 18 | echo " - Raspbian: ${RASPFN}" 19 | echo " - Pine: ${PINEFN}" 20 | echo "" 21 | 22 | # init 23 | echo "Checking files..." 24 | 25 | # pine image exists 26 | if [ -f "${PINEFN}" ]; then 27 | echo " - pine image exists" 28 | else 29 | echo " - pine image not found" 30 | fi 31 | 32 | # raspbian image exists 33 | if [ -f "${RASPFN}" ]; then 34 | echo " - raspbian image exists" 35 | else 36 | echo " - raspbian image not found" 37 | fi 38 | 39 | # zip exists 40 | if [ -f "${ZIPFN}" ]; then 41 | echo " - zipfile exists" 42 | else 43 | echo " - zipfile not found, aborting unpack. run ./download.sh first." 44 | echo "" 45 | exit 1 46 | fi 47 | 48 | echo "" 49 | echo "Checking SHA1 signature..." 50 | SHA1=`sha1sum ${ZIPFN}` 51 | GOOD="3947412babbf63f9f022f1b0b22ea6a308bb630c" 52 | 53 | if [ "${SHA1}"="${GOOD}" ]; then 54 | echo "SHA1 signature matches, unpacking..." 55 | unzip ${ZIPFN} -d ${WD} 56 | 57 | else 58 | echo "Bad SHA1 signature, aborting unpack." 59 | echo "" 60 | exit 1 61 | fi 62 | 63 | echo "" 64 | ./bin/first_build.sh 65 | -------------------------------------------------------------------------------- /auto-pine/defaults/boot/cmdline.txt: -------------------------------------------------------------------------------- 1 | dwc_otg.lpm_enable=0 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=tty1 root=/dev/nfs nfsroot=192.168.1.1:/opt/pine-distro-nfs ip=dhcp rootfstype=nfs 2 | -------------------------------------------------------------------------------- /auto-pine/defaults/stage-1/etc/init.d/start_pine_daemons.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Taken from: http://www.debian-administration.org/articles/28 3 | case "$1" in 4 | start) 5 | input-event-daemon 6 | echo "Running input-event-daemon" 7 | ;; 8 | stop) 9 | echo "TODO: Kill input-event-daemon" 10 | ;; 11 | *) 12 | echo "Usage: sh /etc/init.d/start_pine_daemons.sh {start|stop}" 13 | exit 1 14 | ;; 15 | esac 16 | 17 | exit 0 18 | -------------------------------------------------------------------------------- /auto-pine/defaults/stage-1/etc/inittab: -------------------------------------------------------------------------------- 1 | # /etc/inittab: init(8) configuration. 2 | # $Id: inittab,v 1.91 2002/01/25 13:35:21 miquels Exp $ 3 | 4 | # The default runlevel. 5 | id:2:initdefault: 6 | 7 | # Boot-time system configuration/initialization script. 8 | # This is run first except when booting in emergency (-b) mode. 9 | si::sysinit:/etc/init.d/rcS 10 | 11 | # What to do in single-user mode. 12 | ~~:S:wait:/sbin/sulogin 13 | 14 | # /etc/init.d executes the S and K scripts upon change 15 | # of runlevel. 16 | # 17 | # Runlevel 0 is halt. 18 | # Runlevel 1 is single-user. 19 | # Runlevels 2-5 are multi-user. 20 | # Runlevel 6 is reboot. 21 | 22 | l0:0:wait:/etc/init.d/rc 0 23 | l1:1:wait:/etc/init.d/rc 1 24 | l2:2:wait:/etc/init.d/rc 2 25 | l3:3:wait:/etc/init.d/rc 3 26 | l4:4:wait:/etc/init.d/rc 4 27 | l5:5:wait:/etc/init.d/rc 5 28 | l6:6:wait:/etc/init.d/rc 6 29 | # Normally not reached, but fallthrough in case of emergency. 30 | z6:6:respawn:/sbin/sulogin 31 | 32 | # What to do when CTRL-ALT-DEL is pressed. 33 | ca:12345:ctrlaltdel:/sbin/shutdown -t1 -a -r now 34 | 35 | # Action on special keypress (ALT-UpArrow). 36 | #kb::kbrequest:/bin/echo "Keyboard Request--edit /etc/inittab to let this work." 37 | 38 | # What to do when the power fails/returns. 39 | pf::powerwait:/etc/init.d/powerfail start 40 | pn::powerfailnow:/etc/init.d/powerfail now 41 | po::powerokwait:/etc/init.d/powerfail stop 42 | 43 | # /sbin/getty invocations for the runlevels. 44 | # 45 | # The "id" field MUST be the same as the last 46 | # characters of the device (after "tty"). 47 | # 48 | # Format: 49 | # ::: 50 | # 51 | # Note that on most Debian systems tty7 is used by the X Window System, 52 | # so if you want to add more getty's go ahead but skip tty7 if you run X. 53 | # 54 | #1:2345:respawn:/sbin/getty --noclear 38400 tty1 # RPICFG_TO_ENABLE 55 | 1:2345:respawn:/bin/login -f root tty1 /dev/tty1 2>&1 # RPICFG_TO_DISABLE 56 | 2:23:respawn:/sbin/getty 38400 tty2 57 | 3:23:respawn:/sbin/getty 38400 tty3 58 | 4:23:respawn:/sbin/getty 38400 tty4 59 | 5:23:respawn:/sbin/getty 38400 tty5 60 | 6:23:respawn:/sbin/getty 38400 tty6 61 | 62 | # Example how to put a getty on a serial line (for a terminal) 63 | # 64 | #T0:23:respawn:/sbin/getty -L ttyS0 9600 vt100 65 | #T1:23:respawn:/sbin/getty -L ttyS1 9600 vt100 66 | 67 | # Example how to put a getty on a modem line. 68 | # 69 | #T3:23:respawn:/sbin/mgetty -x0 -s 57600 ttyS3 70 | 71 | 72 | #Spawn a getty on Raspberry Pi serial line 73 | T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100 74 | -------------------------------------------------------------------------------- /auto-pine/defaults/stage-1/root/.bashrc: -------------------------------------------------------------------------------- 1 | /bin/bash /root/setup/pine_setup.sh 2 | -------------------------------------------------------------------------------- /auto-pine/defaults/stage-1/root/setup/pine_setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Run in Raspbian. 4 | # RUN THIS AS ROOT. 5 | # 6 | # Sets up Raspbian environment and gets packages for Pine. 7 | 8 | if [[ $UID -ne 0 ]]; then 9 | echo "$0 must be run as root" 10 | exit 1 11 | fi 12 | 13 | 14 | ####### SET UP SOURCES 15 | echo 16 | echo "*** Stage 1 ***" 17 | echo 18 | 19 | echo \ 20 | "deb http://archive.raspbian.org/raspbian wheezy main contrib non-free rpi 21 | deb-src http://archive.raspbian.org/raspbian wheezy main contrib non-free rpi 22 | deb http://archive.raspbian.org/mate wheezy main" \ 23 | | cat > /etc/apt/sources.list 24 | 25 | # Add GPG key 26 | wget http://archive.raspbian.org/raspbian.public.key -O - | sudo apt-key add - 27 | 28 | apt-get update 29 | 30 | 31 | ####### GET DEPENDENCIES 32 | echo 33 | echo "*** Stage 2 ***" 34 | echo 35 | 36 | apt-get --yes --force-yes install curl xorg nodejs openssh-server git-core vim 37 | 38 | # Install rpi-update (https://github.com/Hexxeh/rpi-update/) 39 | wget http://goo.gl/1BOfJ -O /usr/bin/rpi-update && chmod +x /usr/bin/rpi-update 40 | 41 | # Install input-event-daemon 42 | git clone git://github.com/gandro/input-event-daemon.git /usr/local/lib/input-event-daemon 43 | cd /usr/local/lib/input-event-daemon 44 | make 45 | make install 46 | 47 | 48 | ####### INSTALL CHROMIUM 49 | echo 50 | echo "*** Stage 3 ***" 51 | echo 52 | 53 | # Install Hexxeh's Chromium. (http://hexxeh.net/?p=328117859) 54 | #bash <(curl -sL http://goo.gl/go5yx) # BROKEN 55 | 56 | ### COPY OF install_chromium.sh STARTS ### 57 | # From http://hexxeh.net/?p=328117859 58 | sudo apt-get install -y --force-yes libnss3 libxrender1 libxss1 libgtk2.0-0 libgconf2-4 59 | sudo mkdir -p /opt/google/ 60 | cd /opt/google 61 | sudo wget http://distribution-us.hexxeh.net/chromium-rpi/chromium-rpi-r22.tar.gz -O chromium-rpi.tar.gz 62 | sudo tar xvf chromium-rpi.tar.gz 63 | sudo chown root:root chrome/chrome-sandbox 64 | sudo chmod 4755 chrome/chrome-sandbox 65 | sudo rm -rf /usr/bin/chrome 66 | sudo ln -s /opt/google/chrome/chrome /usr/bin/chrome 67 | ### COPY OF install_chromium.sh ENDS ### 68 | 69 | ####### SSH 70 | echo 71 | echo "*** Stage 4 ***" 72 | echo 73 | 74 | # Make a safe backup. 75 | cp /etc/ssh/sshd_config /etc/ssh/sshd_config.original 76 | chmod a-w /etc/ssh/sshd_config.original 77 | 78 | 79 | ####### PERFORMANCE HACKS 80 | echo 81 | echo "*** Stage 5 ***" 82 | echo 83 | 84 | # Tweak the RPi memory allocation. Reboot for this to take effect. 85 | rpi-update 224 86 | 87 | 88 | ####### PINE USER CREATION 89 | echo 90 | echo "*** Stage 6 ***" 91 | echo 92 | 93 | # Create the user "pine-user" 94 | useradd -m -s /bin/bash pine-user 95 | 96 | # Files copied in via auto-pine stage 2 97 | 98 | ####### BOOTUP 99 | echo 100 | echo "*** Stage 7 ***" 101 | echo 102 | 103 | # At bootup, login as pine-user without a password prompt. 104 | # http://www.debianadmin.com/how-to-auto-login-and-startx-without-a-display-manager-in-debian.html 105 | # It looks fancy. It's just a find-and-replace. 106 | cp /etc/inittab /etc/inittab.original 107 | 108 | #sed -i 's/1:2345:respawn:\/sbin\/getty 38400 tty1/#1:2345:respawn:\/sbin\/getty 38400 tty1\n1:2345:respawn:\/bin\/login -f pine-user tty1 <\/dev\/tty1 >\/dev\/tty1 2>\&1/' /etc/inittab 109 | #perl -pi -e 's/1:2345:respawn:\/sbin\/getty 38400 tty1/#1:2345:respawn:\/sbin\/getty 38400 tty1\n1:2345:respawn:\/bin\/login -f pine-user tty1 <\/dev\/tty1 >\/dev\/tty1 2>\&1/' /etc/inittab 110 | 111 | ####### INPUT DAEMON 112 | 113 | echo 114 | echo "*** Stage 8 ***" 115 | echo 116 | 117 | echo \ 118 | "[Global] 119 | listen = /dev/input/event0 120 | listen = /dev/input/event1 121 | 122 | [Keys] 123 | ESC = sh /home/pine-user/kill_chrome.sh" \ 124 | | cat > /etc/input-event-daemon.conf 125 | 126 | cd /etc/init.d/ 127 | chmod 755 start_pine_daemons.sh 128 | update-rc.d start_pine_daemons.sh defaults 129 | 130 | echo 131 | echo "*** FINISHED***" 132 | echo "Reboot to run Pine" 133 | echo 134 | reboot 135 | -------------------------------------------------------------------------------- /auto-pine/defaults/stage-2/etc/inittab: -------------------------------------------------------------------------------- 1 | # /etc/inittab: init(8) configuration. 2 | # $Id: inittab,v 1.91 2002/01/25 13:35:21 miquels Exp $ 3 | 4 | # The default runlevel. 5 | id:2:initdefault: 6 | 7 | # Boot-time system configuration/initialization script. 8 | # This is run first except when booting in emergency (-b) mode. 9 | si::sysinit:/etc/init.d/rcS 10 | 11 | # What to do in single-user mode. 12 | ~~:S:wait:/sbin/sulogin 13 | 14 | # /etc/init.d executes the S and K scripts upon change 15 | # of runlevel. 16 | # 17 | # Runlevel 0 is halt. 18 | # Runlevel 1 is single-user. 19 | # Runlevels 2-5 are multi-user. 20 | # Runlevel 6 is reboot. 21 | 22 | l0:0:wait:/etc/init.d/rc 0 23 | l1:1:wait:/etc/init.d/rc 1 24 | l2:2:wait:/etc/init.d/rc 2 25 | l3:3:wait:/etc/init.d/rc 3 26 | l4:4:wait:/etc/init.d/rc 4 27 | l5:5:wait:/etc/init.d/rc 5 28 | l6:6:wait:/etc/init.d/rc 6 29 | # Normally not reached, but fallthrough in case of emergency. 30 | z6:6:respawn:/sbin/sulogin 31 | 32 | # What to do when CTRL-ALT-DEL is pressed. 33 | ca:12345:ctrlaltdel:/sbin/shutdown -t1 -a -r now 34 | 35 | # Action on special keypress (ALT-UpArrow). 36 | #kb::kbrequest:/bin/echo "Keyboard Request--edit /etc/inittab to let this work." 37 | 38 | # What to do when the power fails/returns. 39 | pf::powerwait:/etc/init.d/powerfail start 40 | pn::powerfailnow:/etc/init.d/powerfail now 41 | po::powerokwait:/etc/init.d/powerfail stop 42 | 43 | # /sbin/getty invocations for the runlevels. 44 | # 45 | # The "id" field MUST be the same as the last 46 | # characters of the device (after "tty"). 47 | # 48 | # Format: 49 | # ::: 50 | # 51 | # Note that on most Debian systems tty7 is used by the X Window System, 52 | # so if you want to add more getty's go ahead but skip tty7 if you run X. 53 | # 54 | #1:2345:respawn:/sbin/getty --noclear 38400 tty1 # RPICFG_TO_ENABLE 55 | 1:2345:respawn:/bin/login -f pine-user tty1 /dev/tty1 2>&1 # RPICFG_TO_DISABLE 56 | 2:23:respawn:/sbin/getty 38400 tty2 57 | 3:23:respawn:/sbin/getty 38400 tty3 58 | 4:23:respawn:/sbin/getty 38400 tty4 59 | 5:23:respawn:/sbin/getty 38400 tty5 60 | 6:23:respawn:/sbin/getty 38400 tty6 61 | 62 | # Example how to put a getty on a serial line (for a terminal) 63 | # 64 | #T0:23:respawn:/sbin/getty -L ttyS0 9600 vt100 65 | #T1:23:respawn:/sbin/getty -L ttyS1 9600 vt100 66 | 67 | # Example how to put a getty on a modem line. 68 | # 69 | #T3:23:respawn:/sbin/mgetty -x0 -s 57600 ttyS3 70 | 71 | 72 | #Spawn a getty on Raspberry Pi serial line 73 | T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100 74 | -------------------------------------------------------------------------------- /auto-pine/defaults/stage-2/home/pine-user/.bashrc: -------------------------------------------------------------------------------- 1 | node pine.js & startx /opt/google/chrome/chrome --kiosk --disable-ipv6 --window-size=640,480 http://127.0.0.1:4444/ #VGA 2 | #node pine.js & startx /opt/google/chrome/chrome --kiosk --disable-ipv6 --window-size=1280,720 http://127.0.0.1:4444/ #720p 3 | #node pine.js & startx /opt/google/chrome/chrome --kiosk --disable-ipv6 --window-size=1600,900 http://127.0.0.1:4444/ #900p 4 | #node pine.js & startx /opt/google/chrome/chrome --kiosk --disable-ipv6 --window-size=1920,1080 http://127.0.0.1:4444/ #1080i,1080p 5 | -------------------------------------------------------------------------------- /auto-pine/defaults/stage-2/home/pine-user/htdocs/css/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psema4/pine/a037a28933ecb307cb11ef60596b6ee9519b1879/auto-pine/defaults/stage-2/home/pine-user/htdocs/css/app.css -------------------------------------------------------------------------------- /auto-pine/defaults/stage-2/home/pine-user/htdocs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Welcome To Pine 5 | 6 | 7 | 8 | 9 | 10 |

Welcome to Pine

11 | 12 | 13 | -------------------------------------------------------------------------------- /auto-pine/defaults/stage-2/home/pine-user/htdocs/js/app.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psema4/pine/a037a28933ecb307cb11ef60596b6ee9519b1879/auto-pine/defaults/stage-2/home/pine-user/htdocs/js/app.js -------------------------------------------------------------------------------- /auto-pine/defaults/stage-2/home/pine-user/kill_chrome.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | # Kills Chrome 4 | # http://www.commandlinefu.com/commands/view/1138/ps-ef-grep-process-grep-v-grep-awk-print-2-xargs-kill-9 5 | ps aux | grep xinit.*chrome | awk '{print $2}'| xargs kill; 6 | -------------------------------------------------------------------------------- /auto-pine/defaults/stage-2/home/pine-user/pine.js: -------------------------------------------------------------------------------- 1 | var debug = false, 2 | http = require('http'), 3 | fs = require('fs'), 4 | util = require('util'), 5 | server = { 6 | host: '127.0.0.1', 7 | port: 4444 8 | }, 9 | defaultMimeType = 'txt', 10 | mimeTypes = { 11 | html: 'text/html', 12 | txt: 'text/plain', 13 | js: 'text/javascript', 14 | css: 'text/css', 15 | jpg: 'image/jpeg', 16 | gif: 'image/gif', 17 | png: 'image/png' 18 | } 19 | ; 20 | 21 | http.createServer(function (req, res) { 22 | if (debug) { 23 | console.log('dumping request:'); 24 | console.log(util.inspect(req)); 25 | } 26 | 27 | var resource = req.url; 28 | if (resource == '/') resource = '/index.html'; 29 | 30 | var extension = resource.split('.').pop(); 31 | 32 | if (! mimeTypes[extension]) extension = defaultMimeType; 33 | 34 | fs.readFile('./htdocs' + resource, function (err, data) { 35 | if (err) { 36 | res.writeHead(500, {'Content-Type': mimeTypes.txt}); 37 | res.end('Error\n'); 38 | console.log(err); 39 | } else { 40 | res.writeHead(200, {'Content-Type': mimeTypes[extension]}); 41 | res.end(data); 42 | } 43 | }); 44 | }).listen(server.port, server.host); 45 | 46 | console.log('Server running at http://' + server.host + ':' + server.port + '/'); 47 | -------------------------------------------------------------------------------- /auto-pine/distro/.gitignore: -------------------------------------------------------------------------------- 1 | *.img 2 | *.part 3 | *.zip 4 | *.torrent 5 | *.~ 6 | *.swp 7 | -------------------------------------------------------------------------------- /auto-pine/distro/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psema4/pine/a037a28933ecb307cb11ef60596b6ee9519b1879/auto-pine/distro/README.md -------------------------------------------------------------------------------- /auto-pine/distro/clean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # destroy existing pine images 4 | rm -f *.img 5 | rm -f *.part 6 | 7 | if [ -f ../pine.sh ]; then 8 | rm ../pine.sh 9 | fi 10 | 11 | echo "run unpack to create a new pine distro" 12 | -------------------------------------------------------------------------------- /auto-pine/kernel-qemu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psema4/pine/a037a28933ecb307cb11ef60596b6ee9519b1879/auto-pine/kernel-qemu -------------------------------------------------------------------------------- /ui/README.md: -------------------------------------------------------------------------------- 1 | # UI 2 | 3 | To play with the Pine UI the UI in your desktop browser, you need to start the 4 | test server: 5 | 6 | ```` 7 | $: node dev-server.js 8 | ```` 9 | 10 | And navigate to `http://localhost:5000/` in your browser. 11 | -------------------------------------------------------------------------------- /ui/dev-server.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var express = require('express'); 3 | var mustache = require('mustache'); 4 | var packageMeta = require(__dirname + '/lib/package-meta.js'); 5 | 6 | 7 | var server = express(); 8 | 9 | server.get('/', function (req, res) { 10 | var template = fs.readFileSync(__dirname + '/index.html', 'utf8'); 11 | var packageData = { 12 | 'packageList': packageMeta.getPackageList() 13 | }; 14 | var html = mustache.to_html(template, packageData); 15 | res.send(html); 16 | }); 17 | 18 | server.use('/public', express.static(__dirname + '/public')); 19 | server.use('/packages', express.static(__dirname + '/packages')); 20 | server.use('/api', express.static(__dirname + '/../api')); 21 | 22 | server.listen(5000); 23 | 24 | console.log('Pine dev server is now listening on port 5000.'); 25 | console.log('Press ctrl+c to kill the server.'); 26 | -------------------------------------------------------------------------------- /ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Pine 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |

Hello. Welcome to Pine.

16 | 63 |
64 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /ui/lib/package-meta.js: -------------------------------------------------------------------------------- 1 | require('shelljs/global'); 2 | 3 | var fs = require('fs'); 4 | var jsonParse = require('json-parse'); 5 | var _ = require('underscore'); 6 | 7 | 8 | var PACKAGE_DIRECTORY = 'packages'; 9 | var JSON_FILE_NAME = 'pine-package.json'; 10 | 11 | function getJsonList () { 12 | var jsonList = []; 13 | var packageList = ls(PACKAGE_DIRECTORY); 14 | 15 | packageList.forEach(function (packageDir) { 16 | var jsonPath = [PACKAGE_DIRECTORY, packageDir, JSON_FILE_NAME].join('/'); 17 | 18 | // "-e" tests for path existence. 19 | if (test('-e', jsonPath)) { 20 | jsonList.push(jsonPath); 21 | } 22 | }); 23 | 24 | return jsonList; 25 | } 26 | 27 | 28 | function getPackageData () { 29 | var jsonList = getJsonList(); 30 | var packageData = {}; 31 | 32 | jsonList.forEach(function (jsonPath) { 33 | try { 34 | packageData[jsonPath] = jsonParse.sync(jsonPath); 35 | _.extend(packageData[jsonPath], { 36 | 'path': jsonPath.split('/').slice(0, -1).join('/') 37 | }); 38 | } catch (ex) { 39 | console.log(ex); 40 | } 41 | }); 42 | 43 | return packageData; 44 | } 45 | 46 | 47 | function getPackageList () { 48 | var packageData = getPackageData(); 49 | var packageKeys = _.keys(packageData); 50 | var sortedKeys = packageKeys.sort(); 51 | var packageList = []; 52 | 53 | sortedKeys.forEach(function (package) { 54 | packageList.push(packageData[package]); 55 | }); 56 | 57 | return packageList; 58 | } 59 | 60 | 61 | exports.getPackageList = getPackageList; 62 | -------------------------------------------------------------------------------- /ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pine-ui", 3 | "version": "0.0.1", 4 | "description": "The user interface for Pine", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/jeremyckahn/pine.git" 11 | }, 12 | "devDependencies": { 13 | "express": "*", 14 | "shelljs": "*", 15 | "json-parse": "*", 16 | "underscore": "*", 17 | "mustache": "*" 18 | }, 19 | "author": "Jeremy Kahn" 20 | } 21 | -------------------------------------------------------------------------------- /ui/packages/benchmark/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Pine 6 | 7 | 8 | 9 | 10 | 11 | 40 |

Pine performance test

41 |
42 |
43 |
Frames per second:
44 |
45 |
46 |
47 |
Squares
48 |
0
49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /ui/packages/benchmark/js/src/benchmark.js: -------------------------------------------------------------------------------- 1 | ;(function (global) { 2 | 3 | 'use strict'; 4 | 5 | 6 | var onTick = function () {}; 7 | var docEl = document.documentElement; 8 | var testName = document.getElementById('test-name'); 9 | var framerateOutput = document.getElementById('framerate-output'); 10 | var squareCountOutput = document.getElementById('square-count-output'); 11 | var squareCountOutputDD = squareCountOutput.querySelector('dd'); 12 | var scratch = document.getElementById('scratch'); 13 | 14 | var framerate = new Framerate(); 15 | framerate.onUpdate = function (rate) { 16 | framerateOutput.textContent = rate; 17 | }; 18 | 19 | // Set up event listeners if we are running the game directly (not via the 20 | // Pine interface). 21 | if (pine.env.isDev) { 22 | ['keydown', 'keyup'].forEach(function (eventName) { 23 | docEl.addEventListener(eventName, function (evt) { 24 | if (evt.which === 8) { // backspace 25 | evt.preventDefault(); 26 | } 27 | }); 28 | }); 29 | } 30 | 31 | 32 | // HELPER FUNCTIONS 33 | // 34 | 35 | 36 | function tick () { 37 | webkitRequestAnimationFrame(tick); 38 | onTick(); 39 | framerate.tick(); 40 | } 41 | 42 | 43 | /** 44 | * @return {Array.} 45 | */ 46 | function getChildren (parent) { 47 | var children = parent.children; 48 | return Array.prototype.slice.call(children, 0); 49 | } 50 | 51 | 52 | /** 53 | * @return {string} 54 | */ 55 | function getRandomColorString () { 56 | return 'rgb(' 57 | + Math.floor(Math.random() * 256) + ',' 58 | + Math.floor(Math.random() * 256) + ',' 59 | + Math.floor(Math.random() * 256) + ')'; 60 | } 61 | 62 | 63 | // BENCHMARK HELPERS 64 | // 65 | 66 | 67 | function updateSquareCount () { 68 | squareCountOutputDD.textContent = getChildren(scratch).length; 69 | } 70 | 71 | 72 | function addRotatingDiv () { 73 | var div = document.createElement('div'); 74 | div.classList.add('js-rotator'); 75 | div.style.backgroundColor = getRandomColorString(); 76 | div.style.left = (Math.random() * 90) + '%'; 77 | div.style.top = (Math.random() * 90) + '%'; 78 | div.style.webkitTransform = 'rotate(0deg)'; 79 | scratch.appendChild(div); 80 | var childCount = getChildren(scratch).length; 81 | updateSquareCount(); 82 | } 83 | 84 | 85 | function removeRotatingDiv () { 86 | var children = getChildren(scratch); 87 | 88 | if (children.length) { 89 | var lastChild = children[children.length - 1]; 90 | scratch.removeChild(lastChild); 91 | updateSquareCount(); 92 | } 93 | } 94 | 95 | 96 | function rotatingDivsBenchmarkOnTick () { 97 | if (pine.gamepad.downKeys.R1) { 98 | addRotatingDiv(); 99 | } 100 | 101 | if (pine.gamepad.downKeys.L1) { 102 | removeRotatingDiv(); 103 | } 104 | 105 | var childrenArr = getChildren(scratch); 106 | childrenArr.forEach(function (child) { 107 | var currentTransform = child.style.webkitTransform; 108 | var currentRotation = +currentTransform.match(/\d+/)[0]; 109 | var newRotation = ++currentRotation % 360; 110 | child.style.webkitTransform = 'rotate(' + newRotation + 'deg)'; 111 | }); 112 | } 113 | 114 | 115 | // BENCHMARKS 116 | // 117 | 118 | function runRotatingDivsBenchmark () { 119 | docEl.addEventListener('keydown', function (evt) { 120 | var keycode = evt.which; 121 | if (keycode === 32) { // space 122 | addRotatingDiv(); 123 | } else if (keycode === 8) { // backspace 124 | removeRotatingDiv(); 125 | } 126 | }); 127 | 128 | onTick = rotatingDivsBenchmarkOnTick; 129 | squareCountOutput.style.display = 'block'; 130 | } 131 | 132 | 133 | // Test init logic 134 | testName.textContent += ' - Rotating Divs'; 135 | runRotatingDivsBenchmark(); 136 | tick(); 137 | 138 | } (this)); 139 | -------------------------------------------------------------------------------- /ui/packages/benchmark/js/src/framerate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Framerate.js 3 | * 4 | * A minimalistic framerate monitor in JavaScript. 5 | * by Jeremy Kahn (jeremyckahn@gmail.com) 6 | * MIT License. 7 | * 8 | * Usage: 9 | * 10 | * var framerate = new Framerate(); 11 | * framerate.onUpdate = function (rate) { 12 | * console.log(rate); 13 | * }; 14 | * 15 | * 16 | * And then, somewhere in your application code, do this once per animation 17 | * frame: 18 | * 19 | * framerate.tick(); 20 | */ 21 | ;(function (global) { 22 | 23 | 'use strict'; 24 | 25 | 26 | function noop () {} 27 | 28 | 29 | /** 30 | * @return {number} 31 | */ 32 | function now () { 33 | return +new Date(); 34 | } 35 | 36 | 37 | /** 38 | * @param {Framerate} framerate 39 | * @param {number} newEpoch 40 | */ 41 | function recycle (framerate, newEpoch) { 42 | framerate._ticksAccumulated = 0; 43 | framerate._cycleEpoch = newEpoch; 44 | } 45 | 46 | 47 | /** 48 | * @param {Framerate} framerate 49 | */ 50 | function calculateFramerate (framerate) { 51 | var oldEpoch = framerate._cycleEpoch; 52 | var newEpoch = now(); 53 | var delta = newEpoch - oldEpoch; 54 | var scaledDelta = delta / 1000; 55 | framerate.rate = framerate._ticksAccumulated / scaledDelta; 56 | recycle(framerate, newEpoch); 57 | } 58 | 59 | 60 | /** 61 | * @param {Framerate} framerate 62 | */ 63 | function scheduleRecalculation (framerate) { 64 | framerate._updateHandle = setTimeout(function () { 65 | scheduleRecalculation(framerate); 66 | calculateFramerate(framerate); 67 | framerate.onUpdate(framerate.rate); 68 | }, framerate._sampleRate); 69 | } 70 | 71 | 72 | /** 73 | * @param {Object?} opts 74 | * @param {number} sampleRate 75 | * @constructor 76 | */ 77 | var Framerate = global.Framerate = function (opts) { 78 | opts = opts || {}; 79 | 80 | /** @type {number} */ 81 | this._ticksAccumulated = 0; 82 | 83 | /** @type {number} */ 84 | this._sampleRate = opts.sampleRate || 500; 85 | 86 | /** @type {number} */ 87 | this._cycleEpoch = now(); 88 | 89 | /** @type {number} */ 90 | this._updateHandle = null; 91 | 92 | /** @type {number} */ 93 | this.rate = null; 94 | 95 | /** @type {function} */ 96 | this.onUpdate = noop; 97 | 98 | scheduleRecalculation(this); 99 | }; 100 | 101 | 102 | var fn = Framerate.prototype; 103 | 104 | 105 | fn.tick = function () { 106 | this._ticksAccumulated++; 107 | }; 108 | 109 | } (this)); 110 | -------------------------------------------------------------------------------- /ui/packages/benchmark/pine-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Rotation Benchmark", 3 | "version": "0.0.1" 4 | } 5 | -------------------------------------------------------------------------------- /ui/packages/gamepad/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Gamepad API test 5 | 6 | 7 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /ui/packages/gamepad/pine-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Gamepad Test", 3 | "version": "0.0.1" 4 | } 5 | -------------------------------------------------------------------------------- /ui/packages/sample_game/css/game.css: -------------------------------------------------------------------------------- 1 | #game-container { 2 | padding: 20px; 3 | } 4 | -------------------------------------------------------------------------------- /ui/packages/sample_game/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Pine 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |

Hi! I'm a sample game!

15 |
16 |
Up:
17 |
18 |
Down:
19 |
20 |
Left:
21 |
22 |
Right:
23 |
24 |
Start:
25 |
26 |
Select:
27 |
28 |
B:
29 |
30 |
A:
31 |
32 |
Y:
33 |
34 |
X:
35 |
36 |
L1:
37 |
38 |
R1:
39 |
40 |
L2:
41 |
42 |
R2:
43 |
44 |
L3:
45 |
46 |
R3:
47 |
48 |
49 |
50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /ui/packages/sample_game/js/src/key-input-demo.js: -------------------------------------------------------------------------------- 1 | ;(function (global, pine) { 2 | var gamepadButtonDts = document.querySelectorAll('#gamepad-button-list dt'); 3 | var dtsArray = Array.prototype.slice.call(gamepadButtonDts, 0); 4 | 5 | (function tick () { 6 | global.webkitRequestAnimationFrame(tick); 7 | 8 | dtsArray.forEach(function (buttonDt) { 9 | buttonDt.nextElementSibling.textContent = 10 | (buttonDt.dataset.buttonname in pine.gamepad.downKeys) 11 | ? 'Down' 12 | : 'Up'; 13 | }); 14 | }()); 15 | 16 | } (this, this.pine)); 17 | -------------------------------------------------------------------------------- /ui/packages/sample_game/pine-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Sample Game", 3 | "version": "0.0.1" 4 | } 5 | -------------------------------------------------------------------------------- /ui/public/css/pine-animations.css: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes main-menu-selector-ring { 2 | 0% { -webkit-box-shadow: inset 0 0 12px 2px #008eff; } 3 | 100% { -webkit-box-shadow: inset 0 0 24px 2px #008eff; } 4 | } 5 | -------------------------------------------------------------------------------- /ui/public/css/pine.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Gradients from: http://www.colorzilla.com/gradient-editor/ 3 | * Box shadows from: http://css3gen.com/box-shadow/ 4 | **/ 5 | h1.welcome { 6 | margin-bottom: 40px; 7 | text-align: center; 8 | } 9 | 10 | ul { 11 | list-style: none; 12 | margin: 0; 13 | } 14 | 15 | #pine-container { 16 | padding-top: 30px; 17 | } 18 | 19 | /** GAME CONTAINER STYLES START */ 20 | #game-container { 21 | visibility: hidden; 22 | position: fixed; 23 | height: 100%; 24 | left: -9999em; 25 | opacity: 0; 26 | top: 0; 27 | width: 100%; 28 | -webkit-transition: opacity; 29 | -webkit-transition-duration: 400ms; 30 | } 31 | 32 | #game-container.playing { 33 | visibility: visible; 34 | left: 0; 35 | opacity: 1; 36 | } 37 | 38 | /* This must come after #game-container.playing */ 39 | #game-container.exiting { 40 | opacity: 0; 41 | } 42 | 43 | #game-container .input-proxy { 44 | height: inherit; 45 | position: absolute; 46 | left: 0; 47 | top: 0; 48 | width: inherit; 49 | } 50 | 51 | #game-container .input-proxy .key-proxy { 52 | position: absolute; 53 | left: -9999em; 54 | } 55 | 56 | #game-container iframe { 57 | border: none; 58 | height: inherit; 59 | width: inherit; 60 | } 61 | /** GAME CONTAINER STYLES END */ 62 | 63 | /** MENU STYLES START */ 64 | .main-menu { 65 | margin: auto 0; 66 | overflow: hidden; 67 | vertical-align: top; 68 | } 69 | 70 | .main-menu .main-menu-btn-array { 71 | margin: 0 auto; 72 | overflow: auto; 73 | position: relative; 74 | width: 960px; 75 | } 76 | 77 | .main-menu .main-menu-btn-array li { 78 | float: left; 79 | margin: 0 25px 35px; 80 | } 81 | 82 | .main-menu.wide .main-menu-btn-array li { 83 | margin-left: 0; 84 | margin-right: 0; 85 | width: 100%; 86 | } 87 | 88 | .main-menu .main-menu-btn { 89 | border-radius: 30px; 90 | font-size: 34px; 91 | height: 120px; 92 | line-height: 1.25em; 93 | width: 260px; 94 | } 95 | 96 | .main-menu.wide .main-menu-btn { 97 | width: 100%; 98 | } 99 | 100 | .main-menu .main-menu-btn.selected, 101 | .main-menu .main-menu-btn:focus { 102 | outline: none; 103 | -webkit-box-shadow: inset 0 0 12px 2px #008eff; 104 | -webkit-animation-direction: alternate; 105 | -webkit-animation-duration: 1800ms; 106 | -webkit-animation-iteration-count: infinite; 107 | -webkit-animation-name: main-menu-selector-ring; 108 | -webkit-animation-timing-function: cubic-bezier(0.455, 0.030, 0.515, 0.955); 109 | } 110 | 111 | .menu-pager { 112 | margin: 0 auto; 113 | overflow: hidden; 114 | position: relative; 115 | width: 960px; 116 | } 117 | 118 | .menu-pager .menu-pager-rail { 119 | position: relative; 120 | -webkit-transition: left; 121 | -webkit-transition-duration: 350ms; 122 | } 123 | 124 | #main-menus { 125 | opacity: 0; 126 | } 127 | 128 | .loaded #main-menus { 129 | opacity: 1; 130 | -webkit-transition: opacity; 131 | -webkit-transition-duration: 750ms; 132 | } 133 | /** MENU STYLES END */ 134 | 135 | /** KEYBOARD STYLES START */ 136 | .pine-keyboard-scrim { 137 | background: #333; 138 | bottom: 0; 139 | left: 0; 140 | position: absolute; 141 | opacity: .5; 142 | right: 0; 143 | top: 0; 144 | } 145 | 146 | .pine-keyboard-textarea-container { 147 | font-size: 1.4em; 148 | left: 0; 149 | position: absolute; 150 | top: 30%; 151 | width: 100%; 152 | } 153 | 154 | .pine-keyboard-textarea-container textarea{ 155 | display: block; 156 | font-size: inherit; 157 | height: 4.2em; 158 | margin: 0 auto; 159 | } 160 | 161 | .pine-keyboard-layout-grid { 162 | } 163 | 164 | .pine-keyboard-layout-grid table { 165 | border-radius: 5px; 166 | font-family: courier; 167 | margin: 20px auto 0; 168 | padding: 14px; 169 | text-align: center; 170 | 171 | background: -moz-linear-gradient(top, #f0f9ff 0%, #d3d3d3 100%); /* FF3.6+ */ 172 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f0f9ff), color-stop(100%,#d3d3d3)); /* Chrome,Safari4+ */ 173 | background: -webkit-linear-gradient(top, #f0f9ff 0%,#d3d3d3 100%); /* Chrome10+,Safari5.1+ */ 174 | background: -o-linear-gradient(top, #f0f9ff 0%,#d3d3d3 100%); /* Opera 11.10+ */ 175 | background: -ms-linear-gradient(top, #f0f9ff 0%,#d3d3d3 100%); /* IE10+ */ 176 | background: linear-gradient(to bottom, #f0f9ff 0%,#d3d3d3 100%); /* W3C */ 177 | 178 | -webkit-box-shadow: 1px 0px 20px rgba(50, 50, 50, 0.8); 179 | -moz-box-shadow: 1px 0px 20px rgba(50, 50, 50, 0.8); 180 | box-shadow: 1px 0px 20px rgba(50, 50, 50, 0.8); 181 | } 182 | 183 | .pine-keyboard-selected-key { 184 | background: rgba(0, 200, 255, 1); 185 | 186 | -webkit-box-shadow: 0px 0px 3px 3px rgba(0, 200, 255, 1); 187 | -moz-box-shadow: 0px 0px 3px 3px rgba(0, 200, 255, 1); 188 | box-shadow: 0px 0px 3px 3px rgba(0, 200, 255, 1); 189 | } 190 | 191 | /** KEYBOARD STYLES END */ 192 | 193 | /** DEBUG STYLES START */ 194 | #debug-footer { 195 | bottom: 0; 196 | padding-left: 8px; 197 | position: fixed; 198 | left: 0; 199 | } 200 | 201 | #debug-footer .debug-modal { 202 | display: none; 203 | } 204 | 205 | #debug-footer-trigger:hover + .debug-modal { 206 | display: block; 207 | } 208 | 209 | .debug-modal { 210 | background: #000; 211 | border-radius: 6px; 212 | bottom: 5%; 213 | color: #fff; 214 | left: 5%; 215 | opacity: .95; 216 | padding: 30px; 217 | position: fixed; 218 | right: 5%; 219 | top: 5%; 220 | } 221 | 222 | .debug-modal dl { 223 | padding-left: 1em; 224 | } 225 | 226 | .debug-modal dt { 227 | font-size: 1.2em; 228 | } 229 | 230 | .debug-modal dd { 231 | margin-bottom: 1em; 232 | } 233 | /** DEBUG STYLES END */ 234 | -------------------------------------------------------------------------------- /ui/public/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psema4/pine/a037a28933ecb307cb11ef60596b6ee9519b1879/ui/public/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /ui/public/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psema4/pine/a037a28933ecb307cb11ef60596b6ee9519b1879/ui/public/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /ui/public/js/app.js: -------------------------------------------------------------------------------- 1 | /*jslint nomen: true, plusplus: true, undef: true, todo: true, white: true, 2 | browser: true, indent: 2, maxlen: 80 */ 3 | /*global Backbone: false, _: false, $: false, KeyRouter: false, publish: false, 4 | subscribe: false */ 5 | 6 | // Pine app map module 7 | define(['../js/constants'], function (constants) { 8 | 9 | 'use strict'; 10 | 11 | // Serves as a map of initialized app components 12 | var app = { 13 | 'constants': constants 14 | ,'view': { 15 | 'mainMenuPager': null 16 | ,'gameInit': null 17 | } 18 | ,'util': {} 19 | }; 20 | 21 | return app; 22 | 23 | }); 24 | -------------------------------------------------------------------------------- /ui/public/js/constants.js: -------------------------------------------------------------------------------- 1 | /*jslint nomen: true, plusplus: true, undef: true, todo: true, white: true, 2 | browser: true, indent: 2, maxlen: 80 */ 3 | /*global Backbone: false, _: false, $: false, KeyRouter: false, publish: false, 4 | subscribe: false */ 5 | 6 | // Pine constants module 7 | define(['exports'], function (constants) { 8 | 9 | 'use strict'; 10 | 11 | constants.message = { 12 | 'MENU_SELECTED': 'menu-selected' 13 | }; 14 | 15 | constants.key = { 16 | 'LEFT': 37 17 | ,'UP': 38 18 | ,'RIGHT': 39 19 | ,'DOWN': 40 20 | ,'ESC': 27 21 | ,'TAB': 9 22 | ,'ENTER': 13 23 | ,'SPACE': 32 24 | ,'Q': 81 25 | ,'W': 87 26 | ,'E': 69 27 | ,'H': 72 28 | ,'J': 74 29 | ,'K': 75 30 | ,'L': 76 31 | } 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /ui/public/js/init.js: -------------------------------------------------------------------------------- 1 | /*jslint nomen: true, plusplus: true, undef: true, todo: true, white: true, 2 | browser: true, indent: 2, maxlen: 80 */ 3 | /*global Backbone: false, _: false, $: false, KeyRouter: false, publish: false, 4 | subscribe: false */ 5 | 6 | require([ 7 | 8 | 'public/js/app' 9 | 10 | // Views 11 | 12 | ,'public/js/view/view.menu' 13 | ,'public/js/view/view.menu-pager' 14 | ,'public/js/view/view.game-init' 15 | 16 | ], function ( 17 | 18 | app 19 | 20 | ,menu 21 | ,menuPager 22 | ,gameInit) { 23 | 24 | 'use strict'; 25 | 26 | app.util.keyRouter = new KeyRouter(document.documentElement); 27 | var $menu = $('.menu'); 28 | 29 | var menuViews = []; 30 | $menu.each(function (i, el) { 31 | menuViews.push(new menu.view({ 32 | 'el': $(el) 33 | ,'app': app 34 | })); 35 | }); 36 | 37 | var $mainMenus = $('#main-menus'); 38 | app.view.mainMenuPager = new menuPager.view({ 39 | 'app': app 40 | ,'$el': $mainMenus 41 | ,'menuViews': menuViews 42 | }); 43 | 44 | var $pineContainer = $('#pine-container'); 45 | var $gameContainer = $('#game-container'); 46 | app.view.gameInit = new gameInit.view({ 47 | 'app': app 48 | ,'$el': $pineContainer 49 | ,'$gameContainer': $gameContainer 50 | }); 51 | 52 | console.log(app); 53 | 54 | $(function () { 55 | $('body').addClass('loaded'); 56 | }); 57 | 58 | }); 59 | -------------------------------------------------------------------------------- /ui/public/js/lib/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Bootstrap.js by @fat & @mdo 3 | * plugins: bootstrap-transition.js, bootstrap-modal.js, bootstrap-dropdown.js, bootstrap-scrollspy.js, bootstrap-tab.js, bootstrap-tooltip.js, bootstrap-popover.js, bootstrap-alert.js, bootstrap-button.js, bootstrap-collapse.js, bootstrap-carousel.js, bootstrap-typeahead.js 4 | * Copyright 2012 Twitter, Inc. 5 | * http://www.apache.org/licenses/LICENSE-2.0.txt 6 | */ 7 | !function(a){a(function(){a.support.transition=function(){var a=function(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",msTransition:"MSTransitionEnd",transition:"transitionend"},c;for(c in b)if(a.style[c]!==undefined)return b[c]}();return a&&{end:a}}()})}(window.jQuery),!function(a){function c(){var b=this,c=setTimeout(function(){b.$element.off(a.support.transition.end),d.call(b)},500);this.$element.one(a.support.transition.end,function(){clearTimeout(c),d.call(b)})}function d(a){this.$element.hide().trigger("hidden"),e.call(this)}function e(b){var c=this,d=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var e=a.support.transition&&d;this.$backdrop=a('