Compare commits
17 Commits
7a7d580d28
...
a8b9242d4a
Author | SHA1 | Date |
---|---|---|
deltragon | a8b9242d4a | |
AO Localisation Lab | b0b624705e | |
Priit Jõerüüt | ff79402a2f | |
Yaron Shahrabani | dfe7270f57 | |
deltragon | f9e39081d8 | |
deltragon | 8953b2237f | |
deltragon | 60afefc35c | |
deltragon | a823848a91 | |
deltragon | d43e8dc8bb | |
deltragon | affd1af5e0 | |
deltragon | 1d74ee2fbe | |
deltragon | 4227212e89 | |
deltragon | 300cad2b8f | |
deltragon | 38b80b2ae9 | |
deltragon | bf4a04a378 | |
deltragon | bb11f7674e | |
deltragon | 6288c78aea |
|
@ -24,15 +24,16 @@ jobs:
|
||||||
pip install build wheel
|
pip install build wheel
|
||||||
|
|
||||||
- name: Get Current Version
|
- name: Get Current Version
|
||||||
run: |
|
uses: SebRollen/toml-action@v1.2.0
|
||||||
project_version=$(python3 setup.py --version)
|
with:
|
||||||
echo "project_version=$project_version" >> $GITHUB_OUTPUT
|
file: "pyproject.toml"
|
||||||
|
field: project.version
|
||||||
id: get_current_version
|
id: get_current_version
|
||||||
|
|
||||||
- name: Create Tag
|
- name: Create Tag
|
||||||
uses: mathieudutour/github-tag-action@v6.1
|
uses: mathieudutour/github-tag-action@v6.1
|
||||||
with:
|
with:
|
||||||
custom_tag: "${{steps.get_current_version.outputs.project_version}}"
|
custom_tag: "${{steps.get_current_version.outputs.value}}"
|
||||||
github_token: ${{ secrets.GH_API_SECRET }}
|
github_token: ${{ secrets.GH_API_SECRET }}
|
||||||
|
|
||||||
- name: Build Changelog
|
- name: Build Changelog
|
||||||
|
@ -44,7 +45,7 @@ jobs:
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
tag_name: 'v${{steps.get_current_version.outputs.project_version}}'
|
tag_name: 'v${{steps.get_current_version.outputs.value}}'
|
||||||
body: ${{steps.build_changelog.outputs.changelog}}
|
body: ${{steps.build_changelog.outputs.changelog}}
|
||||||
token: ${{ secrets.GH_API_SECRET }}
|
token: ${{ secrets.GH_API_SECRET }}
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,8 @@
|
||||||
include LICENSES/GPL-3.0-or-later.txt
|
include LICENSES/GPL-3.0-or-later.txt
|
||||||
include README.md
|
include README.md
|
||||||
|
|
||||||
|
graft safeeyes
|
||||||
|
|
||||||
|
prune safeeyes/tests
|
||||||
|
|
||||||
|
global-exclude *.py[cod]
|
||||||
|
|
|
@ -200,9 +200,8 @@ To ensure the new strings are well-formed, you can use `python validate_po.py --
|
||||||
1. Checkout the latest commits from the `master` branch
|
1. Checkout the latest commits from the `master` branch
|
||||||
2. Run `python3 -m safeeyes` to make sure nothing is broken
|
2. Run `python3 -m safeeyes` to make sure nothing is broken
|
||||||
3. Update the Safe Eyes version in the following places (Open the project in VSCode and search for the current version):
|
3. Update the Safe Eyes version in the following places (Open the project in VSCode and search for the current version):
|
||||||
- [setup.py](https://github.com/slgobinath/SafeEyes/blob/master/setup.py#L83)
|
- [pyproject.toml](https://github.com/slgobinath/SafeEyes/blob/master/pyproject.toml#L4)
|
||||||
- [setup.py](https://github.com/slgobinath/SafeEyes/blob/master/setup.py#L90)
|
- [pyproject.toml](https://github.com/slgobinath/SafeEyes/blob/master/pyproject.toml#L35)
|
||||||
- [safeeyes.py](https://github.com/slgobinath/SafeEyes/blob/master/safeeyes/safeeyes.py#L42)
|
|
||||||
- [io.github.slgobinath.SafeEyes.metainfo.xml](https://github.com/slgobinath/SafeEyes/blob/master/safeeyes/platform/io.github.slgobinath.SafeEyes.metainfo.xml#L56)
|
- [io.github.slgobinath.SafeEyes.metainfo.xml](https://github.com/slgobinath/SafeEyes/blob/master/safeeyes/platform/io.github.slgobinath.SafeEyes.metainfo.xml#L56)
|
||||||
- [about_dialog.glade](https://github.com/slgobinath/SafeEyes/blob/master/safeeyes/glade/about_dialog.glade#L74)
|
- [about_dialog.glade](https://github.com/slgobinath/SafeEyes/blob/master/safeeyes/glade/about_dialog.glade#L74)
|
||||||
4. Update the [changelog](https://github.com/slgobinath/SafeEyes/blob/master/debian/changelog) (for Ubuntu PPA release)
|
4. Update the [changelog](https://github.com/slgobinath/SafeEyes/blob/master/debian/changelog) (for Ubuntu PPA release)
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "safeeyes"
|
||||||
|
version = "2.2.2"
|
||||||
|
description = "Protect your eyes from eye strain using this continuous breaks reminder."
|
||||||
|
keywords = ["linux utility health eye-strain safe-eyes"]
|
||||||
|
readme = "README.md"
|
||||||
|
authors = [
|
||||||
|
{name = "Gobinath Loganathan", email = "slgobinath@gmail.com"},
|
||||||
|
]
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 5 - Production/Stable",
|
||||||
|
"Environment :: X11 Applications :: GTK",
|
||||||
|
"Environment :: Other Environment",
|
||||||
|
"Intended Audience :: End Users/Desktop",
|
||||||
|
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
||||||
|
"Operating System :: POSIX :: Linux",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
"Topic :: Utilities",
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"PyGObject",
|
||||||
|
"babel",
|
||||||
|
"psutil",
|
||||||
|
"packaging",
|
||||||
|
"python-xlib",
|
||||||
|
]
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://github.com/slgobinath/SafeEyes"
|
||||||
|
Downloads = "https://github.com/slgobinath/SafeEyes/archive/v2.2.2.tar.gz"
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
safeeyes = "safeeyes.__main__:main"
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
healthstats = ["croniter"]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
include=["safeeyes*"]
|
||||||
|
exclude=["safeeyes.tests*"]
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
addopts = [
|
||||||
|
"--import-mode=importlib",
|
||||||
|
]
|
|
@ -6,8 +6,8 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: \n"
|
"Project-Id-Version: \n"
|
||||||
"POT-Creation-Date: \n"
|
"POT-Creation-Date: \n"
|
||||||
"PO-Revision-Date: 2021-02-18 00:50+0000\n"
|
"PO-Revision-Date: 2024-11-12 23:00+0000\n"
|
||||||
"Last-Translator: Kristjan Räts <kristjanrats@gmail.com>\n"
|
"Last-Translator: Priit Jõerüüt <hwlate@joeruut.com>\n"
|
||||||
"Language-Team: Estonian <https://hosted.weblate.org/projects/safe-eyes/"
|
"Language-Team: Estonian <https://hosted.weblate.org/projects/safe-eyes/"
|
||||||
"translations/et/>\n"
|
"translations/et/>\n"
|
||||||
"Language: et\n"
|
"Language: et\n"
|
||||||
|
@ -15,11 +15,11 @@ msgstr ""
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||||
"X-Generator: Weblate 4.5\n"
|
"X-Generator: Weblate 5.9-dev\n"
|
||||||
|
|
||||||
# Short break
|
# Short break
|
||||||
msgid "Gently close your eyes"
|
msgid "Gently close your eyes"
|
||||||
msgstr ""
|
msgstr "Sulge õrnalt oma silmad"
|
||||||
|
|
||||||
# Short break
|
# Short break
|
||||||
msgid "Roll your eyes a few times to each side"
|
msgid "Roll your eyes a few times to each side"
|
||||||
|
@ -103,7 +103,7 @@ msgid ""
|
||||||
"Safe Eyes protects your eyes from eye strain (asthenopia) by reminding you "
|
"Safe Eyes protects your eyes from eye strain (asthenopia) by reminding you "
|
||||||
"to take breaks while you're working long hours at the computer"
|
"to take breaks while you're working long hours at the computer"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Safe Eyes aitab vähendada arvutiga töötamisel silmade väsimust, tuletades "
|
"Safe Eyes aitab arvutiga töötamisel vähendada silmade väsimust, tuletades "
|
||||||
"meelde puhkepause"
|
"meelde puhkepause"
|
||||||
|
|
||||||
# About dialog
|
# About dialog
|
||||||
|
@ -112,11 +112,11 @@ msgstr "Litsents"
|
||||||
|
|
||||||
# About dialog
|
# About dialog
|
||||||
msgid "List of Contributors"
|
msgid "List of Contributors"
|
||||||
msgstr ""
|
msgstr "Kaasautorite loend"
|
||||||
|
|
||||||
# About dialog
|
# About dialog
|
||||||
msgid "Help us translate this app"
|
msgid "Help us translate this app"
|
||||||
msgstr ""
|
msgstr "Aita meil seda rakendust tõlkida"
|
||||||
|
|
||||||
# Break screen
|
# Break screen
|
||||||
msgid "Skip"
|
msgid "Skip"
|
||||||
|
@ -128,7 +128,7 @@ msgstr "Lükka edasi"
|
||||||
|
|
||||||
# Settings dialog
|
# Settings dialog
|
||||||
msgid "Break duration (in seconds)"
|
msgid "Break duration (in seconds)"
|
||||||
msgstr "Pausi kestvus (sekundites)"
|
msgstr "Pausi kestus (sekundites)"
|
||||||
|
|
||||||
# Settings dialog
|
# Settings dialog
|
||||||
msgid "Interval between two breaks (in minutes)"
|
msgid "Interval between two breaks (in minutes)"
|
||||||
|
@ -244,7 +244,7 @@ msgstr "Palun vali pilt"
|
||||||
|
|
||||||
# Settings dialog
|
# Settings dialog
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr "Kestvus"
|
msgstr "Kestus"
|
||||||
|
|
||||||
# Settings dialog
|
# Settings dialog
|
||||||
msgid "Time to wait"
|
msgid "Time to wait"
|
||||||
|
@ -256,11 +256,11 @@ msgstr "Muuda vaikimisi seadeid"
|
||||||
|
|
||||||
# Settings dialog
|
# Settings dialog
|
||||||
msgid "Time (in seconds)"
|
msgid "Time (in seconds)"
|
||||||
msgstr "Kestvus (sekundites)"
|
msgstr "Kestus (sekundites)"
|
||||||
|
|
||||||
# Settings dialog
|
# Settings dialog
|
||||||
msgid "Time (in minutes)"
|
msgid "Time (in minutes)"
|
||||||
msgstr "Kestvus (minutites)"
|
msgstr "Kestus (minutites)"
|
||||||
|
|
||||||
# Settings dialog
|
# Settings dialog
|
||||||
msgid "Break Settings"
|
msgid "Break Settings"
|
||||||
|
@ -497,15 +497,15 @@ msgstr "Tee nüüd paus"
|
||||||
|
|
||||||
#: plugins/trayicon
|
#: plugins/trayicon
|
||||||
msgid "Any break"
|
msgid "Any break"
|
||||||
msgstr ""
|
msgstr "Iga puhkepaus"
|
||||||
|
|
||||||
#: plugins/trayicon
|
#: plugins/trayicon
|
||||||
msgid "Short break"
|
msgid "Short break"
|
||||||
msgstr ""
|
msgstr "Lühike puhkepaus"
|
||||||
|
|
||||||
#: plugins/trayicon
|
#: plugins/trayicon
|
||||||
msgid "Long break"
|
msgid "Long break"
|
||||||
msgstr ""
|
msgstr "Pikk puhkepaus"
|
||||||
|
|
||||||
#: plugins/trayicon
|
#: plugins/trayicon
|
||||||
msgid "Until restart"
|
msgid "Until restart"
|
||||||
|
@ -529,57 +529,61 @@ msgstr "Peata meedia"
|
||||||
|
|
||||||
# plugin/limitconsecutiveskipping
|
# plugin/limitconsecutiveskipping
|
||||||
msgid "Limit Consecutive Skipping"
|
msgid "Limit Consecutive Skipping"
|
||||||
msgstr ""
|
msgstr "Piira järjest vahelejätmisi"
|
||||||
|
|
||||||
# plugin/limitconsecutiveskipping
|
# plugin/limitconsecutiveskipping
|
||||||
msgid "How many skips or postpones are allowed in a row"
|
msgid "How many skips or postpones are allowed in a row"
|
||||||
msgstr ""
|
msgstr "Kui mitu edasilükkamist või vahelejätmist võid järjest teha"
|
||||||
|
|
||||||
# plugin/limitconsecutiveskipping
|
# plugin/limitconsecutiveskipping
|
||||||
msgid "Limit how many breaks can be skipped or postponed in a row"
|
msgid "Limit how many breaks can be skipped or postponed in a row"
|
||||||
msgstr ""
|
msgstr "Kui mitu pausi võid järjest vahele jätta või edasi lükata"
|
||||||
|
|
||||||
# plugin/limitconsecutiveskipping
|
# plugin/limitconsecutiveskipping
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Skipped or postponed %(num)d/%(allowed)d breaks in a row"
|
msgid "Skipped or postponed %(num)d/%(allowed)d breaks in a row"
|
||||||
msgstr ""
|
msgstr "Järjest vahelejäetud või edasilükatatud %(num)d/%(allowed)d pausi"
|
||||||
|
|
||||||
# safeeyes/platform/io.github.slgobinath.SafeEyes.desktop
|
# safeeyes/platform/io.github.slgobinath.SafeEyes.desktop
|
||||||
msgid "RSI Prevention"
|
msgid "RSI Prevention"
|
||||||
msgstr ""
|
msgstr "RSI ennetus"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Please install service providing tray icons for your desktop environment."
|
"Please install service providing tray icons for your desktop environment."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Palun paigalda teenus, mis võimaldab sinu töölauakeskkonnas kasutada "
|
||||||
|
"süsteemisalve."
|
||||||
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Next long break at %s"
|
msgid "Next long break at %s"
|
||||||
msgstr ""
|
msgstr "Järgmine pikk puhkepaus %s"
|
||||||
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Next breaks at %(short)s/%(long)s"
|
msgid "Next breaks at %(short)s/%(long)s"
|
||||||
msgstr ""
|
msgstr "Järgmised puhkepausid %(short)s/%(long)s"
|
||||||
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "The required plugin '%s' is missing dependencies!"
|
msgid "The required plugin '%s' is missing dependencies!"
|
||||||
msgstr ""
|
msgstr "Vajalikul „%s“ pluginal puuduvad sõltuvad komponendid!"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Please install the dependencies or disable the plugin. To hide this message, "
|
"Please install the dependencies or disable the plugin. To hide this message, "
|
||||||
"you can also deactivate the plugin in the settings."
|
"you can also deactivate the plugin in the settings."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Palun lisa vajalikud komponendid või lülita plugin välja. Viimast saad teha "
|
||||||
|
"seadistustest ja sellega ei teki enam ka antud teadet."
|
||||||
|
|
||||||
msgid "Click here for more information"
|
msgid "Click here for more information"
|
||||||
msgstr ""
|
msgstr "Lisateabeks klõpsi siin"
|
||||||
|
|
||||||
msgid "Disable plugin temporarily"
|
msgid "Disable plugin temporarily"
|
||||||
msgstr ""
|
msgstr "Lülita plugin ajutiselt välja"
|
||||||
|
|
||||||
msgid "Disable permanently"
|
msgid "Disable permanently"
|
||||||
msgstr ""
|
msgstr "Lülita püsivalt välja"
|
||||||
|
|
||||||
msgid "License:"
|
msgid "License:"
|
||||||
msgstr ""
|
msgstr "Litsents:"
|
||||||
|
|
||||||
# Short break
|
# Short break
|
||||||
#~ msgid "Tightly close your eyes"
|
#~ msgid "Tightly close your eyes"
|
||||||
|
|
|
@ -6,7 +6,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: \n"
|
"Project-Id-Version: \n"
|
||||||
"POT-Creation-Date: \n"
|
"POT-Creation-Date: \n"
|
||||||
"PO-Revision-Date: 2024-08-24 17:09+0000\n"
|
"PO-Revision-Date: 2024-11-12 23:00+0000\n"
|
||||||
"Last-Translator: AO Localisation Lab <ao@localizationlab.org>\n"
|
"Last-Translator: AO Localisation Lab <ao@localizationlab.org>\n"
|
||||||
"Language-Team: French <https://hosted.weblate.org/projects/safe-eyes/"
|
"Language-Team: French <https://hosted.weblate.org/projects/safe-eyes/"
|
||||||
"translations/fr/>\n"
|
"translations/fr/>\n"
|
||||||
|
@ -15,7 +15,7 @@ msgstr ""
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||||
"X-Generator: Weblate 5.7.1-dev\n"
|
"X-Generator: Weblate 5.9-dev\n"
|
||||||
|
|
||||||
# Short break
|
# Short break
|
||||||
msgid "Gently close your eyes"
|
msgid "Gently close your eyes"
|
||||||
|
@ -117,7 +117,7 @@ msgstr "Liste des contributeurs"
|
||||||
|
|
||||||
# About dialog
|
# About dialog
|
||||||
msgid "Help us translate this app"
|
msgid "Help us translate this app"
|
||||||
msgstr ""
|
msgstr "Aidez-nous à traduire cette appli"
|
||||||
|
|
||||||
# Break screen
|
# Break screen
|
||||||
msgid "Skip"
|
msgid "Skip"
|
||||||
|
@ -526,7 +526,7 @@ msgstr "Jusqu’au redémarrage"
|
||||||
|
|
||||||
#: plugins/trayicon
|
#: plugins/trayicon
|
||||||
msgid "Quit"
|
msgid "Quit"
|
||||||
msgstr "Fermer"
|
msgstr "Quitter"
|
||||||
|
|
||||||
# plugin/mediacontrol
|
# plugin/mediacontrol
|
||||||
msgid "Media Control"
|
msgid "Media Control"
|
||||||
|
|
|
@ -6,7 +6,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: \n"
|
"Project-Id-Version: \n"
|
||||||
"POT-Creation-Date: \n"
|
"POT-Creation-Date: \n"
|
||||||
"PO-Revision-Date: 2021-05-23 09:32+0000\n"
|
"PO-Revision-Date: 2024-11-11 13:00+0000\n"
|
||||||
"Last-Translator: Yaron Shahrabani <sh.yaron@gmail.com>\n"
|
"Last-Translator: Yaron Shahrabani <sh.yaron@gmail.com>\n"
|
||||||
"Language-Team: Hebrew <https://hosted.weblate.org/projects/safe-eyes/"
|
"Language-Team: Hebrew <https://hosted.weblate.org/projects/safe-eyes/"
|
||||||
"translations/he/>\n"
|
"translations/he/>\n"
|
||||||
|
@ -15,11 +15,11 @@ msgstr ""
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
"X-Generator: Weblate 4.7-dev\n"
|
"X-Generator: Weblate 5.8.2\n"
|
||||||
|
|
||||||
# Short break
|
# Short break
|
||||||
msgid "Gently close your eyes"
|
msgid "Gently close your eyes"
|
||||||
msgstr ""
|
msgstr "לעצום את העיניים בעדינות"
|
||||||
|
|
||||||
# Short break
|
# Short break
|
||||||
msgid "Roll your eyes a few times to each side"
|
msgid "Roll your eyes a few times to each side"
|
||||||
|
@ -110,11 +110,11 @@ msgstr "רישיון"
|
||||||
|
|
||||||
# About dialog
|
# About dialog
|
||||||
msgid "List of Contributors"
|
msgid "List of Contributors"
|
||||||
msgstr ""
|
msgstr "רשימת תורמים"
|
||||||
|
|
||||||
# About dialog
|
# About dialog
|
||||||
msgid "Help us translate this app"
|
msgid "Help us translate this app"
|
||||||
msgstr ""
|
msgstr "נשמח לסיוע בתרגום היישום הזה"
|
||||||
|
|
||||||
# Break screen
|
# Break screen
|
||||||
msgid "Skip"
|
msgid "Skip"
|
||||||
|
@ -495,15 +495,15 @@ msgstr "לקחת הפסקה עכשיו"
|
||||||
|
|
||||||
#: plugins/trayicon
|
#: plugins/trayicon
|
||||||
msgid "Any break"
|
msgid "Any break"
|
||||||
msgstr ""
|
msgstr "כל הפסקה שהיא"
|
||||||
|
|
||||||
#: plugins/trayicon
|
#: plugins/trayicon
|
||||||
msgid "Short break"
|
msgid "Short break"
|
||||||
msgstr ""
|
msgstr "הפסקה קצרה"
|
||||||
|
|
||||||
#: plugins/trayicon
|
#: plugins/trayicon
|
||||||
msgid "Long break"
|
msgid "Long break"
|
||||||
msgstr ""
|
msgstr "הפסקה ארוכה"
|
||||||
|
|
||||||
#: plugins/trayicon
|
#: plugins/trayicon
|
||||||
msgid "Until restart"
|
msgid "Until restart"
|
||||||
|
@ -527,57 +527,59 @@ msgstr "השהיית מדיה"
|
||||||
|
|
||||||
# plugin/limitconsecutiveskipping
|
# plugin/limitconsecutiveskipping
|
||||||
msgid "Limit Consecutive Skipping"
|
msgid "Limit Consecutive Skipping"
|
||||||
msgstr ""
|
msgstr "הגבלת דילוג מחזורי"
|
||||||
|
|
||||||
# plugin/limitconsecutiveskipping
|
# plugin/limitconsecutiveskipping
|
||||||
msgid "How many skips or postpones are allowed in a row"
|
msgid "How many skips or postpones are allowed in a row"
|
||||||
msgstr ""
|
msgstr "כמה דילוגים או השהיות מותר ברצף"
|
||||||
|
|
||||||
# plugin/limitconsecutiveskipping
|
# plugin/limitconsecutiveskipping
|
||||||
msgid "Limit how many breaks can be skipped or postponed in a row"
|
msgid "Limit how many breaks can be skipped or postponed in a row"
|
||||||
msgstr ""
|
msgstr "הגבלת כמות ההפסקות שאפשר לדלג או להשהות ברצף"
|
||||||
|
|
||||||
# plugin/limitconsecutiveskipping
|
# plugin/limitconsecutiveskipping
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Skipped or postponed %(num)d/%(allowed)d breaks in a row"
|
msgid "Skipped or postponed %(num)d/%(allowed)d breaks in a row"
|
||||||
msgstr ""
|
msgstr "דילגת או השהית %(num)d/%(allowed)d הפסקות ברצף"
|
||||||
|
|
||||||
# safeeyes/platform/io.github.slgobinath.SafeEyes.desktop
|
# safeeyes/platform/io.github.slgobinath.SafeEyes.desktop
|
||||||
msgid "RSI Prevention"
|
msgid "RSI Prevention"
|
||||||
msgstr ""
|
msgstr "מניעת פציעת מאמץ חוזרני"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Please install service providing tray icons for your desktop environment."
|
"Please install service providing tray icons for your desktop environment."
|
||||||
msgstr ""
|
msgstr "נא להתקין שירות שמספק סמלי שורת מערכת לסביבת שולחן העבודה שלך."
|
||||||
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Next long break at %s"
|
msgid "Next long break at %s"
|
||||||
msgstr ""
|
msgstr "ההפסקה הארוכה הבאה ב־%s"
|
||||||
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Next breaks at %(short)s/%(long)s"
|
msgid "Next breaks at %(short)s/%(long)s"
|
||||||
msgstr ""
|
msgstr "ההפסקות הבאות ב־%(short)s/%(long)s"
|
||||||
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "The required plugin '%s' is missing dependencies!"
|
msgid "The required plugin '%s' is missing dependencies!"
|
||||||
msgstr ""
|
msgstr "לתוסף ההכרחי ‚%s’ חסרות תלויות!"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Please install the dependencies or disable the plugin. To hide this message, "
|
"Please install the dependencies or disable the plugin. To hide this message, "
|
||||||
"you can also deactivate the plugin in the settings."
|
"you can also deactivate the plugin in the settings."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"נא להתקין את התלויות או להשבית את התוסף. כדי להסתיר את ההודעה הזאת ניתן גם "
|
||||||
|
"להשבית את התוסף בהגדרות."
|
||||||
|
|
||||||
msgid "Click here for more information"
|
msgid "Click here for more information"
|
||||||
msgstr ""
|
msgstr "לחיצה כאן תציג מידע נוסף"
|
||||||
|
|
||||||
msgid "Disable plugin temporarily"
|
msgid "Disable plugin temporarily"
|
||||||
msgstr ""
|
msgstr "השבתת תוסף זמנית"
|
||||||
|
|
||||||
msgid "Disable permanently"
|
msgid "Disable permanently"
|
||||||
msgstr ""
|
msgstr "השבתה לצמיתות"
|
||||||
|
|
||||||
msgid "License:"
|
msgid "License:"
|
||||||
msgstr ""
|
msgstr "רישיון:"
|
||||||
|
|
||||||
# Short break
|
# Short break
|
||||||
#~ msgid "Tightly close your eyes"
|
#~ msgid "Tightly close your eyes"
|
||||||
|
|
|
@ -24,6 +24,7 @@ import atexit
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from threading import Timer
|
from threading import Timer
|
||||||
|
from importlib import metadata
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
from safeeyes import utility
|
from safeeyes import utility
|
||||||
|
@ -36,10 +37,11 @@ from safeeyes.plugin_manager import PluginManager
|
||||||
from safeeyes.core import SafeEyesCore
|
from safeeyes.core import SafeEyesCore
|
||||||
from safeeyes.ui.settings_dialog import SettingsDialog
|
from safeeyes.ui.settings_dialog import SettingsDialog
|
||||||
|
|
||||||
|
|
||||||
gi.require_version('Gtk', '3.0')
|
gi.require_version('Gtk', '3.0')
|
||||||
from gi.repository import Gtk, Gio, GLib
|
from gi.repository import Gtk, Gio, GLib
|
||||||
|
|
||||||
SAFE_EYES_VERSION = "2.2.2"
|
SAFE_EYES_VERSION = metadata.version("safeeyes")
|
||||||
|
|
||||||
|
|
||||||
class SafeEyes(Gtk.Application):
|
class SafeEyes(Gtk.Application):
|
||||||
|
|
|
@ -0,0 +1,584 @@
|
||||||
|
from collections import deque
|
||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from safeeyes import core
|
||||||
|
from safeeyes import model
|
||||||
|
|
||||||
|
import threading
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
class TestSafeEyesCore:
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def set_time(self, time_machine):
|
||||||
|
time_machine.move_to(datetime.datetime.fromisoformat("2024-08-25T13:00:00+00:00"), tick=False)
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def monkeypatch_translations(self, monkeypatch):
|
||||||
|
monkeypatch.setattr(core, "_", lambda message: "translated!: " + message, raising=False)
|
||||||
|
monkeypatch.setattr(model, "_", lambda message: "translated!: " + message, raising=False)
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sequential_threading(self, monkeypatch, time_machine):
|
||||||
|
"""This fixture allows stopping threads at any point.
|
||||||
|
|
||||||
|
It is hard-coded for SafeEyesCore, the handle class returned by the fixture must be initialized
|
||||||
|
with a SafeEyesCore instance to be patched.
|
||||||
|
With this, all sleeping/blocking/thread starting calls inside SafeEyesCore are intercepted, and paused.
|
||||||
|
Additionally, all threads inside SafeEyesCore run sequentially.
|
||||||
|
The test code can use the next() method to unpause the thread,
|
||||||
|
which will run until the next sleeping/blocking/thread starting call,
|
||||||
|
after which it needs to be woken up using next() again.
|
||||||
|
The next() method blocks the test code while the thread is running.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# executes instantly, on the same thread
|
||||||
|
# no need to switch threads, as we don't use any gtk things
|
||||||
|
monkeypatch.setattr(
|
||||||
|
core.utility,
|
||||||
|
"execute_main_thread",
|
||||||
|
lambda target_function, *args, **kwargs: target_function(*args, **kwargs)
|
||||||
|
)
|
||||||
|
|
||||||
|
handle = None
|
||||||
|
|
||||||
|
def utility_start_thread(target_function, **kwargs):
|
||||||
|
if not handle:
|
||||||
|
raise Exception("handle must be initialized before first thread")
|
||||||
|
handle.utility_start_thread(target_function, **kwargs)
|
||||||
|
|
||||||
|
def time_sleep(time):
|
||||||
|
if not handle:
|
||||||
|
raise Exception("handle must be initialized before first sleep call")
|
||||||
|
handle.sleep(time)
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
core.utility,
|
||||||
|
"start_thread",
|
||||||
|
utility_start_thread
|
||||||
|
)
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
core.time,
|
||||||
|
"sleep",
|
||||||
|
time_sleep
|
||||||
|
)
|
||||||
|
|
||||||
|
class PatchedCondition(threading.Condition):
|
||||||
|
def __init__(self, handle):
|
||||||
|
super().__init__()
|
||||||
|
self.handle = handle
|
||||||
|
|
||||||
|
def wait(self, timeout):
|
||||||
|
self.handle.wait_condvar(timeout)
|
||||||
|
|
||||||
|
|
||||||
|
class Handle:
|
||||||
|
thread = None
|
||||||
|
task_queue = deque()
|
||||||
|
running = True
|
||||||
|
condvar_in = threading.Condition()
|
||||||
|
condvar_out = threading.Condition()
|
||||||
|
|
||||||
|
def __init__(self, safe_eyes_core):
|
||||||
|
nonlocal handle
|
||||||
|
nonlocal time_machine
|
||||||
|
if handle:
|
||||||
|
raise Exception("only one handle is allowed per test call")
|
||||||
|
handle = self
|
||||||
|
self.time_machine = time_machine
|
||||||
|
self.safe_eyes_core = safe_eyes_core
|
||||||
|
self.safe_eyes_core.waiting_condition = PatchedCondition(self)
|
||||||
|
|
||||||
|
|
||||||
|
def background_thread(self):
|
||||||
|
while True:
|
||||||
|
with self.condvar_in:
|
||||||
|
success = self.condvar_in.wait(1)
|
||||||
|
if not success:
|
||||||
|
raise Exception("thread timed out")
|
||||||
|
|
||||||
|
if not self.running:
|
||||||
|
break
|
||||||
|
|
||||||
|
if self.task_queue:
|
||||||
|
(target_function, kwargs) = self.task_queue.popleft()
|
||||||
|
logging.debug(f"thread started {target_function}")
|
||||||
|
target_function(**kwargs)
|
||||||
|
logging.debug(f"thread finished {target_function}")
|
||||||
|
|
||||||
|
with self.condvar_out:
|
||||||
|
self.condvar_out.notify()
|
||||||
|
|
||||||
|
def sleep(self, time):
|
||||||
|
if self.thread is threading.current_thread():
|
||||||
|
with self.condvar_out:
|
||||||
|
self.condvar_out.notify()
|
||||||
|
self.time_machine.shift(delta=datetime.timedelta(seconds=time))
|
||||||
|
with self.condvar_in:
|
||||||
|
success = self.condvar_in.wait(1)
|
||||||
|
if not success:
|
||||||
|
raise Exception("thread timed out")
|
||||||
|
|
||||||
|
def wait_condvar(self, time):
|
||||||
|
if self.thread is not threading.current_thread():
|
||||||
|
raise Exception("waiting on condition may only happen in thread")
|
||||||
|
|
||||||
|
with self.condvar_out:
|
||||||
|
self.condvar_out.notify()
|
||||||
|
self.time_machine.shift(delta=datetime.timedelta(seconds=time))
|
||||||
|
with self.condvar_in:
|
||||||
|
success = self.condvar_in.wait(1)
|
||||||
|
if not success:
|
||||||
|
raise Exception("thread timed out")
|
||||||
|
|
||||||
|
def utility_start_thread(self, target_function, **kwargs):
|
||||||
|
self.task_queue.append((target_function, kwargs))
|
||||||
|
|
||||||
|
if self.thread is None:
|
||||||
|
self.thread = threading.Thread(target=self.background_thread, name="SequentialThreadingRunner", daemon=False, kwargs=kwargs)
|
||||||
|
self.thread.start()
|
||||||
|
|
||||||
|
def next(self):
|
||||||
|
assert self.thread
|
||||||
|
|
||||||
|
with self.condvar_in:
|
||||||
|
self.condvar_in.notify()
|
||||||
|
|
||||||
|
# wait until done:
|
||||||
|
with self.condvar_out:
|
||||||
|
success = self.condvar_out.wait(1)
|
||||||
|
if not success:
|
||||||
|
raise Exception("thread timed out")
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.running = False
|
||||||
|
with self.condvar_in:
|
||||||
|
self.condvar_in.notify()
|
||||||
|
|
||||||
|
if self.thread:
|
||||||
|
self.thread.join(1)
|
||||||
|
|
||||||
|
yield Handle
|
||||||
|
|
||||||
|
if handle:
|
||||||
|
handle.stop()
|
||||||
|
|
||||||
|
|
||||||
|
def run_next_break(
|
||||||
|
self,
|
||||||
|
sequential_threading_handle,
|
||||||
|
time_machine,
|
||||||
|
safe_eyes_core,
|
||||||
|
context,
|
||||||
|
break_duration,
|
||||||
|
break_name_translated
|
||||||
|
):
|
||||||
|
"""Run one entire cycle of safe_eyes_core.
|
||||||
|
|
||||||
|
It must be waiting for __scheduler_job to run. (This is the equivalent of State.WAITING).
|
||||||
|
That means it must either be just started, or have finished the previous cycle.
|
||||||
|
"""
|
||||||
|
|
||||||
|
on_update_next_break = mock.Mock()
|
||||||
|
on_pre_break = mock.Mock(return_value=True)
|
||||||
|
on_start_break = mock.Mock(return_value=True)
|
||||||
|
start_break = mock.Mock()
|
||||||
|
on_count_down = mock.Mock()
|
||||||
|
|
||||||
|
safe_eyes_core.on_update_next_break += on_update_next_break
|
||||||
|
safe_eyes_core.on_pre_break += on_pre_break
|
||||||
|
safe_eyes_core.on_start_break += on_start_break
|
||||||
|
safe_eyes_core.start_break += start_break
|
||||||
|
safe_eyes_core.on_count_down += on_count_down
|
||||||
|
|
||||||
|
# start __scheduler_job
|
||||||
|
sequential_threading_handle.next()
|
||||||
|
# wait until it reaches the condvar
|
||||||
|
|
||||||
|
|
||||||
|
assert context['state'] == model.State.WAITING
|
||||||
|
|
||||||
|
on_update_next_break.assert_called_once()
|
||||||
|
assert isinstance(on_update_next_break.call_args[0][0], model.Break)
|
||||||
|
assert on_update_next_break.call_args[0][0].name == break_name_translated
|
||||||
|
on_update_next_break.reset_mock()
|
||||||
|
|
||||||
|
# continue after condvar
|
||||||
|
sequential_threading_handle.next()
|
||||||
|
# end of __scheduler_job
|
||||||
|
|
||||||
|
assert context['state'] == model.State.PRE_BREAK
|
||||||
|
|
||||||
|
on_pre_break.assert_called_once()
|
||||||
|
assert isinstance(on_pre_break.call_args[0][0], model.Break)
|
||||||
|
assert on_pre_break.call_args[0][0].name == break_name_translated
|
||||||
|
on_pre_break.reset_mock()
|
||||||
|
|
||||||
|
# start __wait_until_prepare
|
||||||
|
sequential_threading_handle.next()
|
||||||
|
|
||||||
|
# wait until it reaches the condvar
|
||||||
|
# continue after condvar
|
||||||
|
sequential_threading_handle.next()
|
||||||
|
# end of __wait_until_prepare
|
||||||
|
|
||||||
|
# start __start_break
|
||||||
|
sequential_threading_handle.next()
|
||||||
|
|
||||||
|
# first sleep in __start_break
|
||||||
|
sequential_threading_handle.next()
|
||||||
|
|
||||||
|
on_start_break.assert_called_once()
|
||||||
|
assert isinstance(on_start_break.call_args[0][0], model.Break)
|
||||||
|
assert on_start_break.call_args[0][0].name == break_name_translated
|
||||||
|
on_start_break.reset_mock()
|
||||||
|
|
||||||
|
start_break.assert_called_once()
|
||||||
|
assert isinstance(start_break.call_args[0][0], model.Break)
|
||||||
|
assert start_break.call_args[0][0].name == break_name_translated
|
||||||
|
start_break.reset_mock()
|
||||||
|
|
||||||
|
assert context['state'] == model.State.BREAK
|
||||||
|
|
||||||
|
# continue sleep in __start_break
|
||||||
|
for i in range(break_duration - 2):
|
||||||
|
sequential_threading_handle.next()
|
||||||
|
|
||||||
|
sequential_threading_handle.next()
|
||||||
|
# end of __start_break
|
||||||
|
|
||||||
|
on_count_down.assert_called()
|
||||||
|
assert on_count_down.call_count == break_duration
|
||||||
|
on_count_down.reset_mock()
|
||||||
|
|
||||||
|
assert context['state'] == model.State.BREAK
|
||||||
|
|
||||||
|
|
||||||
|
def assert_datetime(self, string):
|
||||||
|
if not string.endswith("+00:00"):
|
||||||
|
string += "+00:00"
|
||||||
|
assert datetime.datetime.now(datetime.timezone.utc) == datetime.datetime.fromisoformat(string)
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_empty(self):
|
||||||
|
context = {}
|
||||||
|
config = {
|
||||||
|
"short_breaks": [],
|
||||||
|
"long_breaks": [],
|
||||||
|
"short_break_interval": 15,
|
||||||
|
"long_break_interval": 75,
|
||||||
|
"long_break_duration": 60,
|
||||||
|
"short_break_duration": 15,
|
||||||
|
"random_order": False,
|
||||||
|
"postpone_duration": 5,
|
||||||
|
}
|
||||||
|
safe_eyes_core = core.SafeEyesCore(context)
|
||||||
|
safe_eyes_core.initialize(config)
|
||||||
|
|
||||||
|
|
||||||
|
def test_start_empty(self, sequential_threading):
|
||||||
|
context = {}
|
||||||
|
config = {
|
||||||
|
"short_breaks": [],
|
||||||
|
"long_breaks": [],
|
||||||
|
"short_break_interval": 15,
|
||||||
|
"long_break_interval": 75,
|
||||||
|
"long_break_duration": 60,
|
||||||
|
"short_break_duration": 15,
|
||||||
|
"random_order": False,
|
||||||
|
"postpone_duration": 5,
|
||||||
|
}
|
||||||
|
on_update_next_break = mock.Mock()
|
||||||
|
safe_eyes_core = core.SafeEyesCore(context)
|
||||||
|
safe_eyes_core.on_update_next_break += mock
|
||||||
|
|
||||||
|
safe_eyes_core.initialize(config)
|
||||||
|
|
||||||
|
safe_eyes_core.start()
|
||||||
|
safe_eyes_core.stop()
|
||||||
|
|
||||||
|
on_update_next_break.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
def test_start(self, sequential_threading, time_machine):
|
||||||
|
context = {
|
||||||
|
"session": {},
|
||||||
|
}
|
||||||
|
config = {
|
||||||
|
"short_breaks": [
|
||||||
|
{"name": "break 1"},
|
||||||
|
{"name": "break 2"},
|
||||||
|
{"name": "break 3"},
|
||||||
|
{"name": "break 4"},
|
||||||
|
],
|
||||||
|
"long_breaks": [
|
||||||
|
{"name": "long break 1"},
|
||||||
|
{"name": "long break 2"},
|
||||||
|
{"name": "long break 3"},
|
||||||
|
],
|
||||||
|
"short_break_interval": 15,
|
||||||
|
"long_break_interval": 75,
|
||||||
|
"long_break_duration": 60,
|
||||||
|
"short_break_duration": 15,
|
||||||
|
"random_order": False,
|
||||||
|
"postpone_duration": 5,
|
||||||
|
}
|
||||||
|
on_update_next_break = mock.Mock()
|
||||||
|
safe_eyes_core = core.SafeEyesCore(context)
|
||||||
|
safe_eyes_core.on_update_next_break += on_update_next_break
|
||||||
|
|
||||||
|
safe_eyes_core.initialize(config)
|
||||||
|
|
||||||
|
sequential_threading_handle = sequential_threading(safe_eyes_core)
|
||||||
|
|
||||||
|
safe_eyes_core.start()
|
||||||
|
|
||||||
|
# start __scheduler_job
|
||||||
|
sequential_threading_handle.next()
|
||||||
|
|
||||||
|
assert context['state'] == model.State.WAITING
|
||||||
|
|
||||||
|
on_update_next_break.assert_called_once()
|
||||||
|
assert isinstance(on_update_next_break.call_args[0][0], model.Break)
|
||||||
|
assert on_update_next_break.call_args[0][0].name == "translated!: break 1"
|
||||||
|
on_update_next_break.reset_mock()
|
||||||
|
|
||||||
|
# wait for end of __scheduler_job - we cannot stop while waiting on the condvar
|
||||||
|
# this just moves us into waiting for __wait_until_prepare to start
|
||||||
|
sequential_threading_handle.next()
|
||||||
|
|
||||||
|
safe_eyes_core.stop()
|
||||||
|
assert context['state'] == model.State.STOPPED
|
||||||
|
|
||||||
|
|
||||||
|
def test_full_run_with_defaults(self, sequential_threading, time_machine):
|
||||||
|
context = {
|
||||||
|
"session": {},
|
||||||
|
}
|
||||||
|
short_break_duration = 15 # seconds
|
||||||
|
short_break_interval = 15 # minutes
|
||||||
|
pre_break_warning_time = 10 # seconds
|
||||||
|
long_break_duration = 60 # seconds
|
||||||
|
long_break_interval = 75 # minutes
|
||||||
|
config = {
|
||||||
|
"short_breaks": [
|
||||||
|
{"name": "break 1"},
|
||||||
|
{"name": "break 2"},
|
||||||
|
{"name": "break 3"},
|
||||||
|
{"name": "break 4"},
|
||||||
|
],
|
||||||
|
"long_breaks": [
|
||||||
|
{"name": "long break 1"},
|
||||||
|
{"name": "long break 2"},
|
||||||
|
{"name": "long break 3"},
|
||||||
|
],
|
||||||
|
"short_break_interval": short_break_interval,
|
||||||
|
"long_break_interval": long_break_interval,
|
||||||
|
"long_break_duration": long_break_duration,
|
||||||
|
"short_break_duration": short_break_duration,
|
||||||
|
"pre_break_warning_time": pre_break_warning_time,
|
||||||
|
"random_order": False,
|
||||||
|
"postpone_duration": 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assert_datetime("2024-08-25T13:00:00")
|
||||||
|
|
||||||
|
safe_eyes_core = core.SafeEyesCore(context)
|
||||||
|
|
||||||
|
sequential_threading_handle = sequential_threading(safe_eyes_core)
|
||||||
|
|
||||||
|
safe_eyes_core.initialize(config)
|
||||||
|
|
||||||
|
safe_eyes_core.start()
|
||||||
|
|
||||||
|
|
||||||
|
self.run_next_break(
|
||||||
|
sequential_threading_handle,
|
||||||
|
time_machine,
|
||||||
|
safe_eyes_core,
|
||||||
|
context,
|
||||||
|
short_break_duration,
|
||||||
|
"translated!: break 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Time passed: 15min 25s
|
||||||
|
# 15min short_break_interval, 10 seconds pre_break_warning_time, 15 seconds short_break_duration
|
||||||
|
self.assert_datetime("2024-08-25T13:15:25")
|
||||||
|
|
||||||
|
self.run_next_break(
|
||||||
|
sequential_threading_handle,
|
||||||
|
time_machine,
|
||||||
|
safe_eyes_core,
|
||||||
|
context,
|
||||||
|
short_break_duration,
|
||||||
|
"translated!: break 2"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assert_datetime("2024-08-25T13:30:50")
|
||||||
|
|
||||||
|
self.run_next_break(
|
||||||
|
sequential_threading_handle,
|
||||||
|
time_machine,
|
||||||
|
safe_eyes_core,
|
||||||
|
context,
|
||||||
|
short_break_duration,
|
||||||
|
"translated!: break 3"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assert_datetime("2024-08-25T13:46:15")
|
||||||
|
|
||||||
|
self.run_next_break(
|
||||||
|
sequential_threading_handle,
|
||||||
|
time_machine,
|
||||||
|
safe_eyes_core,
|
||||||
|
context,
|
||||||
|
short_break_duration,
|
||||||
|
"translated!: break 4"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assert_datetime("2024-08-25T14:01:40")
|
||||||
|
|
||||||
|
self.run_next_break(
|
||||||
|
sequential_threading_handle,
|
||||||
|
time_machine,
|
||||||
|
safe_eyes_core,
|
||||||
|
context,
|
||||||
|
long_break_duration,
|
||||||
|
"translated!: long break 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Time passed: 16min 10s
|
||||||
|
# 15min short_break_interval (from previous, as long_break_interval must be multiple)
|
||||||
|
# 10 seconds pre_break_warning_time, 1 minute long_break_duration
|
||||||
|
self.assert_datetime("2024-08-25T14:17:50")
|
||||||
|
|
||||||
|
self.run_next_break(
|
||||||
|
sequential_threading_handle,
|
||||||
|
time_machine,
|
||||||
|
safe_eyes_core,
|
||||||
|
context,
|
||||||
|
short_break_duration,
|
||||||
|
"translated!: break 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Time passed: 15min 25s
|
||||||
|
# 15min short_break_interval, 10 seconds pre_break_warning_time, 15 seconds short_break_duration
|
||||||
|
self.assert_datetime("2024-08-25T14:33:15")
|
||||||
|
|
||||||
|
safe_eyes_core.stop()
|
||||||
|
|
||||||
|
assert context['state'] == model.State.STOPPED
|
||||||
|
|
||||||
|
|
||||||
|
def test_long_duration_is_bigger_than_short_interval(self, sequential_threading, time_machine):
|
||||||
|
"""Example taken from https://github.com/slgobinath/SafeEyes/issues/640"""
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"session": {},
|
||||||
|
}
|
||||||
|
short_break_duration = 300 # seconds = 5min
|
||||||
|
short_break_interval = 25 # minutes
|
||||||
|
pre_break_warning_time = 10 # seconds
|
||||||
|
long_break_duration = 1800 # seconds = 30min
|
||||||
|
long_break_interval = 100 # minutes
|
||||||
|
config = {
|
||||||
|
"short_breaks": [
|
||||||
|
{"name": "break 1"},
|
||||||
|
{"name": "break 2"},
|
||||||
|
{"name": "break 3"},
|
||||||
|
{"name": "break 4"},
|
||||||
|
],
|
||||||
|
"long_breaks": [
|
||||||
|
{"name": "long break 1"},
|
||||||
|
{"name": "long break 2"},
|
||||||
|
{"name": "long break 3"},
|
||||||
|
],
|
||||||
|
"short_break_interval": short_break_interval,
|
||||||
|
"long_break_interval": long_break_interval,
|
||||||
|
"long_break_duration": long_break_duration,
|
||||||
|
"short_break_duration": short_break_duration,
|
||||||
|
"pre_break_warning_time": pre_break_warning_time,
|
||||||
|
"random_order": False,
|
||||||
|
"postpone_duration": 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assert_datetime("2024-08-25T13:00:00")
|
||||||
|
|
||||||
|
safe_eyes_core = core.SafeEyesCore(context)
|
||||||
|
|
||||||
|
sequential_threading_handle = sequential_threading(safe_eyes_core)
|
||||||
|
|
||||||
|
safe_eyes_core.initialize(config)
|
||||||
|
|
||||||
|
safe_eyes_core.start()
|
||||||
|
|
||||||
|
|
||||||
|
self.run_next_break(
|
||||||
|
sequential_threading_handle,
|
||||||
|
time_machine,
|
||||||
|
safe_eyes_core,
|
||||||
|
context,
|
||||||
|
short_break_duration,
|
||||||
|
"translated!: break 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Time passed: 30m 10s
|
||||||
|
# 25min short_break_interval, 10 seconds pre_break_warning_time, 5 minutes short_break_duration
|
||||||
|
self.assert_datetime("2024-08-25T13:30:10")
|
||||||
|
|
||||||
|
self.run_next_break(
|
||||||
|
sequential_threading_handle,
|
||||||
|
time_machine,
|
||||||
|
safe_eyes_core,
|
||||||
|
context,
|
||||||
|
short_break_duration,
|
||||||
|
"translated!: break 2"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assert_datetime("2024-08-25T14:00:20")
|
||||||
|
|
||||||
|
self.run_next_break(
|
||||||
|
sequential_threading_handle,
|
||||||
|
time_machine,
|
||||||
|
safe_eyes_core,
|
||||||
|
context,
|
||||||
|
short_break_duration,
|
||||||
|
"translated!: break 3"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assert_datetime("2024-08-25T14:30:30")
|
||||||
|
|
||||||
|
self.run_next_break(
|
||||||
|
sequential_threading_handle,
|
||||||
|
time_machine,
|
||||||
|
safe_eyes_core,
|
||||||
|
context,
|
||||||
|
long_break_duration,
|
||||||
|
"translated!: long break 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Time passed: 55min 10s
|
||||||
|
# 25min short_break_interval (from previous, as long_break_interval must be multiple)
|
||||||
|
# 10 seconds pre_break_warning_time, 30 minute long_break_duration
|
||||||
|
self.assert_datetime("2024-08-25T15:25:40")
|
||||||
|
|
||||||
|
self.run_next_break(
|
||||||
|
sequential_threading_handle,
|
||||||
|
time_machine,
|
||||||
|
safe_eyes_core,
|
||||||
|
context,
|
||||||
|
short_break_duration,
|
||||||
|
"translated!: break 4"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Time passed: 30m 10s
|
||||||
|
# 15min short_break_interval, 10 seconds pre_break_warning_time, 15 seconds short_break_duration
|
||||||
|
self.assert_datetime("2024-08-25T15:55:50")
|
||||||
|
|
||||||
|
safe_eyes_core.stop()
|
||||||
|
|
||||||
|
assert context['state'] == model.State.STOPPED
|
|
@ -0,0 +1,354 @@
|
||||||
|
# Safe Eyes is a utility to remind you to take break frequently
|
||||||
|
# to protect your eyes from eye strain.
|
||||||
|
|
||||||
|
# Copyright (C) 2017 Gobinath
|
||||||
|
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import random
|
||||||
|
from safeeyes import model
|
||||||
|
|
||||||
|
class TestBreak:
|
||||||
|
def test_break_short(self):
|
||||||
|
b = model.Break(
|
||||||
|
model.BreakType.SHORT_BREAK,
|
||||||
|
"test break",
|
||||||
|
15,
|
||||||
|
15,
|
||||||
|
None,
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
assert b.is_short_break()
|
||||||
|
assert not b.is_long_break()
|
||||||
|
|
||||||
|
def test_break_long(self):
|
||||||
|
b = model.Break(
|
||||||
|
model.BreakType.LONG_BREAK,
|
||||||
|
"long break",
|
||||||
|
75,
|
||||||
|
60,
|
||||||
|
None,
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
assert not b.is_short_break()
|
||||||
|
assert b.is_long_break()
|
||||||
|
|
||||||
|
|
||||||
|
class TestBreakQueue:
|
||||||
|
def test_create_empty(self):
|
||||||
|
config = {
|
||||||
|
"short_breaks": [],
|
||||||
|
"long_breaks": [],
|
||||||
|
"short_break_interval": 15,
|
||||||
|
"long_break_interval": 75,
|
||||||
|
"long_break_duration": 60,
|
||||||
|
"short_break_duration": 15,
|
||||||
|
"random_order": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
context = {}
|
||||||
|
|
||||||
|
bq = model.BreakQueue(
|
||||||
|
config,
|
||||||
|
context
|
||||||
|
)
|
||||||
|
|
||||||
|
assert bq.is_empty()
|
||||||
|
assert bq.is_empty(model.BreakType.LONG_BREAK)
|
||||||
|
assert bq.is_empty(model.BreakType.SHORT_BREAK)
|
||||||
|
assert bq.next() is None
|
||||||
|
assert bq.get_break() is None
|
||||||
|
|
||||||
|
|
||||||
|
def get_bq_only_short(self, monkeypatch, random_seed=None):
|
||||||
|
if random_seed is not None:
|
||||||
|
random.seed(random_seed)
|
||||||
|
|
||||||
|
monkeypatch.setattr(model, "_", lambda message: "translated!: " + message, raising=False)
|
||||||
|
|
||||||
|
config = {
|
||||||
|
"short_breaks": [
|
||||||
|
{"name": "break 1"},
|
||||||
|
{"name": "break 2"},
|
||||||
|
{"name": "break 3"},
|
||||||
|
],
|
||||||
|
"long_breaks": [],
|
||||||
|
"short_break_interval": 15,
|
||||||
|
"long_break_interval": 75,
|
||||||
|
"long_break_duration": 60,
|
||||||
|
"short_break_duration": 15,
|
||||||
|
"random_order": random_seed is not None,
|
||||||
|
}
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"session": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
return model.BreakQueue(
|
||||||
|
config,
|
||||||
|
context
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_bq_only_long(self, monkeypatch, random_seed=None):
|
||||||
|
if random_seed is not None:
|
||||||
|
random.seed(random_seed)
|
||||||
|
|
||||||
|
monkeypatch.setattr(model, "_", lambda message: "translated!: " + message, raising=False)
|
||||||
|
|
||||||
|
config = {
|
||||||
|
"short_breaks": [],
|
||||||
|
"long_breaks": [
|
||||||
|
{"name": "long break 1"},
|
||||||
|
{"name": "long break 2"},
|
||||||
|
{"name": "long break 3"},
|
||||||
|
],
|
||||||
|
"short_break_interval": 15,
|
||||||
|
"long_break_interval": 75,
|
||||||
|
"long_break_duration": 60,
|
||||||
|
"short_break_duration": 15,
|
||||||
|
"random_order": random_seed is not None,
|
||||||
|
}
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"session": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
return model.BreakQueue(
|
||||||
|
config,
|
||||||
|
context
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_bq_full(self, monkeypatch, random_seed=None):
|
||||||
|
if random_seed is not None:
|
||||||
|
random.seed(random_seed)
|
||||||
|
|
||||||
|
monkeypatch.setattr(model, "_", lambda message: "translated!: " + message, raising=False)
|
||||||
|
|
||||||
|
config = {
|
||||||
|
"short_breaks": [
|
||||||
|
{"name": "break 1"},
|
||||||
|
{"name": "break 2"},
|
||||||
|
{"name": "break 3"},
|
||||||
|
{"name": "break 4"},
|
||||||
|
],
|
||||||
|
"long_breaks": [
|
||||||
|
{"name": "long break 1"},
|
||||||
|
{"name": "long break 2"},
|
||||||
|
{"name": "long break 3"},
|
||||||
|
],
|
||||||
|
"short_break_interval": 15,
|
||||||
|
"long_break_interval": 75,
|
||||||
|
"long_break_duration": 60,
|
||||||
|
"short_break_duration": 15,
|
||||||
|
"random_order": random_seed is not None,
|
||||||
|
}
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"session": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
return model.BreakQueue(
|
||||||
|
config,
|
||||||
|
context
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_only_short(self, monkeypatch):
|
||||||
|
bq = self.get_bq_only_short(monkeypatch)
|
||||||
|
|
||||||
|
assert not bq.is_empty()
|
||||||
|
assert not bq.is_empty(model.BreakType.SHORT_BREAK)
|
||||||
|
assert bq.is_empty(model.BreakType.LONG_BREAK)
|
||||||
|
|
||||||
|
|
||||||
|
def test_only_short_repeat_get_break_no_change(self, monkeypatch):
|
||||||
|
bq = self.get_bq_only_short(monkeypatch)
|
||||||
|
|
||||||
|
next = bq.get_break()
|
||||||
|
assert next.name == "translated!: break 1"
|
||||||
|
|
||||||
|
next = bq.get_break()
|
||||||
|
assert next.name == "translated!: break 1"
|
||||||
|
|
||||||
|
assert not bq.is_long_break()
|
||||||
|
|
||||||
|
|
||||||
|
def test_only_short_next_break(self, monkeypatch):
|
||||||
|
bq = self.get_bq_only_short(monkeypatch)
|
||||||
|
|
||||||
|
next = bq.get_break()
|
||||||
|
assert next.name == "translated!: break 1"
|
||||||
|
|
||||||
|
assert bq.next().name == "translated!: break 2"
|
||||||
|
assert bq.next().name == "translated!: break 3"
|
||||||
|
assert bq.next().name == "translated!: break 1"
|
||||||
|
assert bq.next().name == "translated!: break 2"
|
||||||
|
assert bq.next().name == "translated!: break 3"
|
||||||
|
assert bq.next().name == "translated!: break 1"
|
||||||
|
assert bq.next().name == "translated!: break 2"
|
||||||
|
assert bq.next().name == "translated!: break 3"
|
||||||
|
|
||||||
|
|
||||||
|
def test_only_short_next_break_random(self, monkeypatch):
|
||||||
|
random_seed = 5
|
||||||
|
bq = self.get_bq_only_short(monkeypatch, random_seed)
|
||||||
|
|
||||||
|
next = bq.get_break()
|
||||||
|
assert next.name == "translated!: break 3"
|
||||||
|
|
||||||
|
assert bq.next().name == "translated!: break 2"
|
||||||
|
assert bq.next().name == "translated!: break 1"
|
||||||
|
assert bq.next().name == "translated!: break 3"
|
||||||
|
assert bq.next().name == "translated!: break 1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_only_long(self, monkeypatch):
|
||||||
|
bq = self.get_bq_only_long(monkeypatch)
|
||||||
|
|
||||||
|
assert not bq.is_empty()
|
||||||
|
assert not bq.is_empty(model.BreakType.LONG_BREAK)
|
||||||
|
assert bq.is_empty(model.BreakType.SHORT_BREAK)
|
||||||
|
|
||||||
|
|
||||||
|
def test_only_long_repeat_get_break_no_change(self, monkeypatch):
|
||||||
|
bq = self.get_bq_only_long(monkeypatch)
|
||||||
|
|
||||||
|
next = bq.get_break()
|
||||||
|
assert next.name == "translated!: long break 1"
|
||||||
|
|
||||||
|
next = bq.get_break()
|
||||||
|
assert next.name == "translated!: long break 1"
|
||||||
|
|
||||||
|
assert bq.is_long_break()
|
||||||
|
|
||||||
|
|
||||||
|
def test_only_long_next_break(self, monkeypatch):
|
||||||
|
bq = self.get_bq_only_long(monkeypatch)
|
||||||
|
|
||||||
|
next = bq.get_break()
|
||||||
|
assert next.name == "translated!: long break 1"
|
||||||
|
|
||||||
|
assert bq.next().name == "translated!: long break 2"
|
||||||
|
assert bq.next().name == "translated!: long break 3"
|
||||||
|
assert bq.next().name == "translated!: long break 1"
|
||||||
|
assert bq.next().name == "translated!: long break 2"
|
||||||
|
assert bq.next().name == "translated!: long break 3"
|
||||||
|
assert bq.next().name == "translated!: long break 1"
|
||||||
|
assert bq.next().name == "translated!: long break 2"
|
||||||
|
assert bq.next().name == "translated!: long break 3"
|
||||||
|
|
||||||
|
|
||||||
|
def test_only_long_next_break_random(self, monkeypatch):
|
||||||
|
random_seed = 5
|
||||||
|
bq = self.get_bq_only_long(monkeypatch, random_seed)
|
||||||
|
|
||||||
|
next = bq.get_break()
|
||||||
|
assert next.name == "translated!: long break 3"
|
||||||
|
|
||||||
|
assert bq.next().name == "translated!: long break 2"
|
||||||
|
assert bq.next().name == "translated!: long break 1"
|
||||||
|
assert bq.next().name == "translated!: long break 3"
|
||||||
|
assert bq.next().name == "translated!: long break 1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_full(self, monkeypatch):
|
||||||
|
bq = self.get_bq_full(monkeypatch)
|
||||||
|
|
||||||
|
assert not bq.is_empty()
|
||||||
|
assert not bq.is_empty(model.BreakType.LONG_BREAK)
|
||||||
|
assert not bq.is_empty(model.BreakType.SHORT_BREAK)
|
||||||
|
|
||||||
|
|
||||||
|
def test_full_repeat_get_break_no_change(self, monkeypatch):
|
||||||
|
bq = self.get_bq_full(monkeypatch)
|
||||||
|
|
||||||
|
next = bq.get_break()
|
||||||
|
assert next.name == "translated!: break 1"
|
||||||
|
|
||||||
|
next = bq.get_break()
|
||||||
|
assert next.name == "translated!: break 1"
|
||||||
|
|
||||||
|
assert not bq.is_long_break()
|
||||||
|
|
||||||
|
|
||||||
|
def test_full_next_break(self, monkeypatch):
|
||||||
|
bq = self.get_bq_full(monkeypatch)
|
||||||
|
|
||||||
|
next = bq.get_break()
|
||||||
|
assert next.name == "translated!: break 1"
|
||||||
|
assert not bq.is_long_break()
|
||||||
|
|
||||||
|
assert bq.next().name == "translated!: break 2"
|
||||||
|
assert bq.next().name == "translated!: break 3"
|
||||||
|
assert bq.next().name == "translated!: break 4"
|
||||||
|
assert bq.next().name == "translated!: long break 1"
|
||||||
|
assert bq.is_long_break()
|
||||||
|
assert bq.next().name == "translated!: break 1"
|
||||||
|
assert not bq.is_long_break()
|
||||||
|
assert bq.next().name == "translated!: break 2"
|
||||||
|
assert bq.next().name == "translated!: break 3"
|
||||||
|
assert bq.next().name == "translated!: break 4"
|
||||||
|
assert bq.next().name == "translated!: long break 2"
|
||||||
|
assert bq.next().name == "translated!: break 1"
|
||||||
|
assert bq.next().name == "translated!: break 2"
|
||||||
|
assert bq.next().name == "translated!: break 3"
|
||||||
|
assert bq.next().name == "translated!: break 4"
|
||||||
|
assert bq.next().name == "translated!: long break 3"
|
||||||
|
assert bq.next().name == "translated!: break 1"
|
||||||
|
assert bq.next().name == "translated!: break 2"
|
||||||
|
assert bq.next().name == "translated!: break 3"
|
||||||
|
assert bq.next().name == "translated!: break 4"
|
||||||
|
assert bq.next().name == "translated!: long break 1"
|
||||||
|
assert bq.next().name == "translated!: break 1"
|
||||||
|
assert bq.next().name == "translated!: break 2"
|
||||||
|
assert bq.next().name == "translated!: break 3"
|
||||||
|
assert bq.next().name == "translated!: break 4"
|
||||||
|
assert bq.next().name == "translated!: long break 2"
|
||||||
|
assert bq.next().name == "translated!: break 1"
|
||||||
|
assert bq.next().name == "translated!: break 2"
|
||||||
|
assert bq.next().name == "translated!: break 3"
|
||||||
|
assert bq.next().name == "translated!: break 4"
|
||||||
|
assert bq.next().name == "translated!: long break 3"
|
||||||
|
assert bq.next().name == "translated!: break 1"
|
||||||
|
assert bq.next().name == "translated!: break 2"
|
||||||
|
assert bq.next().name == "translated!: break 3"
|
||||||
|
assert bq.next().name == "translated!: break 4"
|
||||||
|
assert bq.next().name == "translated!: long break 1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_full_next_break_random(self, monkeypatch):
|
||||||
|
random_seed = 5
|
||||||
|
bq = self.get_bq_full(monkeypatch, random_seed)
|
||||||
|
|
||||||
|
next = bq.get_break()
|
||||||
|
assert next.name == "translated!: break 1"
|
||||||
|
|
||||||
|
assert bq.next().name == "translated!: break 2"
|
||||||
|
assert bq.next().name == "translated!: break 4"
|
||||||
|
assert bq.next().name == "translated!: break 3"
|
||||||
|
assert bq.next().name == "translated!: long break 3"
|
||||||
|
assert bq.next().name == "translated!: break 2"
|
||||||
|
assert bq.next().name == "translated!: break 1"
|
||||||
|
assert bq.next().name == "translated!: break 4"
|
||||||
|
assert bq.next().name == "translated!: break 3"
|
||||||
|
assert bq.next().name == "translated!: long break 2"
|
||||||
|
assert bq.next().name == "translated!: break 2"
|
||||||
|
assert bq.next().name == "translated!: break 4"
|
||||||
|
assert bq.next().name == "translated!: break 1"
|
||||||
|
assert bq.next().name == "translated!: break 3"
|
||||||
|
assert bq.next().name == "translated!: long break 1"
|
152
setup.py
152
setup.py
|
@ -1,103 +1,73 @@
|
||||||
import os, sys, site
|
#!/usr/bin/python3
|
||||||
import subprocess
|
|
||||||
import setuptools
|
import os
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from setuptools import Command, setup
|
||||||
|
from setuptools.command.build import build as OriginalBuildCommand
|
||||||
|
|
||||||
|
class BuildCommand(OriginalBuildCommand):
|
||||||
|
sub_commands = [('build_mo', None), *OriginalBuildCommand.sub_commands]
|
||||||
|
|
||||||
|
|
||||||
requires = [
|
class BuildMoSubCommand(Command):
|
||||||
'babel',
|
description = 'Compile .po files into .mo files'
|
||||||
'psutil',
|
|
||||||
'croniter',
|
|
||||||
'PyGObject',
|
|
||||||
'packaging',
|
|
||||||
'python-xlib'
|
|
||||||
]
|
|
||||||
|
|
||||||
_ROOT = os.path.abspath(os.path.dirname(__file__))
|
files = None
|
||||||
|
|
||||||
with open(os.path.join(_ROOT, 'README.md')) as f:
|
def initialize_options(self):
|
||||||
long_description = f.read()
|
self.files = None
|
||||||
|
self.editable_mode = False
|
||||||
|
self.build_lib = None
|
||||||
|
|
||||||
|
def finalize_options(self):
|
||||||
|
self.set_undefined_options("build_py", ("build_lib", "build_lib"))
|
||||||
|
|
||||||
def __compile_po_files():
|
def run(self):
|
||||||
"""
|
files = self._get_files()
|
||||||
Compile the *.po trainslation files.
|
|
||||||
"""
|
for build_file, source_file in files.items():
|
||||||
localedir = 'safeeyes/config/locale'
|
if not self.editable_mode:
|
||||||
po_dirs = [localedir + '/' + l + '/LC_MESSAGES/'
|
# Parent directory required for msgfmt to work correctly
|
||||||
for l in next(os.walk(localedir))[1]]
|
Path(build_file).parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
self.spawn(['msgfmt', source_file, '-o', build_file])
|
||||||
|
|
||||||
|
def _get_files(self):
|
||||||
|
if self.files is not None:
|
||||||
|
return self.files
|
||||||
|
|
||||||
|
files = {}
|
||||||
|
|
||||||
|
localedir = Path('safeeyes/config/locale')
|
||||||
|
po_dirs = [l.joinpath('LC_MESSAGES') for l in localedir.iterdir() if l.is_dir()]
|
||||||
for po_dir in po_dirs:
|
for po_dir in po_dirs:
|
||||||
po_files = [f
|
po_files = [f
|
||||||
for f in next(os.walk(po_dir))[2]
|
for f in po_dir.iterdir()
|
||||||
if os.path.splitext(f)[1] == '.po']
|
if f.is_file() and f.suffix == '.po']
|
||||||
for po_file in po_files:
|
for po_file in po_files:
|
||||||
filename, _ = os.path.splitext(po_file)
|
mo_file = po_file.with_suffix(".mo")
|
||||||
mo_file = filename + '.mo'
|
|
||||||
msgfmt_cmd = 'msgfmt {} -o {}'.format(
|
source_file = po_file
|
||||||
po_dir + po_file, po_dir + mo_file)
|
build_file = mo_file
|
||||||
subprocess.call(msgfmt_cmd, shell=True)
|
|
||||||
|
if not self.editable_mode:
|
||||||
|
build_file = Path(self.build_lib).joinpath(build_file)
|
||||||
|
|
||||||
|
files[str(build_file)] = str(source_file)
|
||||||
|
|
||||||
|
self.files = files
|
||||||
|
return files
|
||||||
|
|
||||||
|
def get_output_mapping(self):
|
||||||
|
return self._get_files()
|
||||||
|
|
||||||
|
def get_outputs(self):
|
||||||
|
return self._get_files().keys()
|
||||||
|
|
||||||
|
def get_source_files(self):
|
||||||
|
return self._get_files().values()
|
||||||
|
|
||||||
|
|
||||||
def __data_files():
|
setup(
|
||||||
"""
|
cmdclass={'build': BuildCommand, 'build_mo': BuildMoSubCommand}
|
||||||
Collect the data files.
|
|
||||||
"""
|
|
||||||
root_dir = sys.prefix
|
|
||||||
return [(os.path.join(root_dir, "share/applications"), ["safeeyes/platform/io.github.slgobinath.SafeEyes.desktop"]),
|
|
||||||
(os.path.join(root_dir, "share/icons/hicolor/24x24/status"), ["safeeyes/platform/icons/hicolor/24x24/status/io.github.slgobinath.SafeEyes-disabled.png", "safeeyes/platform/icons/hicolor/24x24/status/io.github.slgobinath.SafeEyes-enabled.png", "safeeyes/platform/icons/hicolor/24x24/status/io.github.slgobinath.SafeEyes-timer.png"]),
|
|
||||||
(os.path.join(root_dir, "share/icons/hicolor/24x24/apps"), ["safeeyes/platform/icons/hicolor/24x24/apps/io.github.slgobinath.SafeEyes.png"]),
|
|
||||||
(os.path.join(root_dir, "share/icons/hicolor/16x16/status"), ["safeeyes/platform/icons/hicolor/16x16/status/io.github.slgobinath.SafeEyes-disabled.png", "safeeyes/platform/icons/hicolor/16x16/status/io.github.slgobinath.SafeEyes-enabled.png", "safeeyes/platform/icons/hicolor/16x16/status/io.github.slgobinath.SafeEyes-timer.png"]),
|
|
||||||
(os.path.join(root_dir, "share/icons/hicolor/16x16/apps"), ["safeeyes/platform/icons/hicolor/16x16/apps/io.github.slgobinath.SafeEyes.png"]),
|
|
||||||
(os.path.join(root_dir, "share/icons/hicolor/32x32/status"), ["safeeyes/platform/icons/hicolor/32x32/status/io.github.slgobinath.SafeEyes-disabled.png", "safeeyes/platform/icons/hicolor/32x32/status/io.github.slgobinath.SafeEyes-enabled.png"]),
|
|
||||||
(os.path.join(root_dir, "share/icons/hicolor/32x32/apps"), ["safeeyes/platform/icons/hicolor/32x32/apps/io.github.slgobinath.SafeEyes.png"]),
|
|
||||||
(os.path.join(root_dir, "share/icons/hicolor/64x64/apps"), ["safeeyes/platform/icons/hicolor/64x64/apps/io.github.slgobinath.SafeEyes.png"]),
|
|
||||||
(os.path.join(root_dir, "share/icons/hicolor/128x128/apps"), ["safeeyes/platform/icons/hicolor/128x128/apps/io.github.slgobinath.SafeEyes.png"]),
|
|
||||||
(os.path.join(root_dir, "share/icons/hicolor/48x48/status"), ["safeeyes/platform/icons/hicolor/48x48/status/io.github.slgobinath.SafeEyes-disabled.png", "safeeyes/platform/icons/hicolor/48x48/status/io.github.slgobinath.SafeEyes-enabled.png"]),
|
|
||||||
(os.path.join(root_dir, "share/icons/hicolor/48x48/apps"), ["safeeyes/platform/icons/hicolor/48x48/apps/io.github.slgobinath.SafeEyes.png"]),]
|
|
||||||
|
|
||||||
|
|
||||||
def __package_files(directory):
|
|
||||||
"""
|
|
||||||
Collect the package files.
|
|
||||||
"""
|
|
||||||
paths = []
|
|
||||||
for (path, _, filenames) in os.walk(directory):
|
|
||||||
for filename in filenames:
|
|
||||||
paths.append(os.path.join('..', path, filename))
|
|
||||||
return paths
|
|
||||||
|
|
||||||
|
|
||||||
def __package_data():
|
|
||||||
"""
|
|
||||||
Return a list of package data.
|
|
||||||
"""
|
|
||||||
__compile_po_files()
|
|
||||||
data = ['glade/*.glade', 'resource/*']
|
|
||||||
data.extend(__package_files('safeeyes/config'))
|
|
||||||
data.extend(__package_files('safeeyes/plugins'))
|
|
||||||
data.extend(__package_files('safeeyes/platform'))
|
|
||||||
return data
|
|
||||||
|
|
||||||
setuptools.setup(
|
|
||||||
name="safeeyes",
|
|
||||||
version="2.2.2",
|
|
||||||
description="Protect your eyes from eye strain using this continuous breaks reminder.",
|
|
||||||
long_description=long_description,
|
|
||||||
long_description_content_type="text/markdown",
|
|
||||||
author="Gobinath Loganathan",
|
|
||||||
author_email="slgobinath@gmail.com",
|
|
||||||
url="https://github.com/slgobinath/SafeEyes",
|
|
||||||
download_url="https://github.com/slgobinath/SafeEyes/archive/v2.2.2.tar.gz",
|
|
||||||
packages=setuptools.find_packages(),
|
|
||||||
package_data={'safeeyes': __package_data()},
|
|
||||||
data_files=__data_files(),
|
|
||||||
install_requires=requires,
|
|
||||||
entry_points={'console_scripts': ['safeeyes = safeeyes.__main__:main']},
|
|
||||||
keywords='linux utility health eye-strain safe-eyes',
|
|
||||||
classifiers=[
|
|
||||||
"Operating System :: POSIX :: Linux",
|
|
||||||
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
|
||||||
"Development Status :: 5 - Production/Stable",
|
|
||||||
"Environment :: X11 Applications :: GTK",
|
|
||||||
"Intended Audience :: End Users/Desktop",
|
|
||||||
"Topic :: Utilities"] + [('Programming Language :: Python :: %s' % x) for x in '3 3.5 3.6 3.7 3.8 3.9'.split()]
|
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue