Initial public release of PieTime!

\o/
This commit is contained in:
Tomek Wójcik 2016-02-07 15:41:31 +01:00
commit 0912fd15e8
40 changed files with 4838 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
*.pyc
*.pyo
*.swp
.pybuild/
build/
dist/
pie_time.egg-info/

33
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,33 @@
# Quick guide to contributing to PieTime
This document describes the process of contributing to PieTime.
## Mailing List
The mailing list is the designated way of discussing anything related to
PieTime. Use it to report issues, submit patches etc.
Mailing list address: pietime@librelist.com
## Submitting patches
If you've made changes to PieTime source, you're welcome to submit a patch.
Before doing so, make sure you've updated tests and docs to reflect your
changes. Additionally, run the *pep8* utility on changed files.
Once you're done, create a patch from your changes and send it to the mailing
list along with a brief description. If your changes span over multiple
commits, please squash them into one before submitting the patch.
## License information
PieTime itself is licensed under the MIT license, so any code introduced by
your patch must be compatible with this license. Note that, if you don't
explicitly mark a piece of code with "alien" license, it'll automatically be
licensed under the MIT license.
If your patch contains 3rd party resources, make sure that their license(s)
allow for redistribution in open source projects.
If your patch contains 3rd party code and/or resources, make sure you update
the docs and ``debian/copyright`` file accordingly.

19
LICENSE Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2014-2016 Tomek Wójcik <tomek@bthlabs.pl>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

4
MANIFEST.in Normal file
View File

@ -0,0 +1,4 @@
include pie_time/cards/resources/*.ttf
include README.rst
include LICENSE
include requirements.txt

42
README.rst Normal file
View File

@ -0,0 +1,42 @@
PieTime
=======
Desk clock for your Raspberry Pi.
About
-----
PieTime lets you turn your Raspberry Pi into a desk clock. It's written in
Python using PyGame framework.
PieTime comes with three modules that allow displaying the clock, weather and
set of pictures. These modules (also known as *cards*) are highly configurable,
so you can tweak them to better suit your needs.
Additionally, an API is provided for users who wish to write their own cards.
Read on for an example of a card.
Features
--------
With PieTime you can:
* Choose any of the three builtin cards.
* Configure display aspects (e.g. text color) of the cards.
* Define card visibility intervals.
* Set up screen blanking period for power saving.
* Use the card API to create your own cards.
Author, License and Attributions
--------------------------------
PieTime has been created and is developed by
`Tomek Wójcik <https://www.bthlabs.pl/>`_.
PieTime is licensed under the MIT License.
PieTime uses the following 3rd party code and resources:
* Open Sans font by Steve Matteson, (Apache License, Version 2.0),
* Linea Weather 1.0 font by Dario Ferrando (CC BY 4.0),
* PT Mono font by Paratype (SIL OFL, Version 1.1).

1
docs/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
_build/

177
docs/Makefile Normal file
View File

@ -0,0 +1,177 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
endif
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PiClock.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PiClock.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/PiClock"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PiClock"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."

16
docs/api.rst Normal file
View File

@ -0,0 +1,16 @@
API
===
This document describes the interfaces of PieTime.
Application
-----------
.. autoclass:: pie_time.PieTime
:members:
Abstract Card
-------------
.. autoclass:: pie_time.AbstractCard
:members:

22
docs/builtin_cards.rst Normal file
View File

@ -0,0 +1,22 @@
Builtin Cards
=============
This document describes the builtin cards.
ClockCard
---------
.. autoclass:: pie_time.cards.ClockCard
:members:
PictureCard
-----------
.. autoclass:: pie_time.cards.PictureCard
:members:
WeatherCard
-----------
.. autoclass:: pie_time.cards.WeatherCard
:members:

261
docs/conf.py Normal file
View File

@ -0,0 +1,261 @@
# -*- coding: utf-8 -*-
#
# PieTime documentation build configuration file, created by
# sphinx-quickstart on Tue Oct 21 16:34:31 2014.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys
import os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('..'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.viewcode',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'PieTime'
copyright = u'2014-2016, Tomek Wójcik'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '1.0'
# The full version, including alpha/beta/rc tags.
release = '1.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'nature'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'PieTimedoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
('index', 'PieTime.tex', u'PieTime Documentation',
u'Tomek Wójcik', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'pie_time', u'PieTime Documentation',
[u'Tomek Wójcik'], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'PieTime', u'PieTime Documentation',
u'Tomek Wójcik', 'PieTime', 'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False

104
docs/developer_guide.rst Normal file
View File

@ -0,0 +1,104 @@
Developer Guide
===============
This document provides guide to hacking and extending PieTime.
Setup
-----
To develop PieTime or cards, some additional setup is required. First, it's
recommended to use a virtual environment, to separate from OS Python and
extensions. Secondly, use ``requirements-dev.txt`` to install additional
modules and tools used in development.
Custom card example
-------------------
.. sourcecode:: python
from pie_time.card import AbstractCard
import pygame
class ExampleCard(AbstractCard):
def initialize(self):
self.sprite = pygame.surface.Surface((20, 20))
self.sprite.fill((255, 0, 0))
self.orig_sprite_rect = self.sprite.get_rect()
self.orig_speed = [2, 2]
self.sprite_rect = self.orig_sprite_rect
self.speed = self.orig_speed
def show(self):
self.sprite_rect = self.orig_sprite_rect
self.speed = self.orig_speed
def tick(self):
self.sprite_rect = self.sprite_rect.move(self.speed)
if self.sprite_rect.left < 0 or self.sprite_rect.right > self.width:
self.speed[0] = -self.speed[0]
if self.sprite_rect.top < 0 or self.sprite_rect.bottom > self.height:
self.speed[1] = -self.speed[1]
self.surface.fill(self.background_color)
self.surface.blit(self.sprite, self.sprite_rect)
This example shows how easy it is to create custom cards for use in PieTime
decks.
Start by creating a custom class that inherits from *AbstractCard*. Then
implement a few methods to make it display the information you need (in this
example, a red square moving on the screen). Having done that, you'll be ready to use your custom card in a deck.
Head on to :py:class:`pie_time.AbstractCard` documentation for more
information about PieTime card API.
Speed considerations
--------------------
Since PieTime targets the Raspberry Pi, it's important to keep speed in mind.
When developing cards, take care to do as little work as possible in the
:py:meth:`pie_time.AbstractCard.tick` method.
For example, WeatherCard redraws its surface only when weather conditions
change. By doing so, the CPU load is reduced because the only thing PieTime has to do is blit the surface to screen.
Always test the behavior of your card in low FPS. Remember, that PieTime
targets small GPIO-connected LCD screens. For many of them, 20 FPS will be the best refresh rate. If your card behaves badly in such conditions, users may refrain from using it.
Threading considerations
------------------------
Sometimes, it's required to perform background tasks during lifetime of the
card. API exposed by Python's builtin ``threading`` module should come in handy
is such situations.
Take WeatherCard as an example. Once every 10 minutes, it fetches current
conditions from the Internet. If you look into the card's code, there's
``_refresh_conditions`` method. It uses ``threading.Timer`` class to schedule
fetching of the conditions in a separate thread of control. This way, the
HTTP request (which may take some time depending on the network conditions)
won't cause the PieTime application to freeze.
As always with threads, you should be aware of the standard pitfalls - variable
access synchronization, error handling, the GIL. Also, spawning too many
threads will impact PieTime's performance. If you use threads in your card,
make sure to test it on the device to see how it impacts the load.
Handling events
---------------
In every iteration of the main loop, PieTime reads list of current events from
PyGame. This list is available for the cards to use through
:py:attr:`pie_time.PieTime.events`.
The application itself handles the following events:
* ``QUIT`` (e.g. SIGTERM) - if this event appears, the application quits.
* ``KEYDOWN`` on the key specified by :py:attr:`pie_time.PieTime.KEY_QUIT` -
quits the application,
* ``MOUSEBUTTONDOWN`` anywhere on the screen when it's blanked -
if click to unblank is enabled,
* ``MOUSEBUTTONDOWN`` in one of the regions used to manually switch bedween
cards.

69
docs/index.rst Normal file
View File

@ -0,0 +1,69 @@
PieTime
=======
Desk clock for your Raspberry Pi.
About
-----
PieTime lets you turn your Raspberry Pi into a desk clock. It's written in
Python using Pygame framework.
PieTime comes with three modules that allow displaying the clock, weather and
set of pictures. These modules (also known as *cards*) are highly configurable,
so you can tweak them to better suit your needs.
Additionally, an API is provided for users who wish to write their own cards.
Read on for an example of a card.
Features
--------
With PieTime you can:
* Choose any of the three builtin cards.
* Configure display aspects (e.g. text color) of the cards.
* Define card visibility intervals.
* Set up screen blanking period for power saving.
* Use the card API to create your own cards.
Author, License and Attributions
--------------------------------
PieTime has been created and is developed by
`Tomek Wójcik <https://www.bthlabs.pl/>`_.
PieTime is licensed under the MIT License.
PieTime uses the following 3rd party code and resources:
* Open Sans font by Steve Matteson, (Apache License, Version 2.0),
* Linea Weather 1.0 font by Dario Ferrando (CC BY 4.0),
* PT Mono font by Paratype (SIL OFL, Version 1.1).
Source code and issues
----------------------
Source code is available via
`public Git repository <https://git.bthlabs.pl/tomekwojcik/pie-time>`_.
If you wish to contribute to the project, see ``CONTRIBUTING.md`` for more
info.
Contents
--------
.. toctree::
:maxdepth: 2
requirements_and_installation
user_guide
builtin_cards
developer_guide
api
Indices and tables
------------------
* :ref:`genindex`
* :ref:`search`

View File

@ -0,0 +1,79 @@
.. _requirements_and_installation:
Requirements and installation
=============================
This document describes the PieTime requirements and installation process.
Requirements
------------
PieTime requires the following to work:
* Python 2.7,
* PyGame 1.9.1 (also tested with 1.9.2a0),
* requests 2.4.1 (should work with newer versions).
Installing on Raspbian Jessie
-----------------------------
If you're using Raspbian Jessie on your Raspberry Pi, you can install PieTime
using binary packages by following the guide below.
**Add PieTime APT repository**
Create the file /etc/apt/sources.list.d/pie-time.list and add the following
line:
.. sourcecode:: text
deb https://pie-time.bthlabs.pl/repos/apt/ raspbian-jessie main
**Import the repository signing key**
.. sourcecode:: console
$ wget --quiet -O - https://pie-time.bthlabs.pl/keys/apt.asc | sudo apt-key add -
**Update the package lists and install PieTime**
.. sourcecode:: console
$ sudo apt-get update
$ sudo apt-get install pie-time
Installing on other systems using PyPI
--------------------------------------
If you wish to install PieTime on system other than Raspbian Jessie (and
potentially on device other than Raspberry Pi), you can do so using the
PyPI package by using the guide below.
#. Install PyGame dependencies,
#. Run ``$ sudo pip install pie_time`` to install PieTime and its dependencies.
**NOTE**: The second step may require installing additional packages from the
system repository, depending on your current setup.
Installing from the source
--------------------------
In order to install PieTime, please follow the guide below. Note that this
guide assumes Unix-like OS and root access.
#. Install PyGame dependencies,
#. Clone the repository ``$ git clone https://git.bthlabs.pl/tomekwojcik/pie-time.git``,
#. Enter the PieTime directory: ``$ cd pie-time``,
#. Install PieTime: ``$ python setup.py install``.
**NOTE**: The fourth step may require installing additional packages from the
system repository, depending on your current setup.
Installing as non-root user
---------------------------
If you wish to install PieTime as non-root user, you can do so by installing
it from the PyPI package or source code using a virtual env.
To learn more about Python virtual envs, see the
`virtual env documentation <https://virtualenv.readthedocs.org/en/latest/>`_

295
docs/user_guide.rst Normal file
View File

@ -0,0 +1,295 @@
.. _user_guide:
User Guide
==========
This document provides guide to using PieTime.
The application script
----------------------
In order to use PieTime, you'll have to write a simple Python script to set
up and (optionally) start the application.
**Short example**
.. sourcecode:: python
#!/usr/bin/env python2.7
from datetime import timedelta
import os
import sys
if os.getcwd() not in sys.path:
sys.path.insert(0, os.getcwd())
from pie_time import PieTime
from pie_time.cards import ClockCard, PictureCard, WeatherCard
deck = [
ClockCard,
(
WeatherCard, 20, {
'api_key': 'Your OpenWeatherMap API KEY',
'city': 'Wroclaw,PL'
}
),
(
PictureCard, 10, {
'urls': [
'http://lorempixel.com/320/240/city',
'http://lorempixel.com/200/125/technics'
]
}
)
]
blanker_schedule = (
timedelta(hours=23), timedelta(hours=6)
)
app = PieTime(deck, blanker_schedule=blanker_schedule)
if __name__ == '__main__':
app.run()
This script sets up PieTime application with the following settings:
* Three cards,
* Clock card set up to display for 60 seconds,
* Weather card set up to fetch data for the city of Wrocław in Poland and
display for 20 seconds,
* Picture frame card set to display two separate images (fetched from the Net)
for 10 seconds each.
* Blanker schedule set up to blank the screen between 23:00 (11:00 PM) and
6:00 AM,
* Click to unblank interval set to 10 seconds.
The deck
--------
The first argument passed to :py:class:`pie_time.PieTime` constructor defines
the deck of cards to be displayed, along with additional information about
each of the cards.
Example deck could look like this:
.. sourcecode:: python
deck = [
ClockCard,
(ClockCard, 30),
(ClockCard, 30, {'text_color': (255, 0, 0)}),
]
The first item is just a card class. A card defined this way will display
for the duration defined by :py:attr:`pie_time.PieTime.CARD_INTERVAL` and
won't have any additional settings.
The second item is a tuple of card class and number. A card defined this
way will display for the specified number of seconds and won't have any
additional settings.
The third item is a tuple of card class, number and dictionary. A card
defined this way will display for the specified number of seconds and will
have additional settings as specified by the dictionary.
Blanker schedule
----------------
The *blanker_schedule* keyword argument defines how the screen should be
blanked.
Example blanker schedule could look like this:
.. sourcecode:: python
blanker_schedule = (
datetime.timedelta(hours=23), datetime.timedelta(hours=6)
)
Such a schedule will make the application blank the screen between 23:00
(11:00 PM) and 6:00 AM.
When the blanker is active, the screen is filled with color defined in
:py:attr:`pie_time.PieTime.BLANK_COLOR`.
Blanker also prevents the following actions:
* Transitioning cards,
* Calling the visible card's ``tick()`` method,
* Blitting the visible card's surface to the screen.
When the blanker deactivates it transitions to the first card from the deck.
Click to unblank
----------------
A PieTime application can be set up to allow temporary overriding of the screen
blanker. In order to do so, set *click_to_unblank_interval* to a non-negative
number. When the screen is blanked, just click anywhere and PieTime will show
for number of seconds defined by *click_to_unblank_interval*. Before
unblanking, PieTime will set the first card from deck as the current card.
Since PieTime uses PyGame, it'll automatically support many input devices. For
example, a properly configured touch screen for PiTFT-like displays should be
supported out of the box.
Click to transition
-------------------
If you wish, you can change the currently visible card manually. In order to do
so, just click (or tap) the bottom-left or bottom-right corner of the screen.
The bottom-left corner will switch to the previous card. The bottom-right
corner will switch to the next card.
This feature is enabled automatically and can be disabled by setting
*click_to_transition* keyword argument to ``False``.
Other settings
--------------
PieTime app allows changing other settings. Have a look at
:py:class:`pie_time.PieTime` class documentation to learn more.
Video drivers and screen size
-----------------------------
Since PieTime is based on PyGame, it supports the same range ouf output
devices. As of time of writing this document, PieTime has been tested with
``x11``, ``fbcon`` and ``Quartz`` video drivers.
You can configure the outut device using SDL environment variables. See the
`SDL environment variables documentation <https://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlenvvars.html>`_ to learn more.
Since PieTime mostly targets LCD shields, the screen size defaults to
320x240px. Support for other screen sizes is limited.
Testing the script
------------------
Once you've created the script to set up the application and chosen the video
driver, you can start PieTime manually using the following command:
.. sourcecode:: console
$ python2.7 <path_to_app_script>
In this case, video driver will be chosen automatically. If you get any
errors, try using a different video driver. Note that framebuffer drivers
usually require root privileges.
To exit the application, press *[ESC]*.
**NOTE**: If you installed PieTime from the PyPI package or source code in a
virtual env, make sure it's properly activated before trying to start the
application.
Creating and using the PieTime INI file
---------------------------------------
After you've tested your application script and are satisfied with it, it's
time to create the INI file. This INI file will be used by the *pie_time*
program to set up and launch your application.
**Short example**
.. sourcecode:: ini
[PieTime]
app_module = examples.customization_example:app
log_path = log.txt
[SDL]
VIDEODRIVER = fbcon
**The PieTime section**
The *PieTime* section should contain the following fields:
* ``app_module`` (string) - app module import path,
* ``log_path`` (string) - optional path to log file (if omitted, standard
output will be used).
**The app module import path**
The *app_module* field defines the app module import path. This import path
will be used to import app script and extract the app object from it. In the
example, the import path translates to *app attribute in customization_example
module in examples package*. Note that the import path is related to the
current working directory.
**The SDL section**
The SDL section allows you to set up SDL environment variables before starting
the PieTime application. You can specify any environment variable supported by
SDL. The field names should be specified without the ``SDL_`` prefix, which
will be added automatically.
Starting PieTime on boot
------------------------
Since PieTime was designed as a desk clock replacement, it's best to have it
start automatically on boot. In order to do so, please follow instructions in
one of the subsections.
**Setting up PieTime installed from APT repository**
If you installed PieTime from APT repository, follow the guide below to set it
up to start on boot.
#. Edit ``/etc/default/pie-time`` and adjust its contents according to your
needs,
#. Place the INI file in the path specified in the ``/etc/default/pie-time``
file,
#. Place your application script in the path specified in the INI file,
#. Run ``$ sudo dpkg-reconfigure pie-time`` and answer *Yes* when it asks you
about starting on boot,
#. Either reboot the Raspberry Pi or run
``$ sudo /etc/init.d/pie-time restart`` to start PieTime.
**NOTE**: You can skip the third step, if you replied *Yes* to the question
when installing the PieTime package.
**The /etc/default/pie-time file**
The ``/etc/default/pie-time`` file contains minimum shell environment required
to properly start the *pie_time* program.
Supported environment variables:
* ``WORKDIR`` - path to working directory (which will be used to import the app
module). Defaults to ``/var/lib/pie-time``.
* ``INI_PATH`` - path to the INI file. Defaults to ``/etc/pie-time.ini``.
* ``USER`` - name of the user which will start the app. Defaults to ``root``.
**NOTE**: If you change the ``USER`` field to a non-root user, make sure it can
acess the selected output device and paths (most notably, the log path).
**Setting up PieTime installed from PyPI package or source code**
If you chose to install PieTime from PyPI package or source code and wish to
start it on boot, the recommended method is to use
`supervisor <http://supervisord.org/>`_ to achieve that.
**Example supervisor config for PieTime**
.. sourcecode:: text
[program:pie_time]
command=/usr/bin/python2.7 /home/pi/pie-time/app.py
numprocs=1
directory=/home/pi/pie-time
autostart=true
user=root
stdout_logfile=/home/pi/pie-time/log/stdout.log
stderr_logfile=/home/pi/pie-time/log/stderr.log
Troubleshooting
---------------
In case of any problems, it's recommend to set the *verbose* keyword to
argument to ``True`` and start the app again. In verbose mode, the app's logger
is set to ``DEBUG`` level (as opposed to ``INFO`` in non-verbose mode) and will
display a lot of useful debugging information.

0
examples/__init__.py Normal file
View File

38
examples/basic_usage.py Normal file
View File

@ -0,0 +1,38 @@
#!/usr/bin/env python2.7
from datetime import timedelta
import os
import sys
if os.getcwd() not in sys.path:
sys.path.insert(0, os.getcwd())
from pie_time import PieTime
from pie_time.cards import ClockCard, PictureCard, WeatherCard
deck = [
ClockCard,
(
WeatherCard, 20, {
'api_key': 'Your OpenWeatherMap API KEY',
'city': 'Wroclaw,PL'
}
),
(
PictureCard, 10, {
'urls': [
'http://lorempixel.com/320/240/city',
'http://lorempixel.com/200/125/technics'
]
}
)
]
blanker_schedule = (
timedelta(hours=23), timedelta(hours=6)
)
app = PieTime(deck, blanker_schedule=blanker_schedule)
if __name__ == '__main__':
app.run()

View File

@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
from datetime import datetime, timedelta
import os
import sys
import time
if os.getcwd() not in sys.path:
sys.path.insert(0, os.getcwd())
from pie_time import PieTime
from pie_time.cards import ClockCard
from pie_time.card import AbstractCard
import pygame
class ExampleCard(AbstractCard):
def initialize(self):
self.sprite = pygame.surface.Surface((20, 20))
self.sprite.fill((255, 0, 0))
self.orig_sprite_rect = self.sprite.get_rect()
self.orig_speed = [2, 2]
self.sprite_rect = self.orig_sprite_rect
self.speed = self.orig_speed
def show(self):
self.sprite_rect = self.orig_sprite_rect
self.speed = self.orig_speed
def tick(self):
self.sprite_rect = self.sprite_rect.move(self.speed)
if self.sprite_rect.left < 0 or self.sprite_rect.right > self.width:
self.speed[0] = -self.speed[0]
if self.sprite_rect.top < 0 or self.sprite_rect.bottom > self.height:
self.speed[1] = -self.speed[1]
self.surface.fill(self.background_color)
self.surface.blit(self.sprite, self.sprite_rect)
class CustomApp(PieTime):
def __init__(self, *args, **kwargs):
super(CustomApp, self).__init__(*args, **kwargs)
self._ctt_region_prev = pygame.Rect(0, 0, 160, 240)
self._ctt_region_next = pygame.Rect(160, 0, 160, 240)
def will_blank(self):
self.logger.debug('CustomApp.will_blank')
def will_unblank(self):
self.logger.debug('CustomApp.will_unblank')
deck = [
(ClockCard, 10),
(ExampleCard, 10),
]
app = CustomApp(
deck, verbose=True, fps=40,
blanker_schedule=(
timedelta(hours=23), timedelta(hours=6)
),
click_to_unblank_interval=10
)
if __name__ == '__main__':
app.run()

3
examples/example.ini Normal file
View File

@ -0,0 +1,3 @@
[PieTime]
app_module = examples.customization_example:app
log_path = log.txt

84
extra/initscript.debian Normal file
View File

@ -0,0 +1,84 @@
#!/bin/bash
### BEGIN INIT INFO
# Provides: pie-time
# Required-Start: $network
# Required-Stop: $network
# Should-Start: $local_fs
# Should-Stop: $local_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Desk clock application for the Raspberry Pi
# Description: Desk clock application for the Raspberry Pi
### END INIT INFO
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON="/usr/bin/pie_time"
NAME="pie-time"
DESC="Desk clock application for the Raspberry Pi"
RUNDIR="/var/run/pie-time"
PIDFILE="/var/run/pie-time/pie-time.pid"
WORKDIR="/var/lib/pie-time"
CONFIG_PATH="/etc/pie-time.ini"
USER="root"
if [ -f "/etc/default/pie-time" ];then
. "/etc/default/pie-time"
fi
. /lib/lsb/init-functions
set -e
case "$1" in
start)
echo -n "Starting $DESC: "
mkdir -p $RUNDIR
chmod 755 $RUNDIR
chown $USER $RUNDIR
if start-stop-daemon --start --quiet --pidfile $PIDFILE --chuid $USER --chdir $WORKDIR -b -m --exec $DAEMON -- $CONFIG_PATH;then
echo "$NAME."
else
echo "failed"
fi
;;
stop)
echo -n "Stopping $DESC: "
if start-stop-daemon --stop --retry forever/TERM/1 --quiet --oknodo --pidfile $PIDFILE;then
echo "$NAME."
rm -f $PIDFILE
else
echo "failed"
fi
;;
restart|force-reload)
${0} stop
${0} start
;;
status)
STATUS="4"
start-stop-daemon --status --pidfile $PIDFILE || STATUS="$?"
case $STATUS in
0)
echo "$NAME: running"
;;
1|3)
echo "$NAME: not running"
;;
*)
echo "$NAME: unable to determine the status"
;;
esac
;;
*)
echo "Usage: /etc/init.d/$NAME {start|stop|restart|force-reload|status}" >&2
exit 1
;;
esac
exit 0

View File

@ -0,0 +1,7 @@
[PieTime]
app_module = pie_time_app:app
log_path = /var/log/pie-time/daemon.log
[SDL]
VIDEODRIVER = fbcon
FBDEV = /dev/fb0

12
pie_time/__init__.py Normal file
View File

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
__title__ = 'pie_time'
__version__ = '1.0'
__author__ = u'Tomek Wójcik'
__license__ = 'MIT'
__copyright__ = (
u'Copyright (c) 2014-2016 Tomek Wójcik <tomek@bthlabs.pl>'
)
from .application import PieTime
from .card import AbstractCard

511
pie_time/application.py Normal file
View File

@ -0,0 +1,511 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2014-2016 Tomek Wójcik <tomek@bthlabs.pl>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
"""
pie_time.application
====================
This module implements the PieTime application.
"""
import datetime
import argparse
import imp
import logging
import os
import sys
import pygame
from pie_time import __copyright__ as copyright, __version__ as version
RET_OK = 0
RET_ERROR = 99
MOTD_PICLOCK_BANNER = u"PieTime v%s by Tomek Wójcik" % (
version
)
MOTD_LICENSE_BANNER = u"Released under the MIT license"
EVENT_QUIT = 0
EVENT_CLICK_TO_UNBLANK = 1
EVENT_CLICK_TO_PREV_CARD = 2
EVENT_CLICK_TO_NEXT_CARD = 3
class Quit(Exception):
pass
class PieTimeEvent(object):
def __init__(self, app, event):
self.event = event
self.app = app
def is_quit(self):
return (self.event.type == pygame.QUIT)
def is_key_quit(self):
return (
self.event.type == pygame.KEYDOWN
and self.event.key == self.app.KEY_QUIT
)
def is_click_to_unblank(self):
return (
self.event.type == pygame.MOUSEBUTTONDOWN
and self.app._click_to_unblank_interval is not None
and self.app._is_blanked is True
)
def is_click_to_prev_card(self):
return (
self.event.type == pygame.MOUSEBUTTONDOWN
and self.app._click_to_transition is True
and self.app._is_blanked is False
and self.app._ctt_region_prev.collidepoint(self.event.pos) == 1
)
def is_click_to_next_card(self):
return (
self.event.type == pygame.MOUSEBUTTONDOWN
and self.app._click_to_transition is True
and self.app._is_blanked is False
and self.app._ctt_region_next.collidepoint(self.event.pos) == 1
)
class PieTime(object):
"""
The PieTime application.
:param deck: the deck
:param screen_size: tuple of (width, height) to use as the screen size
:param fps: number of frames per second to limit rendering to
:param blanker_schedule: blanker schedule
:param click_to_unblank_interval: time interval for click to unblank
:param click_to_transition: boolean defining if click to transition is
enabled
:param verbose: boolean defining if verbose logging should be on
:param log_path: path to log file (if omitted, *stdout* will be used)
"""
#: Default background color
BACKGROUND_COLOR = (0, 0, 0)
#: Blanked screen color
BLANK_COLOR = (0, 0, 0)
#: Default card display duration interval
CARD_INTERVAL = 60
#: Defines key which quits the application
KEY_QUIT = pygame.K_ESCAPE
#: Defines size of click to transition region square
CLICK_TO_TRANSITION_REGION_SIZE = 30
_DEFAULT_OUTPUT_STREAM = sys.stdout
_STREAM_FACTORY = file
def __init__(self, deck, screen_size=(320, 240), fps=20,
blanker_schedule=None, click_to_unblank_interval=None,
click_to_transition=True, verbose=False, log_path=None):
self._deck = deck
#: The screen surface
self.screen = None
#: The screen size tuple
self.screen_size = screen_size
#: List of events captured in this frame
self.events = []
#: Path to log file. If `None`, *stdout* will be used for logging.
self.log_path = log_path
self._fps = fps
self._verbose = verbose
self._blanker_schedule = blanker_schedule
self._click_to_unblank_interval = click_to_unblank_interval
self._click_to_transition = click_to_transition
self._clock = None
self._cards = []
self._is_blanked = False
self._current_card_idx = None
self._current_card_time = None
self._should_quit = False
self._internal_events = set()
self._ctu_timer = None
self._output_stream = None
self._ctt_region_prev = pygame.Rect(
0,
self.screen_size[1] - self.CLICK_TO_TRANSITION_REGION_SIZE,
self.CLICK_TO_TRANSITION_REGION_SIZE,
self.CLICK_TO_TRANSITION_REGION_SIZE
)
self._ctt_region_next = pygame.Rect(
self.screen_size[0] - self.CLICK_TO_TRANSITION_REGION_SIZE,
self.screen_size[1] - self.CLICK_TO_TRANSITION_REGION_SIZE,
self.CLICK_TO_TRANSITION_REGION_SIZE,
self.CLICK_TO_TRANSITION_REGION_SIZE
)
def _should_blank(self, now=None):
if self._has_click_to_unblank_event() or self._ctu_timer is not None:
if self._is_blanked is False and self._ctu_timer is None:
self._ctu_timer = None
return False
if self._click_to_unblank_interval is not None:
if self._ctu_timer is None:
self._ctu_timer = self._click_to_unblank_interval
return False
self._ctu_timer -= self._clock.get_time() / 1000.0
if self._ctu_timer <= 0:
self._ctu_timer = None
return True
else:
return False
if self._blanker_schedule:
delta_blanker_start, delta_blanker_end = self._blanker_schedule
if now is None:
now = datetime.datetime.now()
midnight = now.replace(hour=0, minute=0, second=0, microsecond=0)
blanker_start = midnight + delta_blanker_start
blanker_end = midnight + delta_blanker_end
if blanker_start > blanker_end:
if now.hour < 12:
blanker_start -= datetime.timedelta(days=1)
else:
blanker_end += datetime.timedelta(days=1)
if now >= blanker_start and now < blanker_end:
return True
return False
def _blank(self):
if self._is_blanked is False:
self.logger.debug('Blanking the screen!')
self.will_blank()
self._is_blanked = True
self.screen.fill(self.BLANK_COLOR)
def _unblank(self):
if self._is_blanked:
self.logger.debug('Unblanking the screen!')
self.will_unblank()
self._is_blanked = False
self._current_card_idx = 0
self._current_card_time = 0
self._cards[self._current_card_idx][0].show()
def _transition_cards(self, direction=1, force=False):
if self._current_card_idx is None and force is False:
self._current_card_idx = 0
self._current_card_time = 0
self._cards[self._current_card_idx][0].show()
elif len(self._cards) > 1:
self._current_card_time += self._clock.get_time() / 1000.0
card_interval = self._cards[self._current_card_idx][1]
if self._current_card_time >= card_interval or force is True:
new_card_idx = self._current_card_idx + direction
if new_card_idx >= len(self._cards):
new_card_idx = 0
elif new_card_idx < 0:
new_card_idx = len(self._cards) - 1
self.logger.debug('Card transition: %d -> %d' % (
self._current_card_idx, new_card_idx
))
self._cards[self._current_card_idx][0].hide()
self._current_card_idx = new_card_idx
self._current_card_time = 0
self._cards[self._current_card_idx][0].show()
def _get_events(self):
self._internal_events = set()
self.events = []
for event in pygame.event.get():
pie_time_event = PieTimeEvent(self, event)
if pie_time_event.is_quit():
self.logger.debug('_get_events: QUIT')
self._internal_events.add(EVENT_QUIT)
elif pie_time_event.is_key_quit():