├── widgets ├── template │ ├── renderer.js │ ├── README.md │ ├── config.json │ └── index.html ├── spotify-listener │ ├── execute.bat │ ├── media │ │ ├── preview.png │ │ ├── skip-back.png │ │ ├── pause-play.png │ │ ├── skip-forward.png │ │ ├── pause.svg │ │ ├── play.svg │ │ ├── pause-play.svg │ │ ├── skip-back.svg │ │ └── skip-forward.svg │ ├── execute.vbs │ ├── package.json │ ├── exec-python.vbs │ ├── result.json │ ├── README.md │ ├── config.json │ ├── preload.js │ ├── install.js │ ├── main.js │ ├── index.html │ ├── retrieve-media-playback-info.py │ ├── renderer.js │ └── styles.css ├── media_thumb.jpg └── README.md ├── src ├── execute.bat ├── widgets.bat ├── widgets.vbs ├── renderer.js ├── execute.vbs ├── package.json ├── index.html ├── config.json ├── preload.js ├── install.js └── main.js ├── .gitignore ├── install.bat ├── .github └── workflows │ ├── test.yml │ └── publish.yml ├── test.bat ├── package.json ├── LICENSE ├── README.md ├── icon.svg └── bin └── cli.js /widgets/template/renderer.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/execute.bat: -------------------------------------------------------------------------------- 1 | cd %~dp0 & npm start 2 | -------------------------------------------------------------------------------- /widgets/spotify-listener/execute.bat: -------------------------------------------------------------------------------- 1 | cd %~dp0 & npm start 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | node_modules 3 | widgets/widgets.svg 4 | -------------------------------------------------------------------------------- /src/widgets.bat: -------------------------------------------------------------------------------- 1 | for /d %%i in ("widgets\*") do "wscript %%i\execute.vbs" 2 | -------------------------------------------------------------------------------- /widgets/template/README.md: -------------------------------------------------------------------------------- 1 | # Template Widget 2 | 3 | This is an example of a widget 4 | -------------------------------------------------------------------------------- /src/widgets.vbs: -------------------------------------------------------------------------------- 1 | CreateObject("Wscript.Shell").Run "cmd /c widgets\widgets.bat", 0, False 2 | -------------------------------------------------------------------------------- /src/renderer.js: -------------------------------------------------------------------------------- 1 | // Here you can add script for your widgets 2 | // NodeJS APIs are available 3 | -------------------------------------------------------------------------------- /widgets/media_thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/underpig1/widget-builder/HEAD/widgets/media_thumb.jpg -------------------------------------------------------------------------------- /widgets/spotify-listener/media/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/underpig1/widget-builder/HEAD/widgets/spotify-listener/media/preview.png -------------------------------------------------------------------------------- /widgets/spotify-listener/media/skip-back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/underpig1/widget-builder/HEAD/widgets/spotify-listener/media/skip-back.png -------------------------------------------------------------------------------- /widgets/spotify-listener/media/pause-play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/underpig1/widget-builder/HEAD/widgets/spotify-listener/media/pause-play.png -------------------------------------------------------------------------------- /widgets/spotify-listener/media/skip-forward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/underpig1/widget-builder/HEAD/widgets/spotify-listener/media/skip-forward.png -------------------------------------------------------------------------------- /src/execute.vbs: -------------------------------------------------------------------------------- 1 | CreateObject("Wscript.Shell").Run "cmd /c """ & CreateObject("Scripting.FileSystemObject").GetParentFolderName(WScript.ScriptFullName) & "\execute.bat""", 0, False 2 | -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./main.js", 3 | "scripts": { 4 | "start": "electron ." 5 | }, 6 | "devDependencies": { 7 | "electron": "16.0.6" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /widgets/spotify-listener/execute.vbs: -------------------------------------------------------------------------------- 1 | CreateObject("Wscript.Shell").Run "cmd /c """ & CreateObject("Scripting.FileSystemObject").GetParentFolderName(WScript.ScriptFullName) & "\execute.bat""", 0, False 2 | -------------------------------------------------------------------------------- /widgets/spotify-listener/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./main.js", 3 | "scripts": { 4 | "start": "electron ." 5 | }, 6 | "devDependencies": { 7 | "electron": "16.0.6" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /widgets/spotify-listener/exec-python.vbs: -------------------------------------------------------------------------------- 1 | CreateObject("Wscript.Shell").Run "python """ & CreateObject("Scripting.FileSystemObject").GetParentFolderName(WScript.ScriptFullName) & "\retrieve-media-playback-info.py"" loop", 0, False 2 | -------------------------------------------------------------------------------- /widgets/spotify-listener/result.json: -------------------------------------------------------------------------------- 1 | {"album_artist": "", "album_title": "", "album_track_count": 0, "artist": "", "genres": [], "playback_type": 1, "subtitle": "", "title": "Advertisement", "track_number": 0, "timeline_position": "21", "timeline_duration": "30", "playing": 1} -------------------------------------------------------------------------------- /widgets/spotify-listener/README.md: -------------------------------------------------------------------------------- 1 | # Spotify listener 2 | 3 | ![Image](media/preview.png) 4 | 5 | # Install 6 | 7 | First, make sure you install `widget-builder` with `npm` and download this repository. 8 | 9 | `cd` to the `spotify-listener` folder and run: 10 | ``` 11 | widgets install 12 | ``` 13 | 14 | On restart, the widget should be up and running. 15 | -------------------------------------------------------------------------------- /install.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | mkdir "%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\widgets" 3 | attrib +h "%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\widgets" /s /d 4 | xcopy src\widgets.bat "%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\widgets" 5 | xcopy src\widgets.vbs "%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\" 6 | npm install -g 7 | -------------------------------------------------------------------------------- /widgets/template/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "template", 3 | "version": "1.0.0", 4 | "description": "Custom desktop widget", 5 | "index": "./index.html", 6 | "properties": { 7 | "x": 1700, 8 | "y": 100, 9 | "width": 100, 10 | "height": 100, 11 | "transparent": false, 12 | "interact": true, 13 | "draggable": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /widgets/template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Clock 6 | 7 | 8 |

Hello!

9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello World! 6 | 7 | 8 |

Hello World!

9 |
Here is your content
10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | test: 11 | runs-on: windows-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-node@v2 15 | with: 16 | node-version: 16 17 | - run: npm install -g 18 | - run: npm start 19 | - run: npm test 20 | -------------------------------------------------------------------------------- /src/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "widget", 3 | "version": "1.0.0", 4 | "description": "Custom desktop widget", 5 | "index": "./index.html", 6 | "properties": { 7 | "x": 100, 8 | "y": 100, 9 | "width": 100, 10 | "height": 100, 11 | "transparent": false, 12 | "interact": true, 13 | "draggable": true, 14 | "top": false 15 | }, 16 | "requirements": [], 17 | "install": "" 18 | } 19 | -------------------------------------------------------------------------------- /widgets/spotify-listener/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spotify-listener", 3 | "version": "1.0.0", 4 | "description": "Spotify listener", 5 | "index": "./index.html", 6 | "properties": { 7 | "x": 1450, 8 | "y": 200, 9 | "width": 355, 10 | "height": 215, 11 | "transparent": true, 12 | "interact": true, 13 | "draggable": true, 14 | "top": true 15 | }, 16 | "install": "pip install winrt" 17 | } -------------------------------------------------------------------------------- /test.bat: -------------------------------------------------------------------------------- 1 | REM initialize tests 2 | npm install -g 3 | npm start 4 | 5 | REM test CLI 6 | widgets init example-widgets\clock 7 | cd example-widgets\clock 8 | widgets init 9 | widgets build 10 | widgets publish 11 | cd ../dist 12 | widgets install 13 | widgets list 14 | widgets uninstall clock 15 | 16 | REM test meta 17 | cmd /c "%appdata%\Microsoft\Windows\Start Menu\Programs\Startup\widgets.vbs" 18 | cmd /c "%appdata%\Microsoft\Windows\Start Menu\Programs\Startup\widgets\widgets.bat" 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "widget-builder", 3 | "version": "1.0.3", 4 | "description": "CLI and program to compile HTML into custom desktop widgets", 5 | "license": "MIT", 6 | "keywords": ["windows", "widgets", "javascript", "html"], 7 | "website": "https://github.com/underpig1/widget-builder", 8 | "main": "bin/cli.js", 9 | "bin": { 10 | "widgets": "./bin/cli.js" 11 | }, 12 | "scripts": { 13 | "start": "cmd /c install.bat", 14 | "test": "cmd /c test.bat" 15 | }, 16 | "author": "underpig", 17 | "dependencies": { 18 | "electron": "^17.2.0", 19 | "fs-extra": "^10.0.1", 20 | "yargs": "^17.4.0", 21 | "path": "^0.12.7" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | on: 4 | release: 5 | types: [ published ] 6 | 7 | jobs: 8 | test: 9 | runs-on: windows-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions/setup-node@v2 13 | with: 14 | node-version: 16 15 | - run: npm install -g 16 | - run: npm start 17 | - run: npm test 18 | publish: 19 | runs-on: windows-latest 20 | steps: 21 | - uses: actions/checkout@v2 22 | - uses: actions/setup-node@v2 23 | with: 24 | node-version: 16 25 | registry-url: https://registry.npmjs.org/ 26 | - run: npm publish 27 | env: 28 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 29 | -------------------------------------------------------------------------------- /widgets/README.md: -------------------------------------------------------------------------------- 1 | # Contributing Widgets 2 | 3 | Here are the instructions for publishing widgets for others to use: 4 | 1) Fork this repository 5 | 2) Prepare your widget for publishing by using `widgets publish [path/to/your/project]` 6 | 3) Find the `dist` folder that was created in the same directory as your widget project 7 | 4) Move it to this folder in your forked repository and rename it to your widget title 8 | 5) Add a `README.md` containing a description and optional preview of your widget 9 | 6) Submit a pull request 10 | 11 | When you submit a pull request, make sure it includes the following: 12 | - A `README.md` with a description and optional preview 13 | - A properly-formatted `config.json` file 14 | - A master html file 15 | -------------------------------------------------------------------------------- /src/preload.js: -------------------------------------------------------------------------------- 1 | draggable = require("./config.json").properties.draggable 2 | 3 | window.addEventListener("DOMContentLoaded", () => { 4 | if (draggable) { 5 | var top_bar = document.createElement("div") 6 | top_bar.style.position = "absolute" 7 | top_bar.style.width = "100%" 8 | top_bar.style.height = "32px" 9 | top_bar.style.top = top_bar.style.left = 0 10 | top_bar.style.webkitAppRegion = "drag" 11 | top_bar.style.zIndex = "-1" 12 | document.body.appendChild(top_bar) 13 | } 14 | draggable_elements = document.querySelectorAll(".draggable") 15 | for (var el of draggable_elements) { 16 | el.style.webkitAppRegion = "drag" 17 | } 18 | 19 | interactable_elements = document.querySelectorAll(".interact") 20 | for (var el of interactable_elements) { 21 | el.style.webkitAppRegion = "no-drag" 22 | } 23 | }) 24 | -------------------------------------------------------------------------------- /widgets/spotify-listener/preload.js: -------------------------------------------------------------------------------- 1 | draggable = require("./config.json").properties.draggable 2 | 3 | window.addEventListener("DOMContentLoaded", () => { 4 | if (draggable) { 5 | var top_bar = document.createElement("div") 6 | top_bar.style.position = "absolute" 7 | top_bar.style.width = "100%" 8 | top_bar.style.height = "32px" 9 | top_bar.style.top = top_bar.style.left = 0 10 | top_bar.style.webkitAppRegion = "drag" 11 | top_bar.style.zIndex = "-1" 12 | document.body.appendChild(top_bar) 13 | } 14 | draggable_elements = document.querySelectorAll(".draggable") 15 | for (var el of draggable_elements) { 16 | el.style.webkitAppRegion = "drag" 17 | } 18 | 19 | interactable_elements = document.querySelectorAll(".interact") 20 | for (var el of interactable_elements) { 21 | el.style.webkitAppRegion = "no-drag" 22 | } 23 | }) 24 | -------------------------------------------------------------------------------- /src/install.js: -------------------------------------------------------------------------------- 1 | const path = require("path") 2 | const fs = require("fs-extra") 3 | const pkg = require(path.join(__dirname, "config.json")) 4 | const exec = require("child_process").exec 5 | 6 | var target_dir = process.env.APPDATA + "\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\widgets\\" + pkg.name 7 | 8 | fs.copy(__dirname, target_dir).then(() => { console.log("Successfully installed " + pkg.name + "! Use 'widgets list' to see all enabled widgets") }).catch(err => console.error(err)) 9 | 10 | setTimeout(() => { 11 | if (pkg.hasOwnProperty("requirements")) { 12 | for (var req of pkg.requirements) { 13 | run_cmd("cd \"" + target_dir + "\" & npm install ") 14 | } 15 | } 16 | if (pkg.hasOwnProperty("install")) { 17 | if (Array.isArray(pkg.install)) { 18 | for (var cmd of pkg.install) { 19 | run_cmd(cmd) 20 | } 21 | } 22 | else { 23 | run_cmd(pkg.install) 24 | } 25 | } 26 | }, 1000) 27 | 28 | function run_cmd(cmd) { 29 | exec(cmd, (err, stdout, stderr) => { 30 | if (err) { 31 | console.error(err) 32 | return 33 | } 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Underpig 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /widgets/spotify-listener/install.js: -------------------------------------------------------------------------------- 1 | const path = require("path") 2 | const fs = require("fs-extra") 3 | const pkg = require(path.join(__dirname, "config.json")) 4 | const exec = require("child_process").exec 5 | 6 | var target_dir = process.env.APPDATA + "\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\widgets\\" + pkg.name 7 | 8 | fs.copy(__dirname, target_dir).then(() => { console.log("Successfully installed " + pkg.name + "! Use 'widgets list' to see all enabled widgets") }).catch(err => console.error(err)) 9 | 10 | setTimeout(() => { 11 | if (pkg.hasOwnProperty("requirements")) { 12 | for (var req of pkg.requirements) { 13 | run_cmd("cd \"" + target_dir + "\" & npm install ") 14 | } 15 | } 16 | if (pkg.hasOwnProperty("install")) { 17 | if (Array.isArray(pkg.install)) { 18 | for (var cmd of pkg.install) { 19 | run_cmd(cmd) 20 | } 21 | } 22 | else { 23 | run_cmd(pkg.install) 24 | } 25 | } 26 | }, 1000) 27 | 28 | function run_cmd(cmd) { 29 | exec(cmd, (err, stdout, stderr) => { 30 | if (err) { 31 | console.error(err) 32 | return 33 | } 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | const {app, BrowserWindow} = require("electron") 2 | const path = require("path") 3 | 4 | const config = require(path.join(__dirname, "config.json")) 5 | 6 | function init() { 7 | const main = new BrowserWindow({ 8 | width: config.properties.width, 9 | height: config.properties.height, 10 | frame: false, 11 | transparent: config.properties.transparent, 12 | show: false, 13 | webPreferences: { 14 | preload: path.join(__dirname, "preload.js"), 15 | nodeIntegration: true, 16 | contextIsolation: false 17 | } 18 | }) 19 | main.setPosition(config.properties.x, config.properties.y) 20 | main.setSkipTaskbar(true) 21 | main.setResizable(false) 22 | if (!config.properties.interact) { 23 | const top = new BrowserWindow({parent: main, modal: true, transparent: true, frame: false, show: true, width: 0, height: 0}) 24 | top.setSkipTaskbar(true) 25 | } 26 | main.setAlwaysOnTop(config.properties.top) 27 | main.once("ready-to-show", () => main.show()) 28 | main.loadFile(config.index) 29 | } 30 | 31 | app.whenReady().then(() => { 32 | init() 33 | app.on("activate", function () { 34 | if (BrowserWindow.getAllWindows().length === 0) init() 35 | }) 36 | }) 37 | 38 | app.on("window-all-closed", function () { 39 | if (process.platform !== "darwin") app.quit() 40 | }) 41 | -------------------------------------------------------------------------------- /widgets/spotify-listener/main.js: -------------------------------------------------------------------------------- 1 | const {app, BrowserWindow} = require("electron") 2 | const path = require("path") 3 | 4 | const config = require(path.join(__dirname, "config.json")) 5 | 6 | function init() { 7 | const main = new BrowserWindow({ 8 | width: config.properties.width, 9 | height: config.properties.height, 10 | frame: false, 11 | transparent: config.properties.transparent, 12 | show: false, 13 | webPreferences: { 14 | preload: path.join(__dirname, "preload.js"), 15 | nodeIntegration: true, 16 | contextIsolation: false 17 | } 18 | }) 19 | main.setPosition(config.properties.x, config.properties.y) 20 | main.setSkipTaskbar(true) 21 | main.setResizable(false) 22 | if (!config.properties.interact) { 23 | const top = new BrowserWindow({parent: main, modal: true, transparent: true, frame: false, show: true, width: 0, height: 0}) 24 | top.setSkipTaskbar(true) 25 | } 26 | main.setAlwaysOnTop(config.properties.top) 27 | main.once("ready-to-show", () => main.show()) 28 | main.loadFile(config.index) 29 | } 30 | 31 | app.whenReady().then(() => { 32 | init() 33 | app.on("activate", function () { 34 | if (BrowserWindow.getAllWindows().length === 0) init() 35 | }) 36 | }) 37 | 38 | app.on("window-all-closed", function () { 39 | if (process.platform !== "darwin") app.quit() 40 | }) 41 | -------------------------------------------------------------------------------- /widgets/spotify-listener/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Spotify listener 7 | 8 | 9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | 17 | 18 | 19 |
20 |
21 |
22 |
23 |

0:00

24 |
25 |
26 |
27 |
28 |
29 |
30 |

31 |

32 |
33 |
34 |
35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /widgets/spotify-listener/retrieve-media-playback-info.py: -------------------------------------------------------------------------------- 1 | # https://stackoverflow.com/questions/65011660/how-can-i-get-the-title-of-the-currently-playing-media-in-windows-10-with-python 2 | 3 | import asyncio 4 | import sys 5 | from winrt.windows.media.control import GlobalSystemMediaTransportControlsSessionManager as MediaManager 6 | from winrt.windows.storage.streams import DataReader, Buffer, InputStreamOptions 7 | import os 8 | import json 9 | import time 10 | 11 | parent = os.path.dirname(os.path.abspath(__file__)) 12 | 13 | async def get_current_session(): 14 | sessions = await MediaManager.request_async() 15 | current_session = sessions.get_current_session() 16 | return current_session 17 | 18 | async def retrieve(): 19 | global media 20 | 21 | sessions = await MediaManager.request_async() 22 | current_session = sessions.get_current_session() 23 | if current_session: 24 | info = await current_session.try_get_media_properties_async() 25 | info_dir = {song_attr: info.__getattribute__(song_attr) for song_attr in dir(info) if song_attr[0] != "_"} 26 | info_dir["genres"] = list(info_dir["genres"]) 27 | 28 | timeline_properties = current_session.get_timeline_properties() 29 | timeline_position = str(timeline_properties.position.duration)[:-7] 30 | timeline_duration = str(timeline_properties.end_time.duration)[:-7] 31 | info_dir["timeline_position"] = timeline_position 32 | info_dir["timeline_duration"] = timeline_duration 33 | 34 | return info_dir 35 | 36 | def get_thumbnail(thumbnail): 37 | async def read_stream_into_buffer(stream_ref, buffer): 38 | readable_stream = await stream_ref.open_read_async() 39 | readable_stream.read_async(buffer, buffer.capacity, InputStreamOptions.READ_AHEAD) 40 | 41 | thumb_read_buffer = Buffer(5000000) 42 | asyncio.run(read_stream_into_buffer(thumbnail, thumb_read_buffer)) 43 | 44 | buffer_reader = DataReader.from_buffer(thumb_read_buffer) 45 | byte_buffer = buffer_reader.read_bytes(thumb_read_buffer.length) 46 | 47 | with open(parent + "\\media\\album-cover.jpg", "wb+") as fobj: 48 | fobj.write(bytearray(byte_buffer)) 49 | 50 | # main 51 | 52 | if len(sys.argv) > 1: 53 | if sys.argv[1] == "toggle": 54 | try: 55 | current_session = asyncio.run(get_current_session()) 56 | current_session.try_toggle_play_pause_async() 57 | sys.stdout.write("1") 58 | except: 59 | pass 60 | elif sys.argv[1] == "loop": 61 | while True: 62 | media = asyncio.run(retrieve()) 63 | if media != None: 64 | try: 65 | get_thumbnail(media["thumbnail"]) 66 | media = {song_attr: media[song_attr] for song_attr in media if song_attr != "thumbnail"} 67 | media["playing"] = 1 68 | with open(parent + "\\result.json", "w") as file: 69 | json.dump(media, file) 70 | except: 71 | with open(parent + "\\result.json", "r+") as file: 72 | data = json.load(file) 73 | data["playing"] = 0 74 | file.seek(0) 75 | json.dump(data, file) 76 | file.truncate() 77 | else: 78 | with open(parent + "\\result.json", "r+") as file: 79 | data = json.load(file) 80 | data["playing"] = 0 81 | file.seek(0) 82 | json.dump(data, file) 83 | file.truncate() 84 | time.sleep(0.1) 85 | elif sys.argv[1] == "skip-back": 86 | try: 87 | current_session = asyncio.run(get_current_session()) 88 | current_session.try_skip_previous_async() 89 | sys.stdout.write("1") 90 | except: 91 | pass 92 | elif sys.argv[1] == "skip-forward": 93 | try: 94 | current_session = asyncio.run(get_current_session()) 95 | current_session.try_skip_next_async() 96 | sys.stdout.write("1") 97 | except: 98 | pass 99 | elif sys.argv[1] == "pause": 100 | try: 101 | current_session = asyncio.run(get_current_session()) 102 | current_session.try_pause_async() 103 | sys.stdout.write("1") 104 | except: 105 | pass 106 | elif sys.argv[1] == "play": 107 | try: 108 | current_session = asyncio.run(get_current_session()) 109 | current_session.try_play_async() 110 | sys.stdout.write("1") 111 | except: 112 | pass 113 | elif sys.argv[1] == "rewind": 114 | try: 115 | current_session = asyncio.run(get_current_session()) 116 | current_session.try_rewind_async() 117 | sys.stdout.write("1") 118 | except: 119 | pass 120 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Widget Builder

4 | 5 | 6 | npm version 7 |

Develop, install, and distribute HTML widgets for your Windows desktop with a simple CLI

8 |
9 |
10 | 11 | # Table of Contents 12 | 1. [Installation](#installation) 13 | 2. [Quickstart Guide](#quickstart-guide) 14 | 3. [Samples](#samples) 15 | 4. [Publishing and installing](#publishing-and-installing-widgets) 16 | 1. [Publishing](#publishing) 17 | 2. [Installing](#installing) 18 | 5. [The Config File](#the-config-file) 19 | 6. [Using the CLI](#using-the-cli) 20 | 7. [Contributing Widgets](#contributing-widgets) 21 | 22 |
23 | 24 | # Installation 25 | Download, unzip, navigate to the folder, and install with `npm`: 26 | ``` 27 | $ npm install -g 28 | $ npm start 29 | ``` 30 | or download directly from `npm`: 31 | ``` 32 | $ npm install -g widget-builder 33 | ``` 34 | 35 | # Quickstart Guide 36 | 1) Create a new folder 37 | 2) `cd` to the folder, enter `widgets init`, and fill in your project's name 38 | 3) Mess around with the generated HTML file to customize your widget's appearance 39 | 4) Add and link JavaScript or CSS files to your master HTML file 40 | 5) Modify the config file 41 | 6) Navigate to the folder and enter `widgets build` to locally build and install your widget 42 | 43 |
44 | 45 | # Samples 46 | Check out [Spotify listener](widgets/spotify-listener/media/preview.png), a widget for listening to your favorite tunes: 47 | [![Spotify listener](widgets/spotify-listener/media/preview.png)](https://github.com/underpig1/widget-builder/tree/master/widgets/spotify-listener) 48 | 49 | # Publishing and installing widgets 50 | ### Publishing 51 | So you want to share your widget for distribution? Here's what to do: 52 | 1) `cd` to your project folder 53 | 2) Run `widgets publish` 54 | 3) A new `dist` folder will be generated in the same directory as your project folder. You can now distribute this folder, and others can install it with `widgets install` 55 | 56 | ### Installing 57 | Here's how to install a widget that was shared with you: 58 | 1) Navigate to the folder 59 | 2) Run `widgets install` 60 | 61 |
62 | 63 | # The Config File 64 | Every widgets project contains a `config.json` file. This file tells the program what settings you would like to use for your widget.
65 | Here's a standard config file: 66 | ```json 67 | { 68 | "name": "widget", 69 | "version": "1.0.0", 70 | "description": "Custom desktop widget", 71 | "index": "./index.html", 72 | "properties": { 73 | "x": 100, 74 | "y": 100, 75 | "width": 100, 76 | "height": 100, 77 | "transparent": false, 78 | "interact": true, 79 | "draggable": true 80 | } 81 | } 82 | ``` 83 | | Property | Definition | 84 | | ---- | ---- | 85 | | `name` (string) | Project name | 86 | | `version` (string) | Project version | 87 | | `index` (string) | The reference to your master HTML file. Other references (like JS or CSS) should be linked in this file. | 88 | | `x`, `y`, `width`, and `height` (integers) | The position and dimensions of your widget when it is first start up | 89 | | `transparent` (boolean) | Make the widget's background transparent | 90 | | `interact` (boolean) | Make the widget interactable | 91 | | `draggable` (boolean) | Make the widget draggable | 92 | | `top` (boolean) | Make the widget stay on top of all windows | 93 | | `requirements` (array) | npm packages required for the widget to function; these packages are locally installed when the widget is installed | 94 | | `install` (string or array) | Script(s) to run during widget installation | 95 | 96 |
97 | 98 | # Using the CLI 99 | Once widget builder is installed, the CLI can be accessed with the keyword `widgets` 100 | 101 | ### Commands 102 | | Command | Definition | 103 | | ---- | ---- | 104 | | `widgets build [folder]` | Builds HTML files to desktop widget and installs | 105 | | `widgets publish [folder]` | Generates a dist file that can be installed by the widgets cli | 106 | | `widgets install [folder]` | Installs widget at folder | 107 | | `widgets init [folder]` | Initializes widgets project | 108 | | `widgets list` | Lists all installed widgets | 109 | | `widgets uninstall ` | Uninstall widget by name | 110 | | `widgets config ` | Configure widget by name | 111 | | `widgets start [folder]` | Starts the widget at folder | 112 | 113 | ### Options 114 | | Command | Definition | 115 | | ---- | ---- | 116 | | `widgets --help` | Show help | 117 | | `widgets --version` | Displays the current version | 118 | 119 | # Contributing Widgets 120 | [Here](widgets/README.md) you can find instructions for sharing widgets you have created 121 | -------------------------------------------------------------------------------- /widgets/spotify-listener/renderer.js: -------------------------------------------------------------------------------- 1 | var child_process = require("child_process") 2 | var path = require("path") 3 | const fs = require("fs") 4 | 5 | // MediaController("result", (result) => { 6 | // var result = JSON.parse(result.replace(/'(?=(?:(?:[^"]*"){2})*[^"]*$)/g, "\"")) 7 | // console.log(result) 8 | // }) 9 | 10 | child_process.exec(`python \"${path.join(__dirname, "retrieve-media-playback-info.py")}\" loop`, (err, stdout, stderr) => { 11 | if (err) { 12 | console.log(err) 13 | return 14 | } 15 | }) 16 | 17 | var progress_time = document.getElementById("progress-time") 18 | var progress_indicator = document.getElementById("progress-indicator") 19 | var song_title = document.getElementById("song-title") 20 | var song_artist = document.getElementById("song-artist") 21 | var pause_play = document.getElementById("pause-play") 22 | var album_cover = ".\\media\\album-cover.jpg" 23 | var pause = `url("media/pause.svg")` 24 | var play = `url("media/play.svg")` 25 | var main = document.getElementById("main") 26 | var backup = document.getElementById("backup") 27 | var preview = document.getElementById("preview") 28 | var preview_backup = document.getElementById("preview-backup") 29 | var paused = true 30 | var closed = true 31 | 32 | MediaController("pause") 33 | 34 | function MediaController(command, callback = null) { 35 | child_process.exec(`python \"${path.join(__dirname, "retrieve-media-playback-info.py")}\" ${command}`, (err, stdout, stderr) => { 36 | if (err) { 37 | console.log(err) 38 | return 39 | } 40 | if (callback != null) { 41 | callback(stdout) 42 | } 43 | }) 44 | } 45 | 46 | function skipBack() { 47 | MediaController("skip-back") 48 | paused = false 49 | } 50 | function pausePlay() { 51 | if (!closed) { 52 | if (paused) { 53 | MediaController("play") 54 | paused = false 55 | } 56 | else { 57 | MediaController("pause") 58 | paused = true 59 | } 60 | } 61 | } 62 | function skipForward() { 63 | MediaController("skip-forward") 64 | paused = false 65 | } 66 | 67 | var previous_timer = [0, 0] 68 | var previous_result = [0, 0] 69 | 70 | setInterval(() => { 71 | try { 72 | var result = JSON.parse(fs.readFileSync(path.join(__dirname, "result.json"))) 73 | } 74 | catch { 75 | var result = {"playing": 0} 76 | } 77 | if (result.playing == 1) { 78 | var closed = false 79 | } 80 | else { 81 | var closed = true 82 | } 83 | progress_indicator.style.width = Math.floor(parseInt(result.timeline_position)/parseInt(result.timeline_duration) * 100).toString() + "%" 84 | if (result.timeline_position != "") { 85 | var minutes = Math.floor(parseInt(result.timeline_position) / 60) 86 | var seconds = Math.floor(parseInt(result.timeline_position) - minutes * 60) 87 | if (previous_result.toString() == [seconds, minutes].toString() && !paused && !closed) { 88 | seconds = previous_timer[0] 89 | minutes = previous_timer[1] 90 | seconds == 59 ? (minutes += 1, seconds = 0) : seconds += 1 91 | song_title.innerText = `past: ${previous_timer[0]} now: ${seconds}` 92 | } 93 | else { 94 | previous_result = [seconds, minutes] 95 | } 96 | previous_timer = [seconds, minutes] 97 | seconds = seconds.toString() 98 | parseInt(seconds) < 10 ? seconds = "0" + seconds : null 99 | minutes = minutes.toString() 100 | } 101 | else { 102 | seconds = "00" 103 | minutes = "0" 104 | } 105 | progress_time.innerText = `${minutes}:${seconds}` 106 | song_title.innerText = result.title 107 | song_artist.innerText = result.artist 108 | main.style.background = "url(\"media/album-cover.jpg\")" 109 | if (song_title.innerText.length >= 16) { 110 | song_title.style.animation = `loop-scroll ${song_title.innerText.length/2}s linear infinite` 111 | } 112 | else { 113 | song_title.style.animation = "none" 114 | } 115 | if (song_artist.innerText.length >= 16) { 116 | song_artist.style.animation = `loop-scroll ${song_artist.innerText.length/2}s linear infinite` 117 | song_artist.style.right = "auto" 118 | song_artist.style.left = "0" 119 | } 120 | else { 121 | song_artist.style.animation = "none" 122 | song_artist.style.right = "0" 123 | song_artist.style.left = "auto" 124 | } 125 | if (paused) { 126 | pause_play.style.backgroundImage = play 127 | pause_play.childNodes[0].childNodes[0].style.backgroundImage = play 128 | } 129 | else { 130 | pause_play.style.backgroundImage = pause 131 | pause_play.childNodes[0].childNodes[0].style.backgroundImage = pause 132 | } 133 | var time = new Date().getTime() 134 | main.style.setProperty("background-image", "url('media/album-cover.jpg?v=" + time + "')", "important") 135 | setTimeout(() => { 136 | backup.style.setProperty("background-image", "url('media/album-cover.jpg?v=" + time + "')", "important") 137 | }, 500) 138 | preview.style.setProperty("background-image", "url('media/album-cover.jpg?v=" + time + "')", "important") 139 | setTimeout(() => { 140 | preview_backup.style.setProperty("background-image", "url('media/album-cover.jpg?v=" + time + "')", "important") 141 | }, 500) 142 | }, 1000) 143 | -------------------------------------------------------------------------------- /widgets/spotify-listener/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | .main { 7 | display: flex; 8 | width: 345px; 9 | height: 200px; 10 | border-radius: 12px; 11 | background-size: 135% !important; 12 | box-shadow: 0 5px 20px #00000011; 13 | background: #505050; 14 | background-position: center center !important; 15 | background-repeat: no-repeat; 16 | } 17 | 18 | .gradient { 19 | display: block; 20 | position: absolute; 21 | width: inherit; 22 | height: inherit; 23 | top: 0; 24 | left: 0; 25 | border-radius: inherit; 26 | background-size: 100%; 27 | background-image: linear-gradient(to bottom, #00000050, #000000FF); 28 | } 29 | 30 | #backup { 31 | display: block; 32 | position: absolute; 33 | width: inherit; 34 | height: inherit; 35 | top: 0; 36 | left: 0; 37 | border-radius: inherit; 38 | background: #505050; 39 | background-size: inherit !important; 40 | background-position: inherit !important; 41 | background-repeat: inherit; 42 | z-index: -1; 43 | } 44 | 45 | #preview { 46 | position: absolute; 47 | top: 17.5px; 48 | right: 30px; 49 | width: 50px; 50 | height: 50px; 51 | border-radius: 5px; 52 | background-size: 135% !important; 53 | box-shadow: 0 5px 20px #00000011; 54 | background: #505050; 55 | background-position: center top !important; 56 | background-repeat: no-repeat; 57 | } 58 | 59 | #preview-backup { 60 | position: absolute; 61 | top: 0; 62 | right: 0; 63 | width: 100%; 64 | height: 100%; 65 | border-radius: inherit; 66 | background-size: inherit !important; 67 | box-shadow: inherit; 68 | background: #505050; 69 | background-position: inherit !important; 70 | background-repeat: inherit; 71 | } 72 | 73 | .sector { 74 | display: flex; 75 | position: absolute; 76 | min-width: 345px; 77 | left: 0; 78 | align-items: center; 79 | } 80 | 81 | p { 82 | font-size: 12; 83 | font-family: "Segoe UI"; 84 | color: #D0D0D0; 85 | } 86 | 87 | .buttons { 88 | display: inline-flex; 89 | margin-top: 15px; 90 | margin-left: auto; 91 | margin-right: auto; 92 | } 93 | 94 | .btn { 95 | display: inline-table; 96 | border: none; 97 | border-radius: 17.5px; 98 | outline: none; 99 | background: #505050; 100 | transition: transform 0.1s; 101 | transform: scale(1); 102 | background-size: cover; 103 | clip-path: inset(0px round 17.5px); 104 | } 105 | 106 | .btn-small { 107 | position: relative; 108 | width: 35px; 109 | height: 35px; 110 | border-radius: 17.5px; 111 | } 112 | 113 | .btn-large { 114 | width: 45px; 115 | height: 45px; 116 | border-radius: 22.5px; 117 | margin-left: 25px; 118 | margin-right: 25px; 119 | clip-path: inset(0px round 22.5px); 120 | } 121 | 122 | .btn:active { 123 | outline: none; 124 | transform: scale(0.9); 125 | } 126 | 127 | .hover-space { 128 | display: inline-block; 129 | position: absolute; 130 | left: 0; 131 | top: 100%; 132 | width: 100%; 133 | height: 100%; 134 | background: #D0D0D0; 135 | transition: top 0.2s; 136 | } 137 | 138 | .hover-space div { 139 | display: inline-block; 140 | position: absolute; 141 | left: 0; 142 | top: -100%; 143 | width: 100%; 144 | height: 100%; 145 | opacity: 1; 146 | pointer-events: none; 147 | filter: invert(29%) sepia(0%) saturate(1%) hue-rotate(357deg) brightness(99%) contrast(89%); 148 | background-size: cover; 149 | transition: top 0.2s, opacity 0.2s; 150 | } 151 | 152 | .btn:hover .hover-space { 153 | top: 0; 154 | } 155 | 156 | .btn:hover .hover-space div { 157 | top: 0; 158 | } 159 | 160 | .btn:active .hover-space div { 161 | opacity: 0 !important; 162 | } 163 | 164 | .info { 165 | text-align: center; 166 | } 167 | 168 | .progress-bar { 169 | display: inline-flex; 170 | position: absolute; 171 | top: 35px; 172 | left: 35px; 173 | right: 35px; 174 | height: 6px; 175 | border-radius: 6px; 176 | background: #505050; 177 | } 178 | 179 | #progress-indicator { 180 | display: inline-flex; 181 | position: absolute; 182 | height: 6px; 183 | width: 98%; 184 | border-radius: 6px; 185 | background: #303030; 186 | transition: width 0.1s; 187 | } 188 | 189 | #skip-back, #skip-back div { 190 | background-image: url("media/skip-back.svg"); 191 | } 192 | 193 | #pause-play, #pause-play div { 194 | background-image: url("media/play.svg"); 195 | } 196 | 197 | #skip-forward, #skip-forward div { 198 | background-image: url("media/skip-forward.svg"); 199 | } 200 | 201 | .scroll-title { 202 | position: absolute; 203 | margin: 0; 204 | padding: 0; 205 | width: 130px; 206 | height: 45px; 207 | overflow-x: hidden; 208 | white-space: nowrap; 209 | } 210 | 211 | .scroll-title.l p { 212 | left: 0; 213 | } 214 | 215 | .scroll-title.r p { 216 | right: 0; 217 | } 218 | 219 | .scroll-title p { 220 | position: absolute; 221 | top: 0; 222 | } 223 | 224 | ::-webkit-scrollbar { 225 | display: none; 226 | } 227 | 228 | @keyframes loop-scroll { 229 | 0% { 230 | transform: translateX(0); 231 | } 232 | 20% { 233 | transform: translateX(0); 234 | } 235 | 50% { 236 | transform: translateX(calc(130px - 100%)); 237 | } 238 | 70% { 239 | transform: translateX(calc(130px - 100%)); 240 | } 241 | 100% { 242 | transform: translateX(0); 243 | } 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /widgets/spotify-listener/media/pause.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 32 | 35 | 39 | 43 | 44 | 47 | 51 | 55 | 56 | 59 | 67 | 68 | 78 | 81 | 89 | 90 | 93 | 101 | 102 | 111 | 112 | 135 | 137 | 138 | 140 | image/svg+xml 141 | 143 | 144 | 145 | 146 | 147 | 152 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /widgets/spotify-listener/media/play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 32 | 35 | 39 | 43 | 44 | 47 | 51 | 55 | 56 | 59 | 67 | 68 | 78 | 81 | 89 | 90 | 93 | 101 | 102 | 111 | 112 | 135 | 137 | 138 | 140 | image/svg+xml 141 | 143 | 144 | 145 | 146 | 147 | 152 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /widgets/spotify-listener/media/pause-play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 32 | 35 | 39 | 43 | 44 | 47 | 51 | 55 | 56 | 59 | 67 | 68 | 78 | 81 | 89 | 90 | 93 | 101 | 102 | 111 | 112 | 135 | 137 | 138 | 140 | image/svg+xml 141 | 143 | 144 | 145 | 146 | 147 | 152 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /widgets/spotify-listener/media/skip-back.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 32 | 35 | 39 | 43 | 44 | 47 | 51 | 55 | 56 | 59 | 67 | 68 | 78 | 81 | 89 | 90 | 93 | 101 | 102 | 111 | 112 | 135 | 137 | 138 | 140 | image/svg+xml 141 | 143 | 144 | 145 | 146 | 147 | 152 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /widgets/spotify-listener/media/skip-forward.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 32 | 35 | 39 | 43 | 44 | 47 | 51 | 55 | 56 | 59 | 67 | 68 | 78 | 81 | 89 | 90 | 93 | 101 | 102 | 111 | 112 | 135 | 137 | 138 | 140 | image/svg+xml 141 | 143 | 144 | 145 | 146 | 147 | 152 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 25 | 29 | 33 | 34 | 37 | 41 | 45 | 46 | 56 | 66 | 67 | 89 | 91 | 92 | 94 | image/svg+xml 95 | 97 | 98 | 99 | 100 | 101 | 106 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const path = require("path") 4 | const build = require(path.join(__dirname, "..\\build\\build.js")) 5 | const fs = require("fs") 6 | const readline = require("readline").createInterface({ 7 | input: process.stdin, 8 | output: process.stdout 9 | }) 10 | 11 | require("yargs") 12 | .scriptName("widgets") 13 | .usage("$0 [arguments]") 14 | .command("build [folder]", "Builds HTML files to desktop widget and installs", (yargs) => { 15 | yargs.positional("folder", { 16 | type: "string", 17 | describe: "The folder containing the content to build (includes HTML and optional config.json)" 18 | }) 19 | }, function (argv) { 20 | argv.folder ? folder = argv.folder : folder = path.resolve(".") 21 | console.log("Building widget at " + folder) 22 | build.build_local(folder) 23 | console.log("widget successfully built and installed! Run 'widgets list' to see all enabled widgets") 24 | }) 25 | .command("publish [folder]", "Generates a dist file that can be installed by the widgets cli", (yargs) => { 26 | yargs.positional("folder", { 27 | type: "string", 28 | describe: "The folder containing the content to build (includes HTML and optional config.json)" 29 | }) 30 | }, function (argv) { 31 | argv.folder ? folder = argv.folder : folder = path.resolve(".") 32 | console.log("Publishing widget at " + folder) 33 | build.build_dist(folder) 34 | console.log("widget successfully built to " + path.join(path.dirname(folder), "dist")) 35 | }) 36 | .command("install [folder]", "Installs widget at folder", (yargs) => { 37 | yargs.positional("folder", { 38 | type: "string", 39 | describe: "The folder containing the widget to install" 40 | }) 41 | }, function (argv) { 42 | argv.folder ? folder = argv.folder : folder = path.resolve(".") 43 | const child_process = require("child_process") 44 | child_process.exec("node " + path.join(folder, "install.js"), (err, stdout, stderr) => { 45 | if (err) { 46 | console.log(err) 47 | return 48 | } 49 | }) 50 | console.log("widget successfully installed at " + folder + "!") 51 | }) 52 | .command("init [folder]", "Initializes widgets project", (yargs) => { 53 | yargs.positional("folder", { 54 | type: "string", 55 | describe: "The folder to initialize" 56 | }) 57 | }, function (argv) { 58 | argv.folder ? folder = argv.folder : folder = path.resolve(".") 59 | fs.copyFile(path.join(__dirname, "..\\src\\config.json"), path.join(folder, "config.json"), (err) => { if (err) console.error(err) }) 60 | fs.copyFile(path.join(__dirname, "..\\src\\index.html"), path.join(folder, "index.html"), (err) => { if (err) console.error(err) }) 61 | fs.copyFile(path.join(__dirname, "..\\src\\renderer.js"), path.join(folder, "renderer.js"), (err) => { if (err) console.error(err) }) 62 | setTimeout(() => { 63 | readline.question("Name of your project: ", name => { 64 | config = require(path.join(folder, "config.json")) 65 | config.name = name 66 | fs.writeFile(path.join(folder, "config.json"), JSON.stringify(config, null, 2), (err) => { if (err) console.error(err) }) 67 | readline.close() 68 | console.log("widget successfully initialized at " + folder + "! You can now edit your project's properties at config.json") 69 | }) 70 | }, 10) 71 | }) 72 | .command("list", "Lists all installed widgets", (yargs) => {}, function (argv) { 73 | installed = get_all_installed_widgets() 74 | installed = installed.map((x) => x.name + (x.config.version ? " --- " + x.config.version : "")) 75 | installed = installed.join("\n") 76 | console.log(installed) 77 | }) 78 | .command("uninstall ", "Uninstall widget", (yargs) => { 79 | yargs.positional("widget", { 80 | type: "string", 81 | describe: "The widget to uninstall" 82 | }) 83 | }, function (argv) { 84 | installed = get_all_installed_widgets() 85 | target = installed.filter((x) => x.name == argv.widget)[0] 86 | fs.rmdirSync(target.path, { recursive: true, force: true }) 87 | console.log(target.name + " successfully uninstalled!") 88 | }) 89 | .command("config ", "Configure widget by name", (yargs) => { 90 | yargs.positional("widget", { 91 | type: "string", 92 | describe: "The widget to configure" 93 | }) 94 | }, function (argv) { 95 | installed = get_all_installed_widgets() 96 | target = installed.filter((x) => x.name == argv.widget)[0] 97 | var exec = require("child_process").exec 98 | try { 99 | exec(`start \"\" \"${path.join(target.path, "config.json")}\"`) 100 | console.log("Now editing config.json of " + target.name) 101 | } 102 | catch (err) { 103 | console.error("Cannot open " + path.join(target.path, "config.json")) 104 | } 105 | }) 106 | .command("start [folder]", "Starts the widget at folder", (yargs) => { 107 | yargs.positional("folder", { 108 | type: "string", 109 | describe: "The folder containing the content to start (includes HTML and optional config.json)" 110 | }) 111 | }, function (argv) { 112 | argv.folder ? folder = argv.folder : folder = path.resolve(".") 113 | console.log("Starting widget at " + folder) 114 | build.build_start(folder) 115 | setTimeout(() => { 116 | var exec = require("child_process").exec 117 | try { 118 | exec(`npm start \"${folder}\"`) 119 | console.log("widget successfully started at " + folder) 120 | } 121 | catch (err) { 122 | console.error("Cannot start " + folder) 123 | } 124 | }, 20) 125 | }) 126 | .help() 127 | .argv 128 | 129 | function get_all_installed_widgets() { 130 | src = process.env.APPDATA + "\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\widgets" 131 | installed = [] 132 | get_directories = (src) => { 133 | return fs.readdirSync(src) 134 | .map((f) => f = path.join(src, f)) 135 | .filter((f) => { return fs.statSync(f).isDirectory() }) 136 | } 137 | directories = get_directories(src) 138 | for (f of directories) { 139 | try { 140 | var config = require(path.join(f, "config.json")) 141 | installed.push({ 142 | "name": config.name, 143 | "path": f, 144 | "filename": path.basename(f), 145 | "config": config 146 | }) 147 | } 148 | catch (error) {} 149 | } 150 | 151 | return installed 152 | } 153 | --------------------------------------------------------------------------------