diff --git a/app.mk b/app.mk
new file mode 100644
index 0000000..77701cd
--- /dev/null
+++ b/app.mk
@@ -0,0 +1,1035 @@
+#########################################################################
+# app.mk: common include file for application Makefiles.
+#
+# The client Makefile:
+#
+# 1) must set APPNAME to an app ID, typically the app name encoded as
+# a unique identifier suitable for a file/directory name
+# (i.e. alphanumeric, no spaces or punctuation).
+#
+# 2) may set IMPORTS, to a list of .brs base file names to be used from
+# the common utilities.
+#
+# Makefile common usage:
+# > make
+# > make run
+# > make install
+# > make remove
+#
+# Makefile less common usage:
+# > make art-opt
+# > make pkg
+# > make install-native
+# > make remove-native
+# > make tr
+#
+# Important Notes:
+# To use the "run", "install" and "remove" targets to install your
+# application directly from the shell, you must do the following:
+#
+# 1) Make sure that you have the curl command line executable in your path.
+# 2) Set the variable ROKU_DEV_TARGET in your environment to the IP
+# address of your Roku box, e.g.
+# export ROKU_DEV_TARGET=192.168.1.1
+# 3) Set the variable DEVPASSWORD in your environment with the developer
+# password that you have set for your Roku box, e.g.
+# export DEVPASSWORD=mypassword
+# (If you don't set this, you will be prompted for every install command.)
+##########################################################################
+
+##########################################################################
+# Specifying application files to be packaged:
+#
+# By default, ZIP_EXCLUDE will exclude well-known source directories and
+# files that should typically not be included in the application
+# distribution.
+#
+# If you want to entirely override the default settings, you can put your
+# own definition of ZIP_EXCLUDE in your Makefile.
+#
+# Example:
+# ZIP_EXCLUDE= -x keys\*
+# will exclude all files from the keys directory (and only those files).
+#
+# To exclude using more than one pattern, use additional '-x '
+# arguments, e.g.
+# ZIP_EXCLUDE= -x \*.pkg -x storeassets\*
+#
+# If you just need to add additional files to the ZIP_EXCLUDE list, you can
+# define ZIP_EXCLUDE_LOCAL in your Makefile. This pattern will be appended
+# to the default ZIP_EXCLUDE pattern.
+#
+# Example:
+# ZIP_EXCLUDE_LOCAL= -x goldens\*
+##########################################################################
+
+# improve performance and simplify Makefile debugging by omitting
+# default language rules that don't apply to this environment.
+MAKEFLAGS += --no-builtin-rules
+.SUFFIXES:
+
+# we don't want targets to be made in parallel
+.NOTPARALLEL:
+
+##########################################################################
+
+IS_TEAMCITY_BUILD ?=
+ifneq ($(TEAMCITY_BUILDCONF_NAME),)
+IS_TEAMCITY_BUILD := true
+endif
+
+HOST_OS := unknown
+UNAME_S := $(shell uname -s)
+ifeq ($(UNAME_S),Darwin)
+ HOST_OS := macos
+else ifeq ($(UNAME_S),Linux)
+ HOST_OS := linux
+else ifneq (,$(findstring CYGWIN,$(UNAME_S)))
+ HOST_OS := cygwin
+endif
+
+# Use native file extensions for executables
+ifeq ($(HOST_OS),cygwin)
+HOST_EXE_SUFFIX := .exe
+else
+HOST_EXE_SUFFIX :=
+endif
+
+# Use native file extensions for executables
+ifeq ($(HOST_OS),cygwin)
+MAKE_HOST_PATH = $(shell cygpath -m "$1")
+else
+MAKE_HOST_PATH = $1
+endif
+
+# We want to be able to use escape sequences with echo
+ifeq ($(HOST_OS),macos)
+ECHO := echo
+else
+ECHO := echo -e
+endif
+
+# get the root directory in absolute form, so that current directory
+# can be changed during the make if needed.
+APPS_ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
+
+# the current directory is the app root directory
+SOURCEDIR := .
+
+DISTREL := $(APPS_ROOT_DIR)/dist
+COMMONREL := $(APPS_ROOT_DIR)/common
+
+ZIPREL := $(DISTREL)/apps
+PKGREL := $(DISTREL)/packages
+CHECK_TMP_DIR := $(DISTREL)/tmp-check
+
+DATE_TIME := $(shell date +%F-%T)
+
+ifeq ($(APPNAME),)
+$(error ERROR: APPNAME is not set.)
+endif
+
+APP_ZIP_FILE := $(ZIPREL)/$(APPNAME).zip
+APP_PKG_FILE := $(PKGREL)/$(APPNAME)_$(DATE_TIME).pkg
+
+# these variables are only used for the .pkg file version tagging.
+APP_NAME := $(APPNAME)
+APP_VERSION := $(VERSION)
+ifeq ($(IS_TEAMCITY_BUILD),true)
+APP_NAME := $(subst /,-,$(TEAMCITY_BUILDCONF_NAME))
+APP_VERSION := $(BUILD_NUMBER)
+endif
+
+APPSOURCEDIR := $(SOURCEDIR)/source
+IMPORTFILES := $(foreach f,$(sort $(IMPORTS)),$f.brs)
+
+APP_LIBSTUB_DIR := $(SOURCEDIR)/libstub
+
+# ROKU_NATIVE_DEV must be set in the calling environment to
+# the firmware native-build src directory
+NATIVE_DIST_DIR := $(ROKU_NATIVE_DEV)/dist
+#
+NATIVE_DEV_REL := $(NATIVE_DIST_DIR)/rootfs/Linux86_dev.OBJ/root/nvram/incoming
+NATIVE_DEV_PKG := $(NATIVE_DEV_REL)/dev.zip
+NATIVE_PLETHORA := $(NATIVE_DIST_DIR)/application/Linux86_dev.OBJ/root/bin/plethora
+NATIVE_TICKLER := $(NATIVE_PLETHORA) tickle-plugin-installer
+
+APPS_SCRIPTS_DIR := $(APPS_ROOT_DIR)/tools/scripts
+
+# only Linux host is supported for these tools currently
+APPS_TOOLS_DIR := $(APPS_ROOT_DIR)/tools/$(HOST_OS)/bin
+
+APP_PACKAGE_TOOL ?= $(APPS_TOOLS_DIR)/app-package$(HOST_EXE_SUFFIX)
+MAKE_TR_TOOL ?= $(APPS_TOOLS_DIR)/maketr$(HOST_EXE_SUFFIX)
+BRIGHTSCRIPT_TOOL ?= $(APPS_TOOLS_DIR)/brightscript$(HOST_EXE_SUFFIX)
+
+CHECK_MANIFEST_TOOL ?= $(APPS_SCRIPTS_DIR)/check-manifest.pl
+CHECK_THEME_TOOL ?= $(APPS_SCRIPTS_DIR)/check-theme.pl
+
+# if building from a firmware tree, use the BrightScript libraries from there
+ifneq (,$(wildcard $(APPS_ROOT_DIR)/../3rdParty/brightscript/Scripts/LibCore/.))
+BRIGHTSCRIPT_LIBS_DIR ?= $(APPS_ROOT_DIR)/../3rdParty/brightscript/Scripts/LibCore
+endif
+# else use the reference libraries from the tools directory.
+BRIGHTSCRIPT_LIBS_DIR ?= $(APPS_ROOT_DIR)/tools/brightscript/Scripts/LibCore
+
+APP_KEY_PASS_TMP := /tmp/app_key_pass
+DEV_SERVER_TMP_FILE := /tmp/dev_server_out
+
+# The developer password that was set on the player is required for
+# plugin_install operations on modern versions of firmware.
+# It may be pre-specified in the DEVPASSWORD environment variable on entry,
+# otherwise the make will stop and prompt the user to enter it when needed.
+ifdef DEVPASSWORD
+ USERPASS := rokudev:$(DEVPASSWORD)
+else
+ USERPASS := rokudev
+endif
+
+ifeq ($(HOST_OS),macos)
+ # Mac doesn't support these args
+ CP_ARGS =
+else
+ CP_ARGS = --preserve=ownership,timestamps --no-preserve=mode
+endif
+
+# For a quick ping, we want the command to return success as soon as possible,
+# and a timeout failure in no more than a second or two.
+ifeq ($(HOST_OS),cygwin)
+ # This assumes that the Windows ping command is used, not cygwin's.
+ QUICK_PING_ARGS = -n 1 -w 1000
+else ifeq ($(HOST_OS),macos)
+ QUICK_PING_ARGS = -c 1 -t 1
+else # Linux
+ QUICK_PING_ARGS = -c 1 -w 1
+endif
+
+ifndef ZIP_EXCLUDE
+ ZIP_EXCLUDE =
+ # exclude hidden files (name starting with .)
+ ZIP_EXCLUDE += -x .\*
+ ZIP_EXCLUDE += -x \*/.\*
+ # exclude shell scripts (typically top-level build helpers)
+ ZIP_EXCLUDE += -x \*.sh
+ # exclude files with name ending with ~
+ ZIP_EXCLUDE += -x \*~
+ ZIP_EXCLUDE += -x \*.mod
+ ZIP_EXCLUDE += -x \*.pkg
+ ZIP_EXCLUDE += -x \*.zip
+ ZIP_EXCLUDE += -x Makefile
+ ZIP_EXCLUDE += -x keys/\*
+ ZIP_EXCLUDE += -x libapi/\*
+ ZIP_EXCLUDE += -x libstub/\*
+ ZIP_EXCLUDE += -x storeassets/\*
+ # exclude Mac OS X desktop metadata
+ ZIP_EXCLUDE += -x \*__MACOSX\*
+ ZIP_EXCLUDE += -x \*.DS_Store
+endif
+
+ZIP_EXCLUDE_PATTERN = $(ZIP_EXCLUDE)
+ZIP_EXCLUDE_PATTERN += $(ZIP_EXCLUDE_LOCAL)
+
+ZIP_ARGS :=
+
+#ifneq ($(APP_VERBOSE_ARCHIVE),true)
+# ZIP_ARGS += -q
+#endif
+
+ifeq ($(APP_QUIET_ARCHIVE),true)
+ ZIP_ARGS += -q
+endif
+
+# -------------------------------------------------------------------------
+# Colorized output support.
+# If you don't want it, do 'export APP_MK_COLOR=false' in your env.
+# -------------------------------------------------------------------------
+ifndef APP_MK_COLOR
+APP_MK_COLOR := false
+ifeq ($(TERM),$(filter $(TERM),xterm xterm-color xterm-256color))
+ APP_MK_COLOR := true
+endif
+endif
+
+COLOR_START :=
+COLOR_INFO :=
+COLOR_PROMPT :=
+COLOR_DONE :=
+COLOR_ERR :=
+COLOR_OFF :=
+
+ifeq ($(APP_MK_COLOR),true)
+ # ANSI color escape codes:
+
+ # \e[0;30m black
+ # \e[0;31m red
+ # \e[0;32m green
+ # \e[0;33m yellow
+ # \e[0;34m blue
+ # \e[0;35m magenta
+ # \e[0;36m cyan
+ # \e[0;37m light gray
+
+ # \e[1;30m gray
+ # \e[1;31m light red
+ # \e[1;32m light green
+ # \e[1;33m light yellow
+ # \e[1;34m light blue
+ # \e[1;35m light purple
+ # \e[1;36m light cyan
+ # \e[1;37m white
+
+ COLOR_START := \033[1;36m
+ COLOR_INFO := \033[1;35m
+ COLOR_PROMPT := \033[0;31m
+ COLOR_DONE := \033[1;32m
+ COLOR_ERROR := \033[1;31m
+ COLOR_OFF := \033[0m
+endif
+
+# empty if not theme
+APP_IS_THEME := $(shell grep '^theme=' manifest)
+
+# -------------------------------------------------------------------------
+# $(APPNAME): the default target is to create the zip file for the app.
+# This contains the set of files that are to be deployed on a Roku.
+# -------------------------------------------------------------------------
+.PHONY: $(APPNAME)
+$(APPNAME): manifest
+ @$(ECHO) "$(COLOR_START)**** Creating $(APPNAME).zip ****$(COLOR_OFF)"
+
+ @$(ECHO) " >> removing old application zip $(APP_ZIP_FILE)"
+ @if [ -e "$(APP_ZIP_FILE)" ]; then \
+ rm $(APP_ZIP_FILE); \
+ fi
+
+ @$(ECHO) " >> creating destination directory $(ZIPREL)"
+ @if [ ! -d $(ZIPREL) ]; then \
+ mkdir -p $(ZIPREL); \
+ fi
+
+ @$(ECHO) " >> setting directory permissions for $(ZIPREL)"
+ @if [ ! -w $(ZIPREL) ]; then \
+ chmod 755 $(ZIPREL); \
+ fi
+
+ @if [ "$(IMPORTFILES)" ]; then \
+ $(ECHO) " >> copying imports"; \
+ rm -rf $(APPSOURCEDIR)/common; \
+ mkdir $(APPSOURCEDIR)/common; \
+ for IMPORTFILE in $(IMPORTFILES); do \
+ echo " copying: common/$$IMPORTFILE"; \
+ cp -f $(CP_ARGS) $(COMMONREL)/$$IMPORTFILE $(APPSOURCEDIR)/common/; \
+ done; \
+ fi
+
+# Note: zip .png files without compression
+# FIXME: shouldn't it exclude .jpg too?
+# FIXME: if no .png files are found, outputs bogus "zip warning: zip file empty"
+ @$(ECHO) " >> creating application zip $(APP_ZIP_FILE)"
+ @if [ -d $(SOURCEDIR) ]; then \
+ zip $(ZIP_ARGS) -0 -r "$(APP_ZIP_FILE)" . -i \*.png $(ZIP_EXCLUDE_PATTERN); \
+ zip $(ZIP_ARGS) -9 -r "$(APP_ZIP_FILE)" . -x \*.png $(ZIP_EXCLUDE_PATTERN); \
+ else \
+ $(ECHO) "$(COLOR_ERROR)Source for $(APPNAME) not found at $(SOURCEDIR)$(COLOR_OFF)"; \
+ fi
+
+ @if [ "$(IMPORTFILES)" ]; then \
+ $(ECHO) " >> deleting imports"; \
+ rm -rf $(APPSOURCEDIR)/common; \
+ fi
+
+# If DISTDIR is not empty then copy the zip package to the DISTDIR.
+# Note that this is used by the firmware build, to build applications that are
+# embedded in the firmware software image, such as the built-in screensaver.
+# For those cases, the Netflix/Makefile calls this makefile for each app
+# with DISTDIR and DISTZIP set to the target directory and base filename
+# respectively.
+ @if [ $(DISTDIR) ]; then \
+ rm -f $(DISTDIR)/$(DISTZIP).zip; \
+ mkdir -p $(DISTDIR); \
+ cp -f --preserve=ownership,timestamps --no-preserve=mode \
+ $(APP_ZIP_FILE) $(DISTDIR)/$(DISTZIP).zip; \
+ fi
+
+ @$(ECHO) "$(COLOR_DONE)**** packaging $(APPNAME) complete ****$(COLOR_OFF)"
+
+# -------------------------------------------------------------------------
+# clean: remove any build output for the app.
+# -------------------------------------------------------------------------
+.PHONY: clean
+clean:
+ rm -f $(APP_ZIP_FILE)
+# FIXME: we should use a canonical output file name, rather than having
+# the date-time stamp in the output file name.
+# rm -f $(APP_PKG_FILE)
+ rm -f $(PKGREL)/$(APPNAME)_*.pkg
+
+# -------------------------------------------------------------------------
+# clobber: remove any build output for the app.
+# -------------------------------------------------------------------------
+.PHONY: clobber
+clobber: clean
+
+# -------------------------------------------------------------------------
+# dist-clean: remove the dist directory for the sandbox.
+# -------------------------------------------------------------------------
+.PHONY: dist-clean
+dist-clean:
+ rm -rf $(DISTREL)/*
+
+# -------------------------------------------------------------------------
+# CHECK_OPTIONS: this is used to specify configurable options, such
+# as which version of the BrightScript library sources should be used
+# to compile the app.
+# -------------------------------------------------------------------------
+CHECK_OPTIONS =
+
+# add the path to the common/LibCore sources, if available
+ifneq (,$(wildcard $(BRIGHTSCRIPT_LIBS_DIR)/.))
+ CHECK_OPTIONS += -lib $(call MAKE_HOST_PATH,$(BRIGHTSCRIPT_LIBS_DIR))
+endif
+
+# if the app uses BS libraries, it can provide stub libraries to compile with.
+ifneq (,$(wildcard $(APP_LIBSTUB_DIR)/.))
+ CHECK_OPTIONS += -applib $(call MAKE_HOST_PATH,$(APP_LIBSTUB_DIR))
+endif
+
+ifneq ($(APP_IS_LIBRARY),)
+ # if the app is a BS library, let it find auxiliary internal sources
+ CHECK_OPTIONS += -applib $(call MAKE_HOST_PATH,$(CHECK_TMP_DIR)/libsource)
+endif
+
+ifneq ($(APP_IS_LIBRARY),)
+ # if the app is a BS library, we only compile the specific .brs file
+ CHECK_OPTIONS += $(call MAKE_HOST_PATH,$(CHECK_TMP_DIR)/libsource/$(APP_IS_LIBRARY))
+else
+ # else implicitly check all the .brs files in the source directory
+ CHECK_OPTIONS += $(call MAKE_HOST_PATH,$(CHECK_TMP_DIR))
+endif
+
+# -------------------------------------------------------------------------
+# CHECK_TMP_BEGIN is used to create a temporary directory containing
+# a copy of the app archive contents.
+# -------------------------------------------------------------------------
+define CHECK_TMP_BEGIN
+ rm -rf $(CHECK_TMP_DIR)
+ mkdir -p $(CHECK_TMP_DIR)
+ unzip -q $(APP_ZIP_FILE) -d $(CHECK_TMP_DIR)
+endef
+
+# -------------------------------------------------------------------------
+# CHECK_TMP_END is used to remove the temporary directory created by
+# CHECK_TMP_BEGIN.
+# -------------------------------------------------------------------------
+define CHECK_TMP_END
+ rm -rf $(CHECK_TMP_DIR)
+endef
+
+# -------------------------------------------------------------------------
+# check-manifest: run the check tool on the application manifest and
+# any referenced asset files.
+# -------------------------------------------------------------------------
+.PHONY: check-manifest
+check-manifest: $(APPNAME)
+ifeq ($(APP_CHECK_MANIFEST_DISABLED),true)
+ifeq ($(IS_TEAMCITY_BUILD),true)
+ @$(ECHO) "**** Warning: manifest check skipped ****"
+endif
+else
+ifeq ($(wildcard $(CHECK_MANIFEST_TOOL)),)
+ @$(ECHO) "**** Note: manifest check not available ****"
+else
+ @$(ECHO) "$(COLOR_START)**** Checking manifest ****$(COLOR_OFF)"
+ @$(CHECK_TMP_BEGIN)
+ @$(CHECK_MANIFEST_TOOL)
+ @$(CHECK_TMP_END)
+ @$(ECHO) "$(COLOR_DONE)**** Checking complete ****$(COLOR_OFF)"
+endif
+endif
+
+# -------------------------------------------------------------------------
+# check: run the desktop BrightScript compiler/check tool on the
+# application.
+# You can bypass checking on the application by setting
+# APP_CHECK_DISABLED=true in the app's Makefile or in the environment.
+# -------------------------------------------------------------------------
+.PHONY: check
+check: $(APPNAME) check-manifest
+ifeq ($(APP_CHECK_DISABLED),true)
+ifeq ($(IS_TEAMCITY_BUILD),true)
+ @$(ECHO) "**** Warning: application check skipped ****"
+endif
+else
+ifeq ($(wildcard $(BRIGHTSCRIPT_TOOL)),)
+ @$(ECHO) "**** Note: application check not available ****"
+else
+ @$(ECHO) "$(COLOR_START)**** Checking application ****$(COLOR_OFF)"
+ @$(CHECK_TMP_BEGIN)
+ @$(BRIGHTSCRIPT_TOOL) check $(CHECK_OPTIONS)
+ @$(CHECK_TMP_END)
+ @$(ECHO) "$(COLOR_DONE)**** Checking complete ****$(COLOR_OFF)"
+endif
+endif
+
+# -------------------------------------------------------------------------
+# check-strict: run the desktop BrightScript compiler/check tool on the
+# application using strict mode.
+# -------------------------------------------------------------------------
+.PHONY: check-strict
+check-strict: $(APPNAME)
+ @$(ECHO) "$(COLOR_START)**** Checking application (strict) ****$(COLOR_OFF)"
+ @$(CHECK_TMP_BEGIN)
+ @$(BRIGHTSCRIPT_TOOL) check -strict $(CHECK_OPTIONS)
+ @$(CHECK_TMP_END)
+ @$(ECHO) "$(COLOR_DONE)**** Checking complete ****$(COLOR_OFF)"
+
+# -------------------------------------------------------------------------
+# check-info: run the desktop BrightScript compiler/check tool on the
+# application to print out some summary info (currently just function listing).
+# -------------------------------------------------------------------------
+.PHONY: check-info
+check-info: $(APPNAME)
+ @$(ECHO) "$(COLOR_START)**** Dumping application info ****$(COLOR_OFF)"
+ @$(CHECK_TMP_BEGIN)
+ @$(BRIGHTSCRIPT_TOOL) info $(CHECK_OPTIONS)
+ @$(CHECK_TMP_END)
+ @$(ECHO) "$(COLOR_DONE)**** Checking complete ****$(COLOR_OFF)"
+
+# -------------------------------------------------------------------------
+# check-theme: check the theme xml and assets.
+# -------------------------------------------------------------------------
+.PHONY: check-theme
+check-theme: $(APPNAME)
+ifneq ($(APP_IS_THEME),)
+ifeq ($(APP_CHECK_THEME_DISABLED),true)
+else
+ @$(ECHO) "$(COLOR_START)**** Checking theme. ****$(COLOR_OFF)"
+ @$(CHECK_TMP_BEGIN)
+ @-cd $(CHECK_TMP_DIR) && $(CHECK_THEME_TOOL)
+ @$(CHECK_TMP_END)
+ @$(ECHO) "$(COLOR_DONE)**** Checking complete ****$(COLOR_OFF)"
+endif
+else
+ @$(ECHO) "$(COLOR_DONE)**** app is not theme ****$(COLOR_OFF)"
+endif
+
+# -------------------------------------------------------------------------
+# GET_FRIENDLY_NAME_FROM_DD is used to extract the Roku device ID
+# from the ECP device description XML response.
+# -------------------------------------------------------------------------
+define GET_FRIENDLY_NAME_FROM_DD
+ cat $(DEV_SERVER_TMP_FILE) | \
+ grep -o ".*" | \
+ sed "s|||" | \
+ sed "s|||"
+endef
+
+# -------------------------------------------------------------------------
+# CHECK_ROKU_DEV_TARGET is used to check if ROKU_DEV_TARGET refers a
+# Roku device on the network that has an enabled developer web server.
+# If the target doesn't exist or doesn't have an enabled web server
+# the connection should fail.
+# -------------------------------------------------------------------------
+define CHECK_ROKU_DEV_TARGET
+ if [ -z "$(ROKU_DEV_TARGET)" ]; then \
+ $(ECHO) "$(COLOR_ERROR)ERROR: ROKU_DEV_TARGET is not set.$(COLOR_OFF)"; \
+ exit 1; \
+ fi
+ $(ECHO) "$(COLOR_START)Checking dev server at $(ROKU_DEV_TARGET)...$(COLOR_OFF)"
+
+ # first check if the device is on the network via a quick ping
+ ping $(QUICK_PING_ARGS) $(ROKU_DEV_TARGET) &> $(DEV_SERVER_TMP_FILE) || \
+ ( \
+ $(ECHO) "$(COLOR_ERROR)ERROR: Device is not responding to ping.$(COLOR_OFF)"; \
+ exit 1 \
+ )
+
+ # second check ECP, to verify we are talking to a Roku
+ rm -f $(DEV_SERVER_TMP_FILE)
+ curl --connect-timeout 2 --silent --output $(DEV_SERVER_TMP_FILE) \
+ http://$(ROKU_DEV_TARGET):8060 || \
+ ( \
+ $(ECHO) "$(COLOR_ERROR)ERROR: Device is not responding to ECP...is it a Roku?$(COLOR_OFF)"; \
+ exit 1 \
+ )
+
+ # print the device friendly name to let us know what we are talking to
+ ROKU_DEV_NAME=`$(GET_FRIENDLY_NAME_FROM_DD)`; \
+ $(ECHO) "$(COLOR_INFO)Device reports as \"$$ROKU_DEV_NAME\".$(COLOR_OFF)"
+
+ # third check dev web server.
+ # Note, it should return 401 Unauthorized since we aren't passing the password.
+ rm -f $(DEV_SERVER_TMP_FILE)
+ HTTP_STATUS=`curl --connect-timeout 2 --silent --output $(DEV_SERVER_TMP_FILE) \
+ http://$(ROKU_DEV_TARGET)` || \
+ ( \
+ $(ECHO) "$(COLOR_ERROR)ERROR: Device server is not responding...$(COLOR_OFF)"; \
+ $(ECHO) "$(COLOR_ERROR)is the developer installer enabled?$(COLOR_OFF)"; \
+ $(ECHO) "$(COLOR_ERROR)3H 2U R L R L R$(COLOR_OFF)"; \
+ exit 1 \
+ )
+
+ $(ECHO) "$(COLOR_DONE)Dev server is ready.$(COLOR_OFF)"
+endef
+
+# -------------------------------------------------------------------------
+# CHECK_ROKU_DEV_PASSWORD is used to let the user know they might want to set
+# their DEVPASSWORD environment variable.
+# -------------------------------------------------------------------------
+define CHECK_ROKU_DEV_PASSWORD
+ if [ -z "$(DEVPASSWORD)" ]; then \
+ $(ECHO) "Note: DEVPASSWORD is not set."; \
+ fi
+endef
+
+# -------------------------------------------------------------------------
+# CHECK_DEVICE_HTTP_STATUS is used to that the last curl command
+# to the dev web server returned HTTP 200 OK.
+# -------------------------------------------------------------------------
+define CHECK_DEVICE_HTTP_STATUS
+ if [ "$$HTTP_STATUS" != "200" ]; then \
+ $(ECHO) "$(COLOR_ERROR)ERROR: Device returned HTTP $$HTTP_STATUS$(COLOR_OFF)"; \
+ exit 1; \
+ fi
+endef
+
+# -------------------------------------------------------------------------
+# GET_PLUGIN_PAGE_RESULT_STATUS is used to extract the status message
+# (e.g. Success/Failed) from the dev server plugin_* web page response.
+# (Note that the plugin_install web page has two fields, whereas the
+# plugin_package web page just has one).
+# -------------------------------------------------------------------------
+define GET_PLUGIN_PAGE_RESULT_STATUS
+ cat $(DEV_SERVER_TMP_FILE) | \
+ grep -o ".*" | \
+ sed "s|||" | \
+ sed "s|||"
+endef
+
+# -------------------------------------------------------------------------
+# GET_PLUGIN_PAGE_PACKAGE_LINK is used to extract the installed package
+# URL from the dev server plugin_package web page response.
+# -------------------------------------------------------------------------
+define GET_PLUGIN_PAGE_PACKAGE_LINK =
+ cat $(DEV_SERVER_TMP_FILE) | \
+ grep -o " Your Dev ID: THE_DEVID
+#
+# In 7.0 firmware, response text has a line like:
+# devDiv.innerHTML = " THE_DEVID ";
+#
+# If the box is not keyed, or doesn't have a dev app installed,
+# the response doesn't contain html:
+#
+# -------------------------------------------------------------------------
+define GET_PLUGIN_PAGE_DEV_ID =
+ cat $(DEV_SERVER_TMP_FILE) | \
+ grep 'Dev ID' | \
+ grep -o -E '[0-9a-f]{40}'
+endef
+
+# -------------------------------------------------------------------------
+# install: install the app as the dev channel on the Roku target device.
+# -------------------------------------------------------------------------
+.PHONY: install
+install: $(APPNAME) check
+ifneq ($(APP_IS_THEME),)
+install: check-theme
+endif
+ @$(CHECK_ROKU_DEV_TARGET)
+
+ @$(ECHO) "$(COLOR_START)Installing $(APPNAME)...$(COLOR_OFF)"
+ @rm -f $(DEV_SERVER_TMP_FILE)
+ @$(CHECK_ROKU_DEV_PASSWORD)
+ @HTTP_STATUS=`curl --user $(USERPASS) --digest --silent --show-error \
+ -F "mysubmit=Install" -F "archive=@$(APP_ZIP_FILE)" \
+ --output $(DEV_SERVER_TMP_FILE) \
+ --write-out "%{http_code}" \
+ http://$(ROKU_DEV_TARGET)/plugin_install`; \
+ $(CHECK_DEVICE_HTTP_STATUS)
+
+ @MSG=`$(GET_PLUGIN_PAGE_RESULT_STATUS)`; \
+ $(ECHO) "$(COLOR_DONE)Result: $$MSG$(COLOR_OFF)"
+
+# -------------------------------------------------------------------------
+# remove: uninstall the dev channel from the Roku target device.
+# -------------------------------------------------------------------------
+.PHONY: remove
+remove:
+ @$(CHECK_ROKU_DEV_TARGET)
+
+ @$(ECHO) "$(COLOR_START)Removing dev app...$(COLOR_OFF)"
+ @rm -f $(DEV_SERVER_TMP_FILE)
+ @$(CHECK_ROKU_DEV_PASSWORD)
+ @HTTP_STATUS=`curl --user $(USERPASS) --digest --silent --show-error \
+ -F "mysubmit=Delete" -F "archive=" \
+ --output $(DEV_SERVER_TMP_FILE) \
+ --write-out "%{http_code}" \
+ http://$(ROKU_DEV_TARGET)/plugin_install`; \
+ $(CHECK_DEVICE_HTTP_STATUS)
+
+ @MSG=`$(GET_PLUGIN_PAGE_RESULT_STATUS)`; \
+ $(ECHO) "$(COLOR_DONE)Result: $$MSG$(COLOR_OFF)"
+
+# -------------------------------------------------------------------------
+# check-roku-dev-target: check the status of the Roku target device.
+# -------------------------------------------------------------------------
+.PHONY: check-roku-dev-target
+check-roku-dev-target:
+ @$(CHECK_ROKU_DEV_TARGET)
+
+# -------------------------------------------------------------------------
+# run: the install target is 'smart' and doesn't do anything if the package
+# didn't change.
+# But usually I want to run it even if it didn't change, so force a fresh
+# install by doing a remove first.
+# Some day we should look at doing the force run via a plugin_install flag,
+# but for now just brute force it.
+# -------------------------------------------------------------------------
+.PHONY: run
+run: remove install
+
+# -------------------------------------------------------------------------
+# get-target-key: return what developer ID the target Roku is keyed to.
+#
+# FIXME: this will only work if there is a dev app installed.
+# If not, it will incorrectly report 'Not keyed'.
+# -------------------------------------------------------------------------
+.PHONY: get-target-key
+get-target-key:
+ @$(CHECK_ROKU_DEV_TARGET)
+
+ @rm -f $(DEV_SERVER_TMP_FILE)
+ @$(CHECK_ROKU_DEV_PASSWORD)
+ @HTTP_STATUS=`curl --user $(USERPASS) --digest --silent --show-error \
+ --output $(DEV_SERVER_TMP_FILE) \
+ --write-out "%{http_code}" \
+ http://$(ROKU_DEV_TARGET)/plugin_package`; \
+ $(CHECK_DEVICE_HTTP_STATUS)
+
+ @MSG=`$(GET_PLUGIN_PAGE_DEV_ID)`; \
+ if [ -z "$$MSG" ]; then \
+ MSG="Not keyed (or no dev app installed)"; \
+ fi; \
+ $(ECHO) "$(COLOR_DONE)Result: Dev ID: $$MSG$(COLOR_OFF)"
+
+# -------------------------------------------------------------------------
+# rekey-target: key the target Roku with the devid from a .pkg.
+# -------------------------------------------------------------------------
+.PHONY: rekey-target
+rekey-target:
+ @$(CHECK_ROKU_DEV_TARGET)
+
+ @if [ -z "$(APP_KEY_FILE)" ]; then \
+ $(ECHO) "$(COLOR_ERROR)ERROR: APP_KEY_FILE not defined$(COLOR_OFF)"; \
+ exit 1; \
+ fi
+ @if [ ! -f "$(APP_KEY_FILE)" ]; then \
+ $(ECHO) "$(COLOR_ERROR)ERROR: key file not found: $(APP_KEY_FILE)$(COLOR_OFF)"; \
+ exit 1; \
+ fi
+
+ @if [ -z "$(APP_KEY_PASS)" ]; then \
+ read -r -p "Password: " REPLY; \
+ echo "$$REPLY" > $(APP_KEY_PASS_TMP); \
+ else \
+ echo "$(APP_KEY_PASS)" > $(APP_KEY_PASS_TMP); \
+ fi
+
+ @rm -f $(DEV_SERVER_TMP_FILE)
+ @$(CHECK_ROKU_DEV_PASSWORD)
+ @PASSWD=`cat $(APP_KEY_PASS_TMP)`; \
+ HTTP_STATUS=`curl --user $(USERPASS) --digest --silent --show-error \
+ -F "mysubmit=Rekey" -F "archive=@$(APP_KEY_FILE)" \
+ -F "passwd=$$PASSWD" \
+ --output $(DEV_SERVER_TMP_FILE) \
+ --write-out "%{http_code}" \
+ http://$(ROKU_DEV_TARGET)/plugin_inspect`; \
+ $(CHECK_DEVICE_HTTP_STATUS)
+
+ @MSG=`$(GET_PLUGIN_PAGE_RESULT_STATUS)`; \
+ $(ECHO) "$(COLOR_DONE)Result: $$MSG$(COLOR_OFF)"
+
+# -------------------------------------------------------------------------
+# pkg: use to create a pkg file from the application sources.
+#
+# Usage:
+# The application name should be specified via $APPNAME.
+# The application version should be specified via $VERSION.
+# The developer's signing password (from genkey) should be passed via
+# $APP_KEY_PASS, or via stdin, otherwise the script will prompt for it.
+# -------------------------------------------------------------------------
+.PHONY: pkg
+pkg: install
+ @$(ECHO) "$(COLOR_START)**** Creating package ****$(COLOR_OFF)"
+
+ @$(ECHO) " >> creating destination directory $(PKGREL)"
+ @if [ ! -d $(PKGREL) ]; then \
+ mkdir -p $(PKGREL); \
+ fi
+
+ @$(ECHO) " >> setting directory permissions for $(PKGREL)"
+ @if [ ! -w $(PKGREL) ]; then \
+ chmod 755 $(PKGREL); \
+ fi
+
+ @$(CHECK_ROKU_DEV_TARGET)
+
+ @$(ECHO) "Packaging $(APP_NAME)/$(APP_VERSION) to $(APP_PKG_FILE)"
+
+ @if [ -z "$(APP_KEY_PASS)" ]; then \
+ read -r -p "Password: " REPLY; \
+ echo "$$REPLY" > $(APP_KEY_PASS_TMP); \
+ else \
+ echo "$(APP_KEY_PASS)" > $(APP_KEY_PASS_TMP); \
+ fi
+
+ @rm -f $(DEV_SERVER_TMP_FILE)
+ @$(CHECK_ROKU_DEV_PASSWORD)
+ @PASSWD=`cat $(APP_KEY_PASS_TMP)`; \
+ PKG_TIME=`expr \`date +%s\` \* 1000`; \
+ HTTP_STATUS=`curl --user $(USERPASS) --digest --silent --show-error \
+ -F "mysubmit=Package" -F "app_name=$(APP_NAME)/$(APP_VERSION)" \
+ -F "passwd=$$PASSWD" -F "pkg_time=$$PKG_TIME" \
+ --output $(DEV_SERVER_TMP_FILE) \
+ --write-out "%{http_code}" \
+ http://$(ROKU_DEV_TARGET)/plugin_package`; \
+ $(CHECK_DEVICE_HTTP_STATUS)
+
+ @MSG=`$(GET_PLUGIN_PAGE_RESULT_STATUS)`; \
+ case "$$MSG" in \
+ *Success*) \
+ ;; \
+ *) $(ECHO) "$(COLOR_ERROR)Result: $$MSG$(COLOR_OFF)"; \
+ exit 1 \
+ ;; \
+ esac
+
+ @$(CHECK_ROKU_DEV_PASSWORD)
+ @PKG_LINK=`$(GET_PLUGIN_PAGE_PACKAGE_LINK)`; \
+ HTTP_STATUS=`curl --user $(USERPASS) --digest --silent --show-error \
+ --output $(APP_PKG_FILE) \
+ --write-out "%{http_code}" \
+ http://$(ROKU_DEV_TARGET)/pkgs/$$PKG_LINK`; \
+ $(CHECK_DEVICE_HTTP_STATUS)
+
+ @$(ECHO) "$(COLOR_DONE)**** Package $(APPNAME) complete ****$(COLOR_OFF)"
+
+# -------------------------------------------------------------------------
+# app-pkg: use to create a pkg file from the application sources.
+# Similar to the pkg target, but does not require a player to do the signing.
+# Instead it requires the developer key file and signing password to be
+# specified, which are then passed to the app-package desktop tool to create
+# the package file.
+#
+# Usage:
+# The application name should be specified via $APPNAME.
+# The application version should be specified via $VERSION.
+# The developer's key file (.pkg file) should be specified via $APP_KEY_FILE.
+# The developer's signing password (from genkey) should be passed via
+# $APP_KEY_PASS, or via stdin, otherwise the script will prompt for it.
+# -------------------------------------------------------------------------
+.PHONY: app-pkg
+app-pkg: $(APPNAME) check
+ @$(ECHO) "$(COLOR_START)**** Creating package ****$(COLOR_OFF)"
+
+ @$(ECHO) " >> creating destination directory $(PKGREL)"
+ @mkdir -p $(PKGREL) && chmod 755 $(PKGREL)
+
+ @if [ -z "$(APP_KEY_FILE)" ]; then \
+ $(ECHO) "$(COLOR_ERROR)ERROR: APP_KEY_FILE not defined$(COLOR_OFF)"; \
+ exit 1; \
+ fi
+ @if [ ! -f "$(APP_KEY_FILE)" ]; then \
+ $(ECHO) "$(COLOR_ERROR)ERROR: key file not found: $(APP_KEY_FILE)$(COLOR_OFF)"; \
+ exit 1; \
+ fi
+
+ @if [ -z "$(APP_KEY_PASS)" ]; then \
+ read -r -p "Password: " REPLY; \
+ echo "$$REPLY" > $(APP_KEY_PASS_TMP); \
+ else \
+ echo "$(APP_KEY_PASS)" > $(APP_KEY_PASS_TMP); \
+ fi
+
+ @$(ECHO) "Packaging $(APP_NAME)/$(APP_VERSION) to $(APP_PKG_FILE)"
+
+ @if [ -z "$(APP_VERSION)" ]; then \
+ $(ECHO) "WARNING: VERSION is not set."; \
+ fi
+
+ @PASSWD=`cat $(APP_KEY_PASS_TMP)`; \
+ $(APP_PACKAGE_TOOL) package $(APP_ZIP_FILE) \
+ -n $(APP_NAME)/$(APP_VERSION) \
+ -k $(APP_KEY_FILE) \
+ -p "$$PASSWD" \
+ -o $(APP_PKG_FILE)
+
+ @rm $(APP_KEY_PASS_TMP)
+
+ @$(ECHO) "$(COLOR_DONE)**** Package $(APPNAME) complete ****$(COLOR_OFF)"
+
+# -------------------------------------------------------------------------
+# teamcity: used to build .zip and .pkg file on TeamCity.
+# See app-pkg target for info on options for specifying the signing password.
+# -------------------------------------------------------------------------
+.PHONY: teamcity
+teamcity: app-pkg
+ifeq ($(IS_TEAMCITY_BUILD),true)
+ @$(ECHO) "Adding TeamCity artifacts..."
+
+ sudo rm -rf /tmp/artifacts
+ sudo mkdir -p /tmp/artifacts
+
+ cp $(APP_ZIP_FILE) /tmp/artifacts/$(APP_NAME)-$(APP_VERSION).zip
+ @$(ECHO) "##teamcity[publishArtifacts '/tmp/artifacts/$(APP_NAME)-$(APP_VERSION).zip']"
+
+ cp $(APP_PKG_FILE) /tmp/artifacts/$(APP_NAME)-$(APP_VERSION).pkg
+ @$(ECHO) "##teamcity[publishArtifacts '/tmp/artifacts/$(APP_NAME)-$(APP_VERSION).pkg']"
+
+ @$(ECHO) "TeamCity artifacts complete."
+else
+ @$(ECHO) "Not running on TeamCity, skipping artifacts."
+endif
+
+##########################################################################
+
+# -------------------------------------------------------------------------
+# CHECK_NATIVE_TARGET is used to check if the Roku simulator is
+# configured.
+# -------------------------------------------------------------------------
+define CHECK_NATIVE_TARGET
+ if [ -z "$(ROKU_NATIVE_DEV)" ]; then \
+ $(ECHO) "$(COLOR_ERROR)ERROR: ROKU_NATIVE_DEV not defined$(COLOR_OFF)"; \
+ exit 1; \
+ fi
+ if [ ! -d "$(ROKU_NATIVE_DEV)" ]; then \
+ $(ECHO) "$(COLOR_ERROR)ERROR: native dev dir not found: $(ROKU_NATIVE_DEV)$(COLOR_OFF)"; \
+ exit 1; \
+ fi
+ if [ ! -d "$(NATIVE_DIST_DIR)" ]; then \
+ $(ECHO) "$(COLOR_ERROR)ERROR: native build dir not found: $(NATIVE_DIST_DIR)$(COLOR_OFF)"; \
+ exit 1; \
+ fi
+endef
+
+# -------------------------------------------------------------------------
+# install-native: install the app as the dev channel on the Roku simulator.
+# -------------------------------------------------------------------------
+.PHONY: install-native
+install-native: $(APPNAME) check
+ @$(CHECK_NATIVE_TARGET)
+ @$(ECHO) "$(COLOR_START)Installing $(APPNAME) to native.$(COLOR_OFF)"
+ @if [ ! -d "$(NATIVE_DEV_REL)" ]; then \
+ mkdir "$(NATIVE_DEV_REL)"; \
+ fi
+ @$(ECHO) "Source is $(APP_ZIP_FILE)"
+ @$(ECHO) "Target is $(NATIVE_DEV_PKG)"
+ @cp $(APP_ZIP_FILE) $(NATIVE_DEV_PKG)
+ @$(NATIVE_TICKLER)
+
+# -------------------------------------------------------------------------
+# remove-native: uninstall the dev channel from the Roku simulator.
+# -------------------------------------------------------------------------
+.PHONY: remove-native
+remove-native:
+ @$(CHECK_NATIVE_TARGET)
+ @$(ECHO) "$(COLOR_START)Removing $(APPNAME) from native.$(COLOR_OFF)"
+ @rm $(NATIVE_DEV_PKG)
+ @$(NATIVE_TICKLER)
+
+##########################################################################
+
+# -------------------------------------------------------------------------
+# art-jpg-opt: compress any jpg files in the source tree.
+# Used by the art-opt target.
+# -------------------------------------------------------------------------
+APPS_JPG_ART=`\find . -name "*.jpg"`
+
+.PHONY: art-jpg-opt
+art-jpg-opt:
+ p4 edit $(APPS_JPG_ART)
+ for i in $(APPS_JPG_ART); \
+ do \
+ TMPJ=`mktemp` || return 1; \
+ $(ECHO) "Optimizing $$i"; \
+ jpegtran -copy none -optimize -outfile $$TMPJ $$i && mv -f $$TMPJ $$i; \
+ done
+ p4 revert -a $(APPS_JPG_ART)
+
+# -------------------------------------------------------------------------
+# art-png-opt: compress any png files in the source tree.
+# Used by the art-opt target.
+# -------------------------------------------------------------------------
+APPS_PNG_ART=`\find . -name "*.png"`
+
+.PHONY: art-png-opt
+art-png-opt:
+ p4 edit $(APPS_PNG_ART)
+ for i in $(APPS_PNG_ART); \
+ do \
+ $(ECHO) "Optimizing $$i"; \
+ optipng -strip all -quiet $$i; \
+ done
+ p4 revert -a $(APPS_PNG_ART)
+
+# -------------------------------------------------------------------------
+# art-opt: compress any png and jpg files in the source tree using
+# lossless compression options.
+# This assumes a Perforce client/workspace is configured.
+# Modified files are opened for edit in the default changelist.
+# -------------------------------------------------------------------------
+.PHONY: art-opt
+art-opt: art-png-opt art-jpg-opt
+
+##########################################################################
+
+# -------------------------------------------------------------------------
+# tr: this target is used to update translation files for an application
+#
+# Preconditions: 'locale' subdirectory must be present
+# Also there must be a locale subdirectory for each desired locale to be output,
+# e.g. en_US, fr_CA, es_ES, de_DE, ...
+#
+# MAKE_TR_OPTIONS may be set to [-t] [-d] etc. in the external environment,
+# if needed.
+#
+# -n => don't add fake translation placeholders, e.g. 'esES: OK'.
+# Instead, leave the translation empty so it will only get used when
+# an actual translation is provided.
+# -------------------------------------------------------------------------
+MAKE_TR_OPTIONS ?= -n
+.PHONY: tr
+tr:
+ @if [ ! -d locale ]; then \
+ echo "Creating locale directory"; \
+ mkdir locale; \
+ fi
+ @if [ ! -d locale/en_US ]; then \
+ echo "Creating locale/en_US directory"; \
+ mkdir locale/en_US; \
+ fi
+ifneq ($(P4CLIENT),)
+ @-p4 edit locale/.../translations.xml > /dev/null 2>&1
+endif
+ @echo "========================================"
+ @$(MAKE_TR_TOOL) $(MAKE_TR_OPTIONS)
+ @echo "========================================"
+ifneq ($(P4CLIENT),)
+ @-p4 add locale/*/translations.xml > /dev/null 2>&1
+ @-p4 revert locale/en_US/translations.xml > /dev/null 2>&1
+ @-p4 revert -a locale/.../translations.xml > /dev/null 2>&1
+ @-p4 opened -c default
+endif
+
+##########################################################################
+
diff --git a/dist/apps/hello_world.zip b/dist/apps/hello_world.zip
new file mode 100644
index 0000000..d338038
Binary files /dev/null and b/dist/apps/hello_world.zip differ
diff --git a/source/Makefile b/source/Makefile
new file mode 100644
index 0000000..7a08381
--- /dev/null
+++ b/source/Makefile
@@ -0,0 +1,10 @@
+#
+# Copyright (c) 2015 Roku, Inc. All rights reserved.
+# Simple Makefile for Roku Channel Development
+#
+
+APPNAME = hello_world
+IMPORTS =
+
+APPSROOT = ..
+include $(APPSROOT)/app.mk
diff --git a/source/components/helloworld.xml b/source/components/helloworld.xml
new file mode 100644
index 0000000..b7b3e5d
--- /dev/null
+++ b/source/components/helloworld.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
diff --git a/source/images/channel-poster_fhd.png b/source/images/channel-poster_fhd.png
new file mode 100644
index 0000000..a100334
Binary files /dev/null and b/source/images/channel-poster_fhd.png differ
diff --git a/source/images/channel-poster_hd.png b/source/images/channel-poster_hd.png
new file mode 100644
index 0000000..c60faa5
Binary files /dev/null and b/source/images/channel-poster_hd.png differ
diff --git a/source/images/channel-poster_sd.png b/source/images/channel-poster_sd.png
new file mode 100644
index 0000000..fe04d88
Binary files /dev/null and b/source/images/channel-poster_sd.png differ
diff --git a/source/images/splash-screen_fhd.jpg b/source/images/splash-screen_fhd.jpg
new file mode 100644
index 0000000..2ea271f
Binary files /dev/null and b/source/images/splash-screen_fhd.jpg differ
diff --git a/source/images/splash-screen_hd.jpg b/source/images/splash-screen_hd.jpg
new file mode 100644
index 0000000..dabb190
Binary files /dev/null and b/source/images/splash-screen_hd.jpg differ
diff --git a/source/images/splash-screen_sd.jpg b/source/images/splash-screen_sd.jpg
new file mode 100644
index 0000000..6749d13
Binary files /dev/null and b/source/images/splash-screen_sd.jpg differ
diff --git a/source/manifest b/source/manifest
new file mode 100644
index 0000000..06e9efc
--- /dev/null
+++ b/source/manifest
@@ -0,0 +1,27 @@
+#
+# Copyright (c) 2015 Roku, Inc. All rights reserved.
+# Roku Channel Manifest File
+# Full spec at bit.ly/roku-manifest-file
+#
+
+## Channel Details
+title=Hellow World
+major_version=1
+minor_version=0
+build_version=00001
+
+## Channel Assets
+### Main Menu Icons / Channel Poster Artwork
+#### Image sizes are FHD: 540x405px | HD: 290x218px | SD: 214x144px
+mm_icon_focus_fhd=pkg:/images/channel-poster_fhd.png
+mm_icon_focus_hd=pkg:/images/channel-poster_hd.png
+mm_icon_focus_sd=pkg:/images/channel-poster_sd.png
+
+### Splash Screen + Loading Screen Artwork
+#### Image sizes are FHD: 1920x1080px | HD: 1280x720px | SD: 720x480px
+splash_screen_fhd=pkg:/images/splash-screen_fhd.jpg
+splash_screen_hd=pkg:/images/splash-screen_hd.jpg
+splash_screen_sd=pkg:/images/splash-screen_sd.jpg
+
+splash_color=#000000
+splash_min_time=1
diff --git a/source/source/Main.brs b/source/source/Main.brs
new file mode 100644
index 0000000..32c4186
--- /dev/null
+++ b/source/source/Main.brs
@@ -0,0 +1,27 @@
+'*************************************************************
+'** Hello World example
+'** Copyright (c) 2015 Roku, Inc. All rights reserved.
+'** Use of the Roku Platform is subject to the Roku SDK Licence Agreement:
+'** https://docs.roku.com/doc/developersdk/en-us
+'*************************************************************
+
+sub Main()
+ print "in showChannelSGScreen"
+ 'Indicate this is a Roku SceneGraph application'
+ screen = CreateObject("roSGScreen")
+ m.port = CreateObject("roMessagePort")
+ screen.setMessagePort(m.port)
+
+ 'Create a scene and load /components/helloworld.xml'
+ scene = screen.CreateScene("HelloWorld")
+ screen.show()
+
+ while(true)
+ msg = wait(0, m.port)
+ msgType = type(msg)
+ if msgType = "roSGScreenEvent"
+ if msg.isScreenClosed() then return
+ end if
+ end while
+end sub
+