├── .gitignore ├── assets ├── icon80.png ├── icon130.png ├── apply.sh └── screensaver-main.qml ├── .gitmodules ├── frontend ├── App.js ├── index.js └── views │ ├── MainView.js │ └── MainPanel.js ├── appinfo.json ├── tools ├── sync-version.js └── gen-manifest.js ├── CHANGELOG.md ├── README.md ├── .github └── workflows │ ├── main.yml │ └── release.yml ├── LICENSE ├── package.json └── .enyoconfig /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .enyocache 3 | dist 4 | -------------------------------------------------------------------------------- /assets/icon80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webosbrew/custom-screensaver/HEAD/assets/icon80.png -------------------------------------------------------------------------------- /assets/icon130.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webosbrew/custom-screensaver/HEAD/assets/icon130.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/enyo"] 2 | path = lib/enyo 3 | url = https://github.com/enyojs/enyo.git 4 | -------------------------------------------------------------------------------- /frontend/App.js: -------------------------------------------------------------------------------- 1 | /** 2 | Define your enyo/Application kind in this file. 3 | */ 4 | 5 | var 6 | kind = require('enyo/kind'), 7 | Application = require('enyo/Application'), 8 | MainView = require('./views/MainView'); 9 | 10 | module.exports = kind({ 11 | kind: Application, 12 | view: MainView 13 | }); 14 | -------------------------------------------------------------------------------- /frontend/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | Instantiate your enyo/Application kind in this file. Note, application 3 | rendering should be deferred until the DOM is ready by wrapping it in a 4 | call to ready(). 5 | */ 6 | 7 | var 8 | ready = require('enyo/ready'), 9 | App = require('./App'); 10 | 11 | ready(function () { 12 | new App(); 13 | }); 14 | -------------------------------------------------------------------------------- /appinfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "org.webosbrew.custom-screensaver", 3 | "version": "1.0.1", 4 | "vendor": "webosbrew.org", 5 | "title": "Custom Screensaver", 6 | "icon": "assets/icon80.png", 7 | "largeIcon": "assets/icon130.png", 8 | "main": "index.html", 9 | "iconColor": "#ffffff", 10 | "type": "web", 11 | "appDescription": "The last custom webOS Screensaver you will ever need" 12 | } 13 | -------------------------------------------------------------------------------- /tools/sync-version.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | 5 | const packageInfo = JSON.parse(fs.readFileSync('package.json')); 6 | const appInfo = JSON.parse(fs.readFileSync('appinfo.json')); 7 | 8 | fs.writeFileSync( 9 | 'appinfo.json', 10 | `${JSON.stringify( 11 | { 12 | ...appInfo, 13 | version: packageInfo.version, 14 | }, 15 | null, 16 | 4, 17 | )}\n`, 18 | ); 19 | -------------------------------------------------------------------------------- /assets/apply.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e -o pipefail 4 | 5 | MOUNT_TARGET="/usr/palm/applications/com.webos.app.screensaver/qml/main.qml" 6 | QML_PATH="$(dirname "$(realpath "$0")")/screensaver-main.qml" 7 | 8 | if [[ ! -f "$MOUNT_TARGET" ]]; then 9 | echo "[-] Target file does not exist: $MOUNT_TARGET" >&2 10 | exit 1 11 | fi 12 | 13 | if ! findmnt "$MOUNT_TARGET"; then 14 | mount --bind "$QML_PATH" "$MOUNT_TARGET" 15 | echo "[+] Enabled succesfully" >&2 16 | else 17 | echo "[~] Enabled already" >&2 18 | fi 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [1.0.1] - 2022-01-07 10 | ### Changed 11 | - Replaced logo with an inverted one 12 | 13 | ## [1.0.0] - 2022-01-05 14 | Initial release 15 | 16 | [Unreleased]: https://github.com/webosbrew/custom-screensaver/compare/v1.0.1...HEAD 17 | [1.0.1]: https://github.com/webosbrew/custom-screensaver/compare/v1.0.0...v1.0.1 18 | [1.0.0]: https://github.com/webosbrew/custom-screensaver/releases/tag/v1.0.0 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | custom-screensaver 2 | ================== 3 | 4 | Probably the only custom screensaver you'll ever need on webOS TV. 5 | 6 | Requires root. 7 | 8 | Features 9 | -------- 10 | 11 | * Autostart registration 12 | * Temporary apply 13 | * Launch screensaver immediately for testing 14 | 15 | Installation 16 | ------------ 17 | This should be downloadable in Homebrew Channel. Otherwise, there's an `ipk` in 18 | GitHub releases to the right. You are on your own here. 19 | 20 | Acknowledgments 21 | --------------- 22 | - Original animation POC by an unnamed individual. 23 | - According to WikiMedia Commons - DVD Video logotype does not meet the [threshold of originality](https://commons.wikimedia.org/wiki/Commons:Threshold_of_originality) needed for copyright protection, and is therefore in the public domain. 24 | -------------------------------------------------------------------------------- /frontend/views/MainView.js: -------------------------------------------------------------------------------- 1 | var 2 | kind = require('enyo/kind'), 3 | Panels = require('moonstone/Panels'), 4 | IconButton = require('moonstone/IconButton'), 5 | MainPanel = require('./MainPanel.js'); 6 | 7 | module.exports = kind({ 8 | name: 'myapp.MainView', 9 | classes: 'moon enyo-fit main-view', 10 | components: [ 11 | { 12 | kind: Panels, 13 | pattern: 'activity', 14 | hasCloseButton: false, 15 | wrap: true, 16 | popOnBack: true, 17 | components: [ 18 | { 19 | kind: MainPanel, 20 | }, 21 | ], 22 | onTransitionFinish: 'transitionFinish', 23 | } 24 | ], 25 | create: function () { 26 | this.inherited(arguments); 27 | }, 28 | handlers: { 29 | onRequestPushPanel: 'requestPushPanel', 30 | }, 31 | transitionFinish: function (evt, sender) { 32 | document.title = this.$.panels.getActive().title; 33 | }, 34 | requestPushPanel: function (sender, ev) { 35 | this.$.panels.pushPanel(ev.panel); 36 | }, 37 | }); 38 | -------------------------------------------------------------------------------- /tools/gen-manifest.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const crypto = require('crypto'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | const outfile = process.argv[2]; 8 | const packageinfo = JSON.parse(fs.readFileSync('package.json')); 9 | const appinfo = JSON.parse(fs.readFileSync('appinfo.json')); 10 | const ipkfile = `${appinfo.id}_${appinfo.version}_all.ipk`; 11 | const ipkhash = crypto.createHash('sha256').update(fs.readFileSync(ipkfile)).digest('hex'); 12 | 13 | fs.writeFileSync( 14 | outfile, 15 | JSON.stringify({ 16 | id: appinfo.id, 17 | version: appinfo.version, 18 | type: appinfo.type, 19 | title: appinfo.title, 20 | appDescription: appinfo.appDescription, 21 | iconUri: `https://raw.githubusercontent.com/${url.parse(packageinfo.repository.url).path.substring(1)}/main/assets/icon160.png`, 22 | sourceUrl: packageinfo.repository.url, 23 | rootRequired: true, 24 | ipkUrl: ipkfile, 25 | ipkHash: { 26 | sha256: ipkhash, 27 | }, 28 | }), 29 | ); 30 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Build & Test 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [14.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | with: 25 | submodules: recursive 26 | 27 | - name: Use Node.js ${{ matrix.node-version }} 28 | uses: actions/setup-node@v1 29 | with: 30 | node-version: ${{ matrix.node-version }} 31 | 32 | - run: npm ci 33 | - run: npm run build -- --production 34 | - run: npm run package 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Piotr informatic Dobrowolski 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "org.webosbrew.custom-screensaver", 3 | "version": "1.0.1", 4 | "main": "frontend/index.js", 5 | "moduleDir": "frontend", 6 | "scripts": { 7 | "build": "enyo pack", 8 | "package": "ares-package dist/ -e enyo-ilib", 9 | "manifest": "tools/gen-manifest.js ${npm_package_name}.manifest.json", 10 | "deploy": "ares-install ${npm_package_name}_${npm_package_version}_all.ipk", 11 | "launch": "ares-launch ${npm_package_name}", 12 | "version": "node tools/sync-version.js && git add appinfo.json", 13 | "clean": "rm -rf dist/" 14 | }, 15 | "assets": [ 16 | "appinfo.json", 17 | "assets/**/*.*" 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/webosbrew/custom-screensaver" 22 | }, 23 | "styles": [], 24 | "author": "", 25 | "license": "MIT", 26 | "devDependencies": { 27 | "@webosose/ares-cli": "^2.2.0", 28 | "enyo-dev": "^1.0.0" 29 | }, 30 | "dependencies": { 31 | "dompurify": "^2.3.4", 32 | "enyo-ilib": "git+https://github.com/jaycanuck/enyo-ilib.git", 33 | "enyo-webos": "git+https://github.com/enyojs/enyo-webos.git", 34 | "layout": "git+https://github.com/enyojs/layout.git", 35 | "moonstone": "git+https://github.com/enyojs/moonstone.git", 36 | "spotlight": "git+https://github.com/enyojs/spotlight.git", 37 | "svg": "git+https://github.com/enyojs/svg.git" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.enyoconfig: -------------------------------------------------------------------------------- 1 | { 2 | "title": "", 3 | "libDir": "lib", 4 | "paths": [], 5 | "libraries": [ 6 | "enyo", 7 | "moonstone", 8 | "spotlight", 9 | "layout", 10 | "svg", 11 | "enyo-ilib", 12 | "enyo-webos" 13 | ], 14 | "sources": { 15 | "enyo": "https://github.com/enyojs/enyo.git", 16 | "moonstone": "https://github.com/enyojs/moonstone.git", 17 | "spotlight": "https://github.com/enyojs/spotlight.git", 18 | "layout": "https://github.com/enyojs/layout.git", 19 | "svg": "https://github.com/enyojs/svg.git", 20 | "enyo-ilib": "https://github.com/enyojs/enyo-ilib.git", 21 | "enyo-webos": "https://github.com/enyojs/enyo-webos.git" 22 | }, 23 | "targets": { 24 | "enyo": "2.7.0", 25 | "moonstone": "2.7.0", 26 | "spotlight": "2.7.0", 27 | "layout": "2.7.0", 28 | "svg": "2.7.0", 29 | "enyo-ilib": "2.7.0", 30 | "enyo-webos": "2.7.0" 31 | }, 32 | "production": false, 33 | "devMode": true, 34 | "cache": true, 35 | "resetCache": false, 36 | "trustCache": false, 37 | "cacheFile": ".enyocache", 38 | "clean": false, 39 | "sourceMaps": true, 40 | "externals": true, 41 | "strict": false, 42 | "skip": [], 43 | "library": false, 44 | "wip": false, 45 | "outDir": "dist", 46 | "outFile": "index.html", 47 | "lessPlugins": [ 48 | { 49 | "name": "resolution-independence", 50 | "options": { 51 | "baseSize": 24 52 | } 53 | } 54 | ], 55 | "assetRoots": [], 56 | "lessOnlyLess": false, 57 | "minifyCss": false, 58 | "inlineCss": true, 59 | "outCssFile": "output.css", 60 | "outJsFile": "output.js", 61 | "inlineJs": true, 62 | "templateIndex": "", 63 | "watch": false, 64 | "watchPaths": [], 65 | "polling": false, 66 | "pollingInterval": 100, 67 | "headScripts": [], 68 | "tailScripts": [], 69 | "promisePolyfill": false, 70 | "styleOnly": false, 71 | "lessVars": [] 72 | } 73 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Build & Release 5 | 6 | on: 7 | push: 8 | branches: 9 | - '!*' 10 | tags: 11 | - 'v*.*' 12 | 13 | jobs: 14 | build-and-release: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | with: 20 | submodules: recursive 21 | 22 | - name: Use Node.js 14.x 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: 14.x 26 | 27 | - run: npm ci 28 | - run: npm run build -- --production 29 | - run: npm run package 30 | - run: echo RELEASE_FILENAME_IPK=`ls *.ipk` >> $GITHUB_ENV 31 | - run: npm run manifest 32 | 33 | - name: Create Release 34 | id: create_release 35 | uses: actions/create-release@v1 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | with: 39 | tag_name: ${{ github.ref }} 40 | release_name: Release ${{ github.ref }} 41 | draft: false 42 | prerelease: true 43 | 44 | - name: Upload IPK asset 45 | id: upload-ipk-asset 46 | uses: actions/upload-release-asset@v1 47 | env: 48 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 49 | with: 50 | upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps 51 | asset_path: ${{github.workspace}}/${{env.RELEASE_FILENAME_IPK}} 52 | asset_name: ${{env.RELEASE_FILENAME_IPK}} 53 | asset_content_type: application/vnd.debian.binary-package 54 | 55 | - name: Upload Manifest asset 56 | id: upload-manifest-asset 57 | uses: actions/upload-release-asset@v1 58 | env: 59 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 60 | with: 61 | upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps 62 | asset_path: ${{github.workspace}}/org.webosbrew.custom-screensaver.manifest.json 63 | asset_name: org.webosbrew.custom-screensaver.manifest.json 64 | asset_content_type: application/json 65 | -------------------------------------------------------------------------------- /frontend/views/MainPanel.js: -------------------------------------------------------------------------------- 1 | var 2 | kind = require('enyo/kind'), 3 | Panel = require('moonstone/Panel'), 4 | FittableRows = require('layout/FittableRows'), 5 | FittableColumns = require('layout/FittableColumns'), 6 | BodyText = require('moonstone/BodyText'), 7 | Marquee = require('moonstone/Marquee'), 8 | LunaService = require('enyo-webos/LunaService'), 9 | Divider = require('moonstone/Divider'), 10 | Scroller = require('moonstone/Scroller'), 11 | Item = require('moonstone/Item'), 12 | ToggleItem = require('moonstone/ToggleItem'), 13 | Group = require('enyo/Group'); 14 | 15 | var basePath = "/media/developer/apps/usr/palm/applications/org.webosbrew.custom-screensaver"; 16 | var applyPath = basePath + "/assets/apply.sh"; 17 | var linkPath = "/var/lib/webosbrew/init.d/50-custom-screensaver"; 18 | module.exports = kind({ 19 | name: 'MainPanel', 20 | kind: Panel, 21 | title: 'webOS Custom Screensaver', 22 | titleBelow: "webosbrew.org", 23 | headerType: 'medium', 24 | components: [ 25 | {kind: FittableColumns, classes: 'enyo-center', fit: true, components: [ 26 | {kind: Scroller, fit: true, components: [ 27 | {classes: 'moon-hspacing', controlClasses: 'moon-12h', components: [ 28 | {components: [ 29 | // {kind: Divider, content: 'Toggle Items'}, 30 | {kind: ToggleItem, name: "autostart", content: 'Autostart', checked: true, disabled: true, onchange: "autostartToggle"}, 31 | {kind: Item, components: [ 32 | {kind: Marquee.Text, content: 'Apply temporarily'}, 33 | {kind: BodyText, style: 'margin: 10px 0', content: 'This will only enable custom screensaver until a reboot'}, 34 | ], ontap: "temporaryApply"}, 35 | {kind: Item, content: 'Test run screensaver', ontap: "testRun"}, 36 | ]}, 37 | ]}, 38 | ]}, 39 | ]}, 40 | {components: [ 41 | {kind: Divider, content: 'Result'}, 42 | {kind: BodyText, name: 'result', content: 'Nothing selected...'} 43 | ]}, 44 | {kind: LunaService, name: 'statusCheck', service: 'luna://org.webosbrew.hbchannel.service', method: 'exec', onResponse: 'onStatusCheck', onError: 'onStatusCheck'}, 45 | {kind: LunaService, name: 'exec', service: 'luna://org.webosbrew.hbchannel.service', method: 'exec', onResponse: 'onExec', onError: 'onExec'}, 46 | ], 47 | 48 | bindings: [], 49 | 50 | create: function () { 51 | this.inherited(arguments); 52 | this.$.statusCheck.send({ 53 | command: 'readlink ' + linkPath, 54 | }); 55 | }, 56 | 57 | testRun: function (command) { 58 | this.exec("luna-send -n 1 'luna://com.webos.service.tvpower/power/turnOnScreenSaver' '{}'"); 59 | }, 60 | 61 | temporaryApply: function (command) { 62 | this.exec(applyPath); 63 | }, 64 | 65 | exec: function (command) { 66 | console.info(command); 67 | this.$.result.set('content', 'Processing...'); 68 | this.$.exec.send({ 69 | command: command, 70 | }); 71 | }, 72 | 73 | onExec: function (sender, evt) { 74 | console.info(evt); 75 | if (evt.returnValue) { 76 | this.$.result.set('content', 'Success!
' + evt.stdoutString + evt.stderrString); 77 | } else { 78 | this.$.result.set('content', 'Failed: ' + evt.errorText + ' ' + evt.stdoutString + evt.stderrString); 79 | } 80 | }, 81 | 82 | onStatusCheck: function (sender, evt) { 83 | console.info(sender, evt); 84 | // this.$.result.set('content', JSON.stringify(evt.data)); 85 | this.$.autostart.set('disabled', false); 86 | this.$.autostart.set('checked', evt.stdoutString && evt.stdoutString.trim() == applyPath); 87 | }, 88 | 89 | autostartToggle: function (sender) { 90 | console.info("toggle:", sender); 91 | 92 | if (sender.active) { 93 | this.exec('mkdir -p /var/lib/webosbrew/init.d && ln -sf ' + applyPath + ' ' + linkPath); 94 | } else { 95 | this.exec('rm -rf ' + linkPath); 96 | } 97 | }, 98 | }); 99 | -------------------------------------------------------------------------------- /assets/screensaver-main.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Nostalgia screensaver. 3 | * 4 | * Usage: 5 | * mount --bind ./screensaver-main.qml /usr/palm/applications/com.webos.app.screensaver/qml/main.qml 6 | * 7 | * Test launch (no way to trigger on "No signal" screen) 8 | * luna-send -n 1 'luna://com.webos.service.tvpower/power/turnOnScreenSaver' '{}' 9 | */ 10 | import QtQuick 2.4 11 | import Eos.Window 0.1 12 | import QtQuick.Window 2.2 13 | 14 | WebOSWindow { 15 | id: window 16 | 17 | width: 1920 18 | height: 1080 19 | 20 | windowType: "_WEBOS_WINDOW_TYPE_SCREENSAVER" 21 | color: "black" 22 | 23 | title: "Screensaver" 24 | appId: "com.webos.app.screensaver" 25 | visible: true 26 | 27 | Item { 28 | id: root 29 | 30 | Rectangle { 31 | anchors.fill: parent 32 | color: 'black' 33 | } 34 | 35 | Rectangle { 36 | id: boing 37 | 38 | color: 'black' 39 | width: 320 40 | height: 163 41 | 42 | function setRandomColor() { 43 | var colors = ['#f00', '#0f0', '#00f', '#ff0', '#f0f', '#0ff', '#fff']; 44 | var index = (Math.random() * colors.length) | 0; 45 | boing.color = colors[index]; 46 | } 47 | 48 | Image { 49 | anchors.fill: parent 50 | source: 'data:image/png;base64,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' } 51 | 52 | Component.onCompleted: { 53 | boing.setRandomColor(); 54 | } 55 | 56 | SequentialAnimation on x { 57 | loops: Animation.Infinite 58 | PropertyAnimation { 59 | easing.type: Easing.Linear 60 | duration: 1920 * 11 61 | to: 1920 - boing.width 62 | } 63 | 64 | ScriptAction { script: boing.setRandomColor(); } 65 | PropertyAnimation { 66 | easing.type: Easing.Linear 67 | duration: 1920 * 11 68 | to: 0 69 | } 70 | ScriptAction { script: boing.setRandomColor(); } 71 | } 72 | 73 | SequentialAnimation on y { 74 | loops: Animation.Infinite 75 | PropertyAnimation { 76 | easing.type: Easing.Linear 77 | duration: 1080 * 7 78 | to: 1080 - boing.height 79 | } 80 | ScriptAction { script: boing.setRandomColor(); } 81 | PropertyAnimation { 82 | easing.type: Easing.Linear 83 | duration: 1080 * 7 84 | to: 0 85 | } 86 | ScriptAction { script: boing.setRandomColor(); } 87 | } 88 | } 89 | } 90 | } 91 | --------------------------------------------------------------------------------