Compare commits

...

23 Commits

Author SHA1 Message Date
deltragon 3698176f00 Merge branch 'master' into ci-flake8 2024-09-28 18:05:29 +02:00
Hosted Weblate 47be152397
Merge branch 'origin/master' into Weblate. 2024-09-27 14:16:34 +02:00
Andrés Martínez 0fa7b35c26
Translated using Weblate (Catalan)
Currently translated at 71.1% (96 of 135 strings)

Translation: Safe Eyes/Translations
Translate-URL: https://hosted.weblate.org/projects/safe-eyes/translations/ca/
2024-09-27 14:16:31 +02:00
Archisman Panigrahi 628abe298a
Merge pull request #646 from deltragon/xayatanalabel
trayicon: set XAyatanaLabel attribute for "Show next break time in tray icon"
2024-09-23 13:50:01 -04:00
Archisman Panigrahi 641eedafe7
Merge pull request #638 from deltragon/trayicon-retry
PluginManager: retry loading trayicon plugin when service is missing
2024-09-21 23:02:27 -04:00
Archisman Panigrahi 1a70cb6384
Merge pull request #650 from aceArt-GmbH/patch-1
Update plugin.py to capture time_in_minutes
2024-09-21 22:58:00 -04:00
Erik Michelson 7986e09f05
Translated using Weblate (German)
Currently translated at 100.0% (135 of 135 strings)

Translation: Safe Eyes/Translations
Translate-URL: https://hosted.weblate.org/projects/safe-eyes/translations/de/
2024-09-21 17:40:54 +02:00
Danial Behzadi 6186c4d4d4
Translated using Weblate (Persian)
Currently translated at 100.0% (135 of 135 strings)

Translation: Safe Eyes/Translations
Translate-URL: https://hosted.weblate.org/projects/safe-eyes/translations/fa/
2024-09-20 02:40:53 +02:00
aceArt-GmbH 033c56c837
Update plugin.py to capture time_in_minutes 2024-09-18 12:32:20 +02:00
Barman Anonymous Please 982c6c8181
Translated using Weblate (Arabic)
Currently translated at 95.5% (129 of 135 strings)

Translation: Safe Eyes/Translations
Translate-URL: https://hosted.weblate.org/projects/safe-eyes/translations/ar/
2024-09-17 03:09:14 +02:00
deltragon f25f554585 trayicon: set XAyatanaLabel attribute for "Show next break time in tray icon"
Fixes https://github.com/slgobinath/SafeEyes/issues/643

this a non-standard attribute that is not supported by many desktops, e.g. KDE and snixembed
however, this also did not work with libappindicator on KDE before
2024-09-12 10:56:09 +02:00
AircGroup 0b80f503c9
Translated using Weblate (Russian)
Currently translated at 100.0% (135 of 135 strings)

Translation: Safe Eyes/Translations
Translate-URL: https://hosted.weblate.org/projects/safe-eyes/translations/ru/
2024-09-07 00:09:11 +02:00
Hosted Weblate e00fc973da
Merge branch 'origin/master' into Weblate. 2024-09-02 09:09:18 +02:00
Oğuz Ersen b45e59cada
Translated using Weblate (Turkish)
Currently translated at 100.0% (135 of 135 strings)

Translation: Safe Eyes/Translations
Translate-URL: https://hosted.weblate.org/projects/safe-eyes/translations/tr/
2024-09-02 07:09:16 +00:00
gallegonovato f39343790e
Translated using Weblate (Spanish)
Currently translated at 100.0% (135 of 135 strings)

Translation: Safe Eyes/Translations
Translate-URL: https://hosted.weblate.org/projects/safe-eyes/translations/es/
2024-09-02 07:09:15 +00:00
Archisman Panigrahi 411c538506
add flatpak warning 2024-08-31 10:22:58 -04:00
Archisman Panigrahi d93aabf502
Update bug_report.md 2024-08-31 10:21:25 -04:00
vikdevelop 24db938f11
Translated using Weblate (Czech)
Currently translated at 100.0% (135 of 135 strings)

Translation: Safe Eyes/Translations
Translate-URL: https://hosted.weblate.org/projects/safe-eyes/translations/cs/
2024-08-27 21:09:24 +02:00
albanobattistella c934d30d8f
Translated using Weblate (Italian)
Currently translated at 100.0% (135 of 135 strings)

Translation: Safe Eyes/Translations
Translate-URL: https://hosted.weblate.org/projects/safe-eyes/translations/it/
2024-08-27 21:09:24 +02:00
Hosted Weblate 959177e4de
Merge branch 'origin/master' into Weblate. 2024-08-24 19:09:14 +02:00
AO Localisation Lab d16147cd2e
Translated using Weblate (French)
Currently translated at 100.0% (134 of 134 strings)

Translation: Safe Eyes/Translations
Translate-URL: https://hosted.weblate.org/projects/safe-eyes/translations/fr/
2024-08-24 17:09:11 +00:00
deltragon 6edb34344f PluginManager: retry loading trayicon plugin when service is missing 2024-08-21 15:40:04 +02:00
deltragon f9dcb56677 PluginManager: refactor to better keep errored plugins around 2024-08-21 15:38:00 +02:00
17 changed files with 463 additions and 350 deletions

View File

@ -25,6 +25,8 @@ A clear and concise description of what you expected to happen.
- Desktop Env [e.g. Gnome, KDE]
- Version [e.g. 2.0.3]
**Flatpak issues**: If you experience any issue with flatpak, first please ensure that the bug is present in the [native package](https://github.com/slgobinath/SafeEyes?tab=readme-ov-file#installation-guide), and it is not a flatpak-only bug. Flatpak-only bugs should be reported at https://github.com/flathub/io.github.slgobinath.SafeEyes. (**Please erase this paragraph before creating the bug report**)
**Debug Log**
Run the Safe Eyes using `safeeyes --debug` command attach the ~/safeeyes.log` file.

View File

@ -102,7 +102,7 @@ sudo apk add safeeyes
```
### Flatpak
**Warning**: Many plugins and features don't work well in the flatpak. We recommend that you use one of the native packages listed above. Flatpak-only bugs should be reported at https://github.com/flathub/io.github.slgobinath.SafeEyes.
```bash
flatpak install flathub io.github.slgobinath.SafeEyes
```

View File

@ -6,8 +6,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2024-07-26 11:09+0000\n"
"Last-Translator: RDWN IT <raraja701@gmail.com>\n"
"PO-Revision-Date: 2024-09-17 01:09+0000\n"
"Last-Translator: Barman Anonymous Please <barman-object-knee@duck.com>\n"
"Language-Team: Arabic <https://hosted.weblate.org/projects/safe-eyes/"
"translations/ar/>\n"
"Language: ar\n"
@ -16,11 +16,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
"&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n"
"X-Generator: Weblate 5.7-dev\n"
"X-Generator: Weblate 5.8-dev\n"
# Short break
msgid "Gently close your eyes"
msgstr ""
msgstr "أغمض عينيك بلطف"
# Short break
msgid "Roll your eyes a few times to each side"
@ -40,7 +40,7 @@ msgstr "اغمز بعينيك"
# Short break
msgid "Focus on a point in the far distance"
msgstr "ركّز على نقطة في المدى البعيد"
msgstr "ركّز على نقطة بعيدة"
# Short break
msgid "Have some water"
@ -76,7 +76,7 @@ msgstr "إظهار لوحة اﻹعدادات"
# Commandline arg description
msgid "start safeeyes in debug mode"
msgstr "ابدأ عيون سليمة في وضع التنقيح"
msgstr "ابدأ safeeyes في وضع التنقيح (debug)"
# Commandline arg description
msgid "print the status of running safeeyes instance and exit"
@ -109,11 +109,11 @@ msgstr "الرخصة"
# About dialog
msgid "List of Contributors"
msgstr ""
msgstr "قائمة المساهمين"
# About dialog
msgid "Help us translate this app"
msgstr ""
msgstr "ساعدنا في ترجمة هذا التطبيق"
# Break screen
msgid "Skip"
@ -539,36 +539,36 @@ msgstr "أوقِف الوسائط"
# plugin/limitconsecutiveskipping
msgid "Limit Consecutive Skipping"
msgstr ""
msgstr "تقييد التخطي المتتالي"
# plugin/limitconsecutiveskipping
msgid "How many skips or postpones are allowed in a row"
msgstr ""
msgstr "عدد مرات التخطي أو التأجيل المسموح بها على التوالي"
# plugin/limitconsecutiveskipping
msgid "Limit how many breaks can be skipped or postponed in a row"
msgstr ""
msgstr "حدد عدد فترات الاستراحة التي يمكن تخطيها أو تأجيلها على التوالي"
# plugin/limitconsecutiveskipping
#, python-format
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
msgid "RSI Prevention"
msgstr ""
msgstr "الوقاية من الإصابات الناتجة عن الإجهاد المتكرر للعينين"
msgid ""
"Please install service providing tray icons for your desktop environment."
msgstr ""
msgstr "يرجى تثبيت خدمة توفر أيقونات في علبة النظام لبيئة سطح المكتب الخاصة بك."
#, python-format
msgid "Next long break at %s"
msgstr ""
msgstr "الاستراحة الطويلة القادمة في %s"
#, python-format
msgid "Next breaks at %(short)s/%(long)s"
msgstr ""
msgstr "الاستراحة القادمة في %(short)s/%(long)s"
#, python-format
msgid "The required plugin '%s' is missing dependencies!"

View File

@ -6,8 +6,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2022-08-08 21:25+0000\n"
"Last-Translator: calbasi <joan@calbasi.net>\n"
"PO-Revision-Date: 2024-09-27 12:16+0000\n"
"Last-Translator: Andrés Martínez <sitoxplex@gmail.com>\n"
"Language-Team: Catalan <https://hosted.weblate.org/projects/safe-eyes/"
"translations/ca/>\n"
"Language: ca\n"
@ -15,11 +15,11 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.14-dev\n"
"X-Generator: Weblate 5.8-dev\n"
# Short break
msgid "Gently close your eyes"
msgstr ""
msgstr "Tanqueu els ulls suaument"
# Short break
msgid "Roll your eyes a few times to each side"
@ -90,6 +90,8 @@ msgid ""
"Safe Eyes is running without an RPC server. Turn it on to use command-line "
"arguments."
msgstr ""
"Safe Eyes s'està executant sense un servidor RPC. Engegueu-lo per usar els "
"arguments de la línia de comandaments."
# About dialog
msgid "Close"
@ -111,11 +113,11 @@ msgstr "Llicència"
# About dialog
msgid "List of Contributors"
msgstr ""
msgstr "Llista de col·laboradors"
# About dialog
msgid "Help us translate this app"
msgstr ""
msgstr "Ajuda'ns a traduir aquesta aplicació"
# Break screen
msgid "Skip"
@ -139,7 +141,7 @@ msgstr "Temps per preparar-se per a una pausa (en segons)"
# Settings dialog
msgid "Keyboard shortcuts disabled period (in seconds)"
msgstr ""
msgstr "Període d'inhabilitació de les dreceres de teclat (en segons)"
# Settings dialog
msgid "Postponement duration (in minutes)"
@ -159,19 +161,21 @@ msgstr "Permet posposar les pauses"
# Settings dialog
msgid "Persist the internal state"
msgstr ""
msgstr "Persisteix l'estat intern"
# Settings dialog
msgid "Use RPC server to receive runtime commands"
msgstr ""
msgstr "Empra el servidor RPC per a rebre els comandaments del temps d'execució"
# Settings dialog
msgid "Without the RPC server, command-line commands may not work"
msgstr ""
msgstr "Sense el servidor RPC, és possible que els comandaments no funcionen"
# Settings dialog
msgid "Long break interval must be a multiple of short break interval"
msgstr ""
"Cal que l'interval de pauses llargues siga múltiple de l'interval de pauses "
"curtes"
# Settings dialog
msgid "Reset"
@ -196,7 +200,7 @@ msgstr "Pauses llargues"
# Settings dialog
msgid "Delete"
msgstr "Suprimeix"
msgstr "Esborra"
# Settings dialog
msgid "Are you sure you want to delete this break?"
@ -240,7 +244,7 @@ msgstr "Trieu"
# Settings dialog
msgid "Please select an image"
msgstr "Si us plau, trieu una imatge"
msgstr "Trieu una imatge"
# Settings dialog
msgid "Duration"
@ -273,26 +277,26 @@ msgstr "Preferències del complement"
# Settings dialog
#, python-format
msgid "Plugin does not support %s desktop environment"
msgstr ""
msgstr "El complement no admet l'entorn d'escriptori %s"
# Settings dialog
#, python-format
msgid "Please install the Python module '%s'"
msgstr ""
msgstr "Instal·leu el mòdul de Python '%s'"
# Settings dialog
#, python-format
msgid "Please install the command-line tool '%s'"
msgstr ""
msgstr "Instal·leu la ferramenta de la línia de comandament '%s'"
# Settings dialog
msgid "Invalid cron expression '%s'"
msgstr ""
msgstr "Expressió cron no vàlida '%s'"
# Settings dialog
#, python-format
msgid "Please add the resource %(resource)s to %(config_resource)s directory"
msgstr ""
msgstr "Afegiu el recurs %(resource)s al directori %(config_resource)s"
# Settings dialog
msgid "New Break"
@ -316,15 +320,15 @@ msgstr "Alerta audible"
# plugin/audiblealert
msgid "Play audible alert before and after breaks"
msgstr ""
msgstr "Reprodueix una alerta audible abans i després de les pauses"
# plugin/audiblealert
msgid "Play audible alert before breaks"
msgstr ""
msgstr "Reprodueix una alerta audible abans de les pauses"
# plugin/audiblealert
msgid "Play audible alert after breaks"
msgstr ""
msgstr "Reprodueix una alerta audible després de les pauses"
# plugin/donotdisturb
msgid "Do Not Disturb"
@ -332,23 +336,23 @@ msgstr "No molesteu"
# plugin/donotdisturb
msgid "Skip break if the active window is in fullscreen mode"
msgstr ""
msgstr "Omet la pausa si la finestra activa està en mode pantalla completa"
# plugin/donotdisturb
msgid "Do not interrupt these windows anytime"
msgstr ""
msgstr "No interrompis aquestes finestres mai"
# plugin/donotdisturb
msgid "Interrupt these windows regardless of their state"
msgstr ""
msgstr "Interromp aquestes finestres independentment del seu estat"
# plugin/donotdisturb
msgid "Switch the interruptible windows to normal mode"
msgstr ""
msgstr "Canviar les finestres interruptibles al mode normal"
# plugin/donotdisturb
msgid "Do not disturb while on battery"
msgstr ""
msgstr "No molestar quan s'utilitza la bateria"
# plugin/healthstats
msgid "Health Statistics"
@ -356,24 +360,24 @@ msgstr "Estadístiques de salut"
# plugin/healthstats
msgid "Show statistics based on how you use Safe Eyes"
msgstr ""
msgstr "Mostra estadístiques de com useu Safe Eyes"
# plugin/healthstats
msgid "Statistics reset interval (cron expression)"
msgstr ""
msgstr "Interval de reinici de les estadístiques (expressió cron)"
# plugin/notification
msgid "Notification"
msgstr ""
msgstr "Notificació"
# plugin/notification
msgid "Show a system notification before breaks"
msgstr ""
msgstr "Mostra una notificació del sistema abans de les pauses"
# plugin/notification
#, python-format
msgid "Ready for a short break in %s seconds"
msgstr ""
msgstr "Preparat per a una pausa breu en %s segons"
# plugin/notification
#, python-format

View File

@ -6,20 +6,20 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2023-10-31 16:04+0000\n"
"Last-Translator: Kryštof Jelínek <krystof186@gmail.com>\n"
"PO-Revision-Date: 2024-08-27 19:09+0000\n"
"Last-Translator: vikdevelop <super-vik1@protonmail.com>\n"
"Language-Team: Czech <https://hosted.weblate.org/projects/safe-eyes/"
"translations/cs/>\n"
"Language: cs\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
"X-Generator: Weblate 5.2-dev\n"
"Plural-Forms: nplurals=3; plural=((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2);\n"
"X-Generator: Weblate 5.7.1-dev\n"
# Short break
msgid "Gently close your eyes"
msgstr ""
msgstr "Jemně zavřete své oči"
# Short break
msgid "Roll your eyes a few times to each side"
@ -112,11 +112,11 @@ msgstr "Licence"
# About dialog
msgid "List of Contributors"
msgstr ""
msgstr "Seznam přispěvatelů"
# About dialog
msgid "Help us translate this app"
msgstr ""
msgstr "Pomozte nám přeložit tuto aplikaci"
# Break screen
msgid "Skip"
@ -503,15 +503,15 @@ msgstr "Udělat si přestávku"
#: plugins/trayicon
msgid "Any break"
msgstr ""
msgstr "Jakákoliv přestávka"
#: plugins/trayicon
msgid "Short break"
msgstr ""
msgstr "Krátká přestávka"
#: plugins/trayicon
msgid "Long break"
msgstr ""
msgstr "Dlouhá přestávka"
#: plugins/trayicon
msgid "Until restart"
@ -535,57 +535,61 @@ msgstr "Pozastavit média"
# plugin/limitconsecutiveskipping
msgid "Limit Consecutive Skipping"
msgstr ""
msgstr "Omezení po sobě jdoucích přeskočení"
# plugin/limitconsecutiveskipping
msgid "How many skips or postpones are allowed in a row"
msgstr ""
msgstr "Kolik přeskočení nebo odložení je povoleno v řadě za sebou"
# plugin/limitconsecutiveskipping
msgid "Limit how many breaks can be skipped or postponed in a row"
msgstr ""
msgstr "Omezit počet přestávek, které lze vynechat nebo odložit za sebou"
# plugin/limitconsecutiveskipping
#, python-format
msgid "Skipped or postponed %(num)d/%(allowed)d breaks in a row"
msgstr ""
msgstr "Vynechané nebo odložené %(num)d/%(allowed)d přestávky za sebou"
# safeeyes/platform/io.github.slgobinath.SafeEyes.desktop
msgid "RSI Prevention"
msgstr ""
msgstr "Prevence poranění z opakovaného namáhání (RSI)"
msgid ""
"Please install service providing tray icons for your desktop environment."
msgstr ""
"Nainstalujte prosím službu poskytující ikony na panelu pro vaše desktopové "
"prostředí."
#, python-format
msgid "Next long break at %s"
msgstr ""
msgstr "Další dlouhá přestávka za %s"
#, python-format
msgid "Next breaks at %(short)s/%(long)s"
msgstr ""
msgstr "Další přestávky za %(short)s/%(long)s"
#, python-format
msgid "The required plugin '%s' is missing dependencies!"
msgstr ""
msgstr "Požadovanému zásuvnému modulu '%s' chybí závislosti!"
msgid ""
"Please install the dependencies or disable the plugin. To hide this message, "
"you can also deactivate the plugin in the settings."
msgstr ""
"Nainstalujte prosím požadované závislosti nebo zakažte tento zásuvný modul. "
"Pro skrytí této zprávy, můžete také deaktivovat zásuvný modul v nastavení."
msgid "Click here for more information"
msgstr ""
msgstr "Klikněte zde pro více informací"
msgid "Disable plugin temporarily"
msgstr ""
msgstr "Zakázat zásuvný modul dočasně"
msgid "Disable permanently"
msgstr ""
msgstr "Zakázat permanentně"
msgid "License:"
msgstr ""
msgstr "Licence:"
# Short break
#~ msgid "Tightly close your eyes"

View File

@ -6,8 +6,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2024-08-10 19:39+0000\n"
"Last-Translator: Archisman Panigrahi <apandada1@gmail.com>\n"
"PO-Revision-Date: 2024-09-21 15:40+0000\n"
"Last-Translator: Erik Michelson <opensource@erik.michelson.eu>\n"
"Language-Team: German <https://hosted.weblate.org/projects/safe-eyes/"
"translations/de/>\n"
"Language: de\n"
@ -15,7 +15,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.7-dev\n"
"X-Generator: Weblate 5.8-dev\n"
# Short break
msgid "Gently close your eyes"
@ -117,7 +117,7 @@ msgstr "Liste der Mitwirkenden"
# About dialog
msgid "Help us translate this app"
msgstr ""
msgstr "Hilf mit die Anwendung zu übersetzen"
# Break screen
msgid "Skip"

View File

@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2024-08-16 15:09+0000\n"
"PO-Revision-Date: 2024-09-02 07:09+0000\n"
"Last-Translator: gallegonovato <fran-carro@hotmail.es>\n"
"Language-Team: Spanish <https://hosted.weblate.org/projects/safe-eyes/"
"translations/es/>\n"
@ -15,7 +15,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.7\n"
"X-Generator: Weblate 5.8-dev\n"
# Short break
msgid "Gently close your eyes"
@ -116,7 +116,7 @@ msgstr "Colaboradores"
# About dialog
msgid "Help us translate this app"
msgstr ""
msgstr "Ayúdanos a traducir esta aplicación"
# Break screen
msgid "Skip"

View File

@ -6,8 +6,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2024-07-28 15:09+0000\n"
"Last-Translator: Parsa Nobahari <parsa.nobahari@gmail.com>\n"
"PO-Revision-Date: 2024-09-20 00:40+0000\n"
"Last-Translator: Danial Behzadi <dani.behzi@ubuntu.com>\n"
"Language-Team: Persian <https://hosted.weblate.org/projects/safe-eyes/"
"translations/fa/>\n"
"Language: fa\n"
@ -15,11 +15,11 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 5.7-dev\n"
"X-Generator: Weblate 5.8-dev\n"
# Short break
msgid "Gently close your eyes"
msgstr ""
msgstr "چشمانتان را آرام ببندید"
# Short break
msgid "Roll your eyes a few times to each side"
@ -116,7 +116,7 @@ msgstr "لیست مشارکت‌کنندگان"
# About dialog
msgid "Help us translate this app"
msgstr ""
msgstr "کمک به ترجمهٔ این کاره"
# Break screen
msgid "Skip"
@ -581,7 +581,7 @@ msgid "Disable permanently"
msgstr "غیرفعال کردن دائمی"
msgid "License:"
msgstr ""
msgstr "پروانه:"
# Short break
#~ msgid "Tightly close your eyes"

View File

@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2022-01-11 15:55+0000\n"
"PO-Revision-Date: 2024-08-24 17:09+0000\n"
"Last-Translator: AO Localisation Lab <ao@localizationlab.org>\n"
"Language-Team: French <https://hosted.weblate.org/projects/safe-eyes/"
"translations/fr/>\n"
@ -15,11 +15,11 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 4.10.1\n"
"X-Generator: Weblate 5.7.1-dev\n"
# Short break
msgid "Gently close your eyes"
msgstr ""
msgstr "Fermez doucement les yeux"
# Short break
msgid "Roll your eyes a few times to each side"
@ -113,7 +113,7 @@ msgstr "Licence"
# About dialog
msgid "List of Contributors"
msgstr ""
msgstr "Liste des contributeurs"
# About dialog
msgid "Help us translate this app"
@ -510,15 +510,15 @@ msgstr "Prendre une pause maintenant"
#: plugins/trayicon
msgid "Any break"
msgstr ""
msgstr "Nimporte quelle pause"
#: plugins/trayicon
msgid "Short break"
msgstr ""
msgstr "Pause courte"
#: plugins/trayicon
msgid "Long break"
msgstr ""
msgstr "Pause longue"
#: plugins/trayicon
msgid "Until restart"
@ -542,57 +542,61 @@ msgstr "Mettre le contenu multimédia en pause"
# plugin/limitconsecutiveskipping
msgid "Limit Consecutive Skipping"
msgstr ""
msgstr "Limiter les sauts de pause consécutifs"
# plugin/limitconsecutiveskipping
msgid "How many skips or postpones are allowed in a row"
msgstr ""
msgstr "Nombre de sauts ou de reports autorisés daffilée"
# plugin/limitconsecutiveskipping
msgid "Limit how many breaks can be skipped or postponed in a row"
msgstr ""
msgstr "Limiter le nombre de pauses sautées ou reportées daffilée"
# plugin/limitconsecutiveskipping
#, python-format
msgid "Skipped or postponed %(num)d/%(allowed)d breaks in a row"
msgstr ""
msgstr "%(num)d/%(allowed)d pauses sautées ou reportées daffilée"
# safeeyes/platform/io.github.slgobinath.SafeEyes.desktop
msgid "RSI Prevention"
msgstr ""
msgstr "Prévention des lésions attribuables au travail répétitif"
msgid ""
"Please install service providing tray icons for your desktop environment."
msgstr ""
"Installez le service qui fournit les icônes de la barre détat pour votre "
"environnement de bureau."
#, python-format
msgid "Next long break at %s"
msgstr ""
msgstr "Prochaine pause longue à %s"
#, python-format
msgid "Next breaks at %(short)s/%(long)s"
msgstr ""
msgstr "Prochaines pauses à %(short)s/%(long)s"
#, python-format
msgid "The required plugin '%s' is missing dependencies!"
msgstr ""
msgstr "Il manque des dépendances pour le greffon « %s » nécessaire"
msgid ""
"Please install the dependencies or disable the plugin. To hide this message, "
"you can also deactivate the plugin in the settings."
msgstr ""
"Installez les dépendances ou désactivez le greffon. Pour ne plus afficher ce "
"message, vous pouvez aussi désactiver le greffon dans les paramètres."
msgid "Click here for more information"
msgstr ""
msgstr "Cliquer ici pour plus de précisions"
msgid "Disable plugin temporarily"
msgstr ""
msgstr "Désactivation temporaire du greffon"
msgid "Disable permanently"
msgstr ""
msgstr "Désactivation permanente"
msgid "License:"
msgstr ""
msgstr "Licence:"
# Short break
#~ msgid "Tightly close your eyes"

View File

@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2024-07-02 12:09+0000\n"
"PO-Revision-Date: 2024-08-27 19:09+0000\n"
"Last-Translator: albanobattistella <albano_battistella@hotmail.com>\n"
"Language-Team: Italian <https://hosted.weblate.org/projects/safe-eyes/"
"translations/it/>\n"
@ -15,11 +15,11 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.7-dev\n"
"X-Generator: Weblate 5.7.1-dev\n"
# Short break
msgid "Gently close your eyes"
msgstr ""
msgstr "Chiudi dolcemente gli occhi"
# Short break
msgid "Roll your eyes a few times to each side"
@ -116,7 +116,7 @@ msgstr "Elenco dei contributori"
# About dialog
msgid "Help us translate this app"
msgstr ""
msgstr "Aiutaci a tradurre questa app"
# Break screen
msgid "Skip"
@ -591,7 +591,7 @@ msgid "Disable permanently"
msgstr "Disattivare in modo permanente"
msgid "License:"
msgstr ""
msgstr "Licenza:"
# Short break
#~ msgid "Tightly close your eyes"

View File

@ -6,21 +6,21 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2023-11-26 00:02+0000\n"
"Last-Translator: Almaz Mannanov <AlmazWorks@gmail.com>\n"
"PO-Revision-Date: 2024-09-06 22:09+0000\n"
"Last-Translator: AircGroup <imartime@yandex.ru>\n"
"Language-Team: Russian <https://hosted.weblate.org/projects/safe-eyes/"
"translations/ru/>\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 5.2.1-rc\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: Weblate 5.8-dev\n"
# Short break
msgid "Gently close your eyes"
msgstr ""
msgstr "Аккуратно закройте глаза"
# Short break
msgid "Roll your eyes a few times to each side"
@ -113,11 +113,11 @@ msgstr "Лицензия"
# About dialog
msgid "List of Contributors"
msgstr ""
msgstr "Список участников проекта"
# About dialog
msgid "Help us translate this app"
msgstr ""
msgstr "Помоги нам перевести приложение"
# Break screen
msgid "Skip"
@ -504,15 +504,15 @@ msgstr "Отдохнуть сейчас"
#: plugins/trayicon
msgid "Any break"
msgstr ""
msgstr "Сделайте перерыв на что угодно"
#: plugins/trayicon
msgid "Short break"
msgstr ""
msgstr "Небольшой перерыв"
#: plugins/trayicon
msgid "Long break"
msgstr ""
msgstr "Долгий перерыв"
#: plugins/trayicon
msgid "Until restart"
@ -536,57 +536,61 @@ msgstr "Приостановить воспроизведение"
# plugin/limitconsecutiveskipping
msgid "Limit Consecutive Skipping"
msgstr ""
msgstr "Ограничение пропусков подряд"
# plugin/limitconsecutiveskipping
msgid "How many skips or postpones are allowed in a row"
msgstr ""
msgstr "Сколько пропусков или переносов разрешено подряд"
# plugin/limitconsecutiveskipping
msgid "Limit how many breaks can be skipped or postponed in a row"
msgstr ""
msgstr "Какое количество перерывов можно пропустить или отложить подряд"
# plugin/limitconsecutiveskipping
#, python-format
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
msgid "RSI Prevention"
msgstr ""
msgstr "Профилактика RSI"
msgid ""
"Please install service providing tray icons for your desktop environment."
msgstr ""
"Пожалуйста, установите ПО предоставляющий иконки в трее для вашего рабочего "
"стола."
#, python-format
msgid "Next long break at %s"
msgstr ""
msgstr "Следующий длительный перерыв в %s"
#, python-format
msgid "Next breaks at %(short)s/%(long)s"
msgstr ""
msgstr "Следующие перерывы %(short)s/%(long)s"
#, python-format
msgid "The required plugin '%s' is missing dependencies!"
msgstr ""
msgstr "В требуемом плагине '%s' отсутствуют зависимости!"
msgid ""
"Please install the dependencies or disable the plugin. To hide this message, "
"you can also deactivate the plugin in the settings."
msgstr ""
"Пожалуйста, установите зависимости или отключите плагин. Чтобы скрыть это "
"сообщение, вы также можете отключить плагин в настройках."
msgid "Click here for more information"
msgstr ""
msgstr "Нажмите здесь для получения дополнительной информации"
msgid "Disable plugin temporarily"
msgstr ""
msgstr "Временно отключите плагин"
msgid "Disable permanently"
msgstr ""
msgstr "Отключить навсегда"
msgid "License:"
msgstr ""
msgstr "Лицензия:"
# Short break
#~ msgid "Tightly close your eyes"

View File

@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2024-06-28 17:09+0000\n"
"PO-Revision-Date: 2024-09-02 07:09+0000\n"
"Last-Translator: Oğuz Ersen <oguz@ersen.moe>\n"
"Language-Team: Turkish <https://hosted.weblate.org/projects/safe-eyes/"
"translations/tr/>\n"
@ -15,11 +15,11 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.7-dev\n"
"X-Generator: Weblate 5.8-dev\n"
# Short break
msgid "Gently close your eyes"
msgstr ""
msgstr "Yavaşça gözlerinizi kapatın"
# Short break
msgid "Roll your eyes a few times to each side"
@ -113,11 +113,11 @@ msgstr "Lisans"
# About dialog
msgid "List of Contributors"
msgstr ""
msgstr "Katkıda Bulunanların Listesi"
# About dialog
msgid "Help us translate this app"
msgstr ""
msgstr "Bu uygulamayı çevirmemize yardım edin"
# Break screen
msgid "Skip"
@ -533,57 +533,60 @@ msgstr "Ortamı duraklat"
# plugin/limitconsecutiveskipping
msgid "Limit Consecutive Skipping"
msgstr ""
msgstr "Ardışık Geçmeyi Sınırla"
# plugin/limitconsecutiveskipping
msgid "How many skips or postpones are allowed in a row"
msgstr ""
msgstr "Arka arkaya kaç geçmeye veya ertelemeye izin verilir"
# plugin/limitconsecutiveskipping
msgid "Limit how many breaks can be skipped or postponed in a row"
msgstr ""
"Arka arkaya kaç molanın geçilebileceğini veya ertelenebileceğini sınırlayın"
# plugin/limitconsecutiveskipping
#, python-format
msgid "Skipped or postponed %(num)d/%(allowed)d breaks in a row"
msgstr ""
msgstr "Arka arkaya %(num)d/%(allowed)d mola geçildi veya ertelendi"
# safeeyes/platform/io.github.slgobinath.SafeEyes.desktop
msgid "RSI Prevention"
msgstr ""
msgstr "Tekrarlayan zorlanma yaralanmalarının önlenmesi"
msgid ""
"Please install service providing tray icons for your desktop environment."
msgstr ""
msgstr "Lütfen masaüstü ortamınız için tepsi simgeleri sağlayan hizmeti kurun."
#, python-format
msgid "Next long break at %s"
msgstr ""
msgstr "Sonraki uzun mola: %s"
#, python-format
msgid "Next breaks at %(short)s/%(long)s"
msgstr ""
msgstr "Sonraki mola: %(short)s/%(long)s"
#, python-format
msgid "The required plugin '%s' is missing dependencies!"
msgstr ""
msgstr "Gerekli '%s' eklentisinin bağımlılıkları eksik!"
msgid ""
"Please install the dependencies or disable the plugin. To hide this message, "
"you can also deactivate the plugin in the settings."
msgstr ""
"Lütfen bağımlılıkları kurun veya eklentiyi devre dışı bırakın. Bu mesajı "
"gizlemek için eklentiyi ayarlardan da devre dışı bırakabilirsiniz."
msgid "Click here for more information"
msgstr ""
msgstr "Daha fazla bilgi için buraya tıklayın"
msgid "Disable plugin temporarily"
msgstr ""
msgstr "Eklentiyi geçici olarak devre dışı bırak"
msgid "Disable permanently"
msgstr ""
msgstr "Kalıcı olarak devre dışı bırak"
msgid "License:"
msgstr ""
msgstr "Lisans:"
# Short break
#~ msgid "Tightly close your eyes"

View File

@ -407,6 +407,7 @@ class TrayAction:
class PluginDependency:
message: str
link: str | None = None
retryable: bool = False
class RequiredPluginException(Exception):

View File

@ -58,7 +58,7 @@ import os
import sys
from safeeyes import utility
from safeeyes.model import RequiredPluginException
from safeeyes.model import PluginDependency, RequiredPluginException
sys.path.append(os.path.abspath(utility.SYSTEM_PLUGINS_DIR))
sys.path.append(os.path.abspath(utility.USER_PLUGINS_DIR))
@ -67,24 +67,11 @@ HORIZONTAL_LINE_LENGTH = 64
class PluginManager:
"""Imports the Safe Eyes plugins and calls the methods defined in those
plugins.
"""
"""Imports the Safe Eyes plugins and calls the methods defined in those plugins."""
def __init__(self):
logging.info("Load all the plugins")
self.__plugins = {}
self.__plugins_on_init = []
self.__plugins_on_start = []
self.__plugins_on_stop = []
self.__plugins_on_exit = []
self.__plugins_on_pre_break = []
self.__plugins_on_start_break = []
self.__plugins_on_stop_break = []
self.__plugins_on_countdown = []
self.__plugins_update_next_break = []
self.__widget_plugins = []
self.__tray_actions_plugins = []
self.last_break = None
self.horizontal_line = "" * HORIZONTAL_LINE_LENGTH
@ -95,7 +82,8 @@ class PluginManager:
# Load the plugins
for plugin in config.get("plugins"):
try:
self.__load_plugin(plugin)
loaded_plugin = LoadedPlugin(plugin)
self.__plugins[loaded_plugin.id] = loaded_plugin
except RequiredPluginException as e:
raise e
except BaseException as e:
@ -109,62 +97,85 @@ class PluginManager:
logging.error("Error in loading the plugin %s: %s", plugin["id"], e)
continue
# Initialize the plugins
for plugin in self.__plugins_on_init:
plugin["module"].init(context, config, plugin["config"])
for plugin in self.__plugins.values():
plugin.init_plugin(context, config)
return True
def needs_retry(self):
return self.get_retryable_error() is not None
def get_retryable_error(self):
for plugin in self.__plugins.values():
if plugin.required_plugin and plugin.errored and plugin.enabled:
if (
isinstance(plugin.last_error, PluginDependency)
and plugin.last_error.retryable
):
return RequiredPluginException(
plugin.id, plugin.get_name(), plugin.last_error
)
return None
def retry_errored_plugins(self):
for plugin in self.__plugins.values():
if plugin.required_plugin and plugin.errored and plugin.enabled:
if (
isinstance(plugin.last_error, PluginDependency)
and plugin.last_error.retryable
):
plugin.reload_errored()
def start(self):
"""Execute the on_start() function of plugins."""
for plugin in self.__plugins_on_start:
plugin["module"].on_start()
for plugin in self.__plugins.values():
plugin.call_plugin_method("on_start")
return True
def stop(self):
"""Execute the on_stop() function of plugins."""
for plugin in self.__plugins_on_stop:
plugin["module"].on_stop()
for plugin in self.__plugins.values():
plugin.call_plugin_method("on_stop")
return True
def exit(self):
"""Execute the on_exit() function of plugins."""
for plugin in self.__plugins_on_exit:
plugin["module"].on_exit()
for plugin in self.__plugins.values():
plugin.call_plugin_method("on_exit")
return True
def pre_break(self, break_obj):
"""Execute the on_pre_break(break_obj) function of plugins."""
for plugin in self.__plugins_on_pre_break:
if break_obj.plugin_enabled(plugin["id"], plugin["enabled"]):
if plugin["module"].on_pre_break(break_obj):
return False
for plugin in self.__plugins.values():
if plugin.call_plugin_method_break_obj("on_pre_break", 1, break_obj):
return False
return True
def start_break(self, break_obj):
"""Execute the start_break(break_obj) function of plugins."""
self.last_break = break_obj
for plugin in self.__plugins_on_start_break:
if break_obj.plugin_enabled(plugin["id"], plugin["enabled"]):
if plugin["module"].on_start_break(break_obj):
return False
for plugin in self.__plugins.values():
if plugin.call_plugin_method_break_obj("on_start_break", 1, break_obj):
return False
return True
def stop_break(self):
"""Execute the stop_break() function of plugins."""
for plugin in self.__plugins_on_stop_break:
if self.last_break.plugin_enabled(plugin["id"], plugin["enabled"]):
plugin["module"].on_stop_break()
for plugin in self.__plugins.values():
plugin.call_plugin_method("on_stop_break")
def countdown(self, countdown, seconds):
"""Execute the on_countdown(countdown, seconds) function of plugins."""
for plugin in self.__plugins_on_countdown:
if self.last_break.plugin_enabled(plugin["id"], plugin["enabled"]):
plugin["module"].on_countdown(countdown, seconds)
for plugin in self.__plugins.values():
plugin.call_plugin_method("on_countdown", 2, countdown, seconds)
def update_next_break(self, break_obj, break_time):
"""Execute the update_next_break(break_time) function of plugins."""
for plugin in self.__plugins_update_next_break:
plugin["module"].update_next_break(break_obj, break_time)
for plugin in self.__plugins.values():
plugin.call_plugin_method_break_obj(
"update_next_break", 2, break_obj, break_time
)
return True
def get_break_screen_widgets(self, break_obj):
@ -174,184 +185,219 @@ class PluginManager:
get_widget_content functions of plugins.
"""
widget = ""
for plugin in self.__widget_plugins:
if break_obj.plugin_enabled(plugin["id"], plugin["enabled"]):
try:
title = plugin["module"].get_widget_title(break_obj).upper().strip()
if title == "":
continue
content = plugin["module"].get_widget_content(break_obj)
if content == "":
continue
widget += "<b>{}</b>\n{}\n{}\n\n\n".format(
title, self.horizontal_line, content
)
except BaseException:
for plugin in self.__plugins.values():
try:
title = plugin.call_plugin_method_break_obj(
"get_widget_title", 1, break_obj
)
if title is None or not isinstance(title, str) or title == "":
continue
content = plugin.call_plugin_method_break_obj(
"get_widget_content", 1, break_obj
)
if content is None or not isinstance(content, str) or content == "":
continue
title = title.upper().strip()
if title == "":
continue
widget += "<b>{}</b>\n{}\n{}\n\n\n".format(
title, self.horizontal_line, content
)
except BaseException:
continue
return widget.strip()
def get_break_screen_tray_actions(self, break_obj):
"""Return Tray Actions."""
actions = []
for plugin in self.__tray_actions_plugins:
if break_obj.plugin_enabled(plugin["id"], plugin["enabled"]):
action = plugin["module"].get_tray_action(break_obj)
if action:
actions.append(action)
for plugin in self.__plugins.values():
action = plugin.call_plugin_method_break_obj(
"get_tray_action", 1, break_obj
)
if action:
actions.append(action)
return actions
def __load_plugin(self, plugin):
"""Load the given plugin."""
plugin_enabled = plugin["enabled"]
if plugin["id"] in self.__plugins and not plugin_enabled:
# A disabled plugin but that was loaded earlier
plugin_obj = self.__plugins[plugin["id"]]
if plugin_obj["enabled"]:
# Previously enabled but now disabled
plugin_obj["enabled"] = False
utility.remove_if_exists(self.__plugins_on_start, plugin_obj)
utility.remove_if_exists(self.__plugins_on_stop, plugin_obj)
utility.remove_if_exists(self.__plugins_on_exit, plugin_obj)
utility.remove_if_exists(self.__plugins_update_next_break, plugin_obj)
# Call the plugin.disable method if available
if utility.has_method(plugin_obj["module"], "disable"):
plugin_obj["module"].disable()
logging.info("Successfully unloaded the plugin '%s'", plugin["id"])
if not plugin_obj["break_override_allowed"]:
# Remaining methods also should be removed
utility.remove_if_exists(self.__plugins_on_init, plugin_obj)
utility.remove_if_exists(self.__plugins_on_pre_break, plugin_obj)
utility.remove_if_exists(self.__plugins_on_start_break, plugin_obj)
utility.remove_if_exists(self.__plugins_on_stop_break, plugin_obj)
utility.remove_if_exists(self.__plugins_on_countdown, plugin_obj)
utility.remove_if_exists(self.__widget_plugins, plugin_obj)
utility.remove_if_exists(self.__tray_actions_plugins, plugin_obj)
del self.__plugins[plugin["id"]]
class LoadedPlugin:
# state of the plugin
enabled: bool = False
break_override_allowed: bool = False
errored: bool = False
required_plugin: bool = False
# misc data
# FIXME: rename to plugin_config to plugin_json? plugin_config and config are easy
# to confuse
config = None
plugin_config = None
plugin_dir = None
module = None
last_error = None
id = None
def __init__(self, plugin):
(plugin_config, plugin_dir) = self._load_config_json(plugin["id"])
self.id = plugin["id"]
self.plugin_config = plugin_config
self.plugin_dir = plugin_dir
self.enabled = plugin["enabled"]
self.break_override_allowed = plugin_config.get("break_override_allowed", False)
self.required_plugin = plugin_config.get("required_plugin", False)
self.config = dict(plugin.get("settings", {}))
self.config["path"] = os.path.join(plugin_dir, plugin["id"])
if self.enabled or self.break_override_allowed:
plugin_path = os.path.join(plugin_dir, self.id)
message = utility.check_plugin_dependencies(
plugin["id"], plugin_config, plugin.get("settings", {}), plugin_path
)
if message:
self.errored = True
self.last_error = message
if self.required_plugin and not (
isinstance(message, PluginDependency) and message.retryable
):
raise RequiredPluginException(
plugin["id"], plugin_config["meta"]["name"], message
)
return
self._import_plugin()
def reload_config(self, plugin):
if self.enabled and not plugin["enabled"]:
self.enabled = False
if not self.errored and utility.has_method(self.module, "disable"):
self.module.disable()
if not self.enabled and plugin["enabled"]:
self.enabled = True
# Update the config
self.config = dict(plugin.get("settings", {}))
self.config["path"] = os.path.join(self.plugin_dir, plugin["id"])
if self.enabled or self.break_override_allowed:
plugin_path = os.path.join(self.plugin_dir, self.id)
message = utility.check_plugin_dependencies(
self.id, self.plugin_config, self.config, plugin_path
)
if message:
self.errored = True
self.last_error = message
elif self.errored:
self.errored = False
self.last_error = None
if not self.errored and self.module is None:
# No longer errored, import the module now
self._import_plugin()
def reload_errored(self):
if not self.errored:
return
if self.enabled or self.break_override_allowed:
plugin_path = os.path.join(self.plugin_dir, self.id)
message = utility.check_plugin_dependencies(
self.id, self.plugin_config, self.config, plugin_path
)
if message:
self.errored = True
self.last_error = message
elif self.errored:
self.errored = False
self.last_error = None
if not self.errored and self.module is None:
# No longer errored, import the module now
self._import_plugin()
def get_name(self):
return self.plugin_config["meta"]["name"]
def _import_plugin(self):
if self.errored:
# do not try to import errored plugin
return
self.module = importlib.import_module((self.id + ".plugin"))
logging.info("Successfully loaded %s", str(self.module))
if utility.has_method(self.module, "enable"):
self.module.enable()
def _load_config_json(self, plugin_id):
# Look for plugin.py
if os.path.isfile(
os.path.join(utility.SYSTEM_PLUGINS_DIR, plugin["id"], "plugin.py")
os.path.join(utility.SYSTEM_PLUGINS_DIR, plugin_id, "plugin.py")
):
plugin_dir = utility.SYSTEM_PLUGINS_DIR
elif os.path.isfile(
os.path.join(utility.USER_PLUGINS_DIR, plugin["id"], "plugin.py")
os.path.join(utility.USER_PLUGINS_DIR, plugin_id, "plugin.py")
):
plugin_dir = utility.USER_PLUGINS_DIR
else:
logging.error("plugin.py not found for the plugin: %s", plugin["id"])
return
raise Exception("plugin.py not found for the plugin: %s", plugin_id)
# Look for config.json
plugin_path = os.path.join(plugin_dir, plugin["id"])
plugin_path = os.path.join(plugin_dir, plugin_id)
plugin_config_path = os.path.join(plugin_path, "config.json")
if not os.path.isfile(plugin_config_path):
logging.error("config.json not found for the plugin: %s", plugin["id"])
return
raise Exception("config.json not found for the plugin: %s", plugin_id)
plugin_config = utility.load_json(plugin_config_path)
if plugin_config is None:
raise Exception("config.json empty/invalid for the plugin: %s", plugin_id)
return (plugin_config, plugin_dir)
def init_plugin(self, context, safeeyes_config):
if self.errored:
return
if self.break_override_allowed or self.enabled:
if utility.has_method(self.module, "init", 3):
self.module.init(context, safeeyes_config, self.config)
if plugin_enabled or plugin_config.get("break_override_allowed", False):
if plugin["id"] in self.__plugins:
# The plugin is already enabled or partially loaded due to
# break_override_allowed
def call_plugin_method_break_obj(
self, method_name: str, num_args, break_obj, *args, **kwargs
):
if self.errored:
return None
# Validate the dependencies again
if utility.check_plugin_dependencies(
plugin["id"], plugin_config, plugin.get("settings", {}), plugin_path
):
plugin_obj["enabled"] = False
utility.remove_if_exists(self.__plugins_on_start, plugin_obj)
utility.remove_if_exists(self.__plugins_on_stop, plugin_obj)
utility.remove_if_exists(self.__plugins_on_exit, plugin_obj)
utility.remove_if_exists(
self.__plugins_update_next_break, plugin_obj
)
utility.remove_if_exists(self.__plugins_on_init, plugin_obj)
utility.remove_if_exists(self.__plugins_on_pre_break, plugin_obj)
utility.remove_if_exists(self.__plugins_on_start_break, plugin_obj)
utility.remove_if_exists(self.__plugins_on_stop_break, plugin_obj)
utility.remove_if_exists(self.__plugins_on_countdown, plugin_obj)
utility.remove_if_exists(self.__widget_plugins, plugin_obj)
utility.remove_if_exists(self.__tray_actions_plugins, plugin_obj)
del self.__plugins[plugin["id"]]
enabled = False
if self.break_override_allowed:
enabled = break_obj.plugin_enabled(self.id, self.enabled)
else:
enabled = self.enabled
# Use the existing plugin object
plugin_obj = self.__plugins[plugin["id"]]
if enabled:
return self._call_plugin_method_internal(
method_name, num_args, break_obj, *args, **kwargs
)
# Update the config
plugin_obj["config"] = dict(plugin.get("settings", {}))
plugin_obj["config"]["path"] = os.path.join(plugin_dir, plugin["id"])
return None
if plugin_obj["enabled"]:
# Already loaded completely
return
# Plugin was partially loaded due to break_override_allowed
if plugin_enabled:
# Load the rest of the methods
plugin_obj["enabled"] = True
module = plugin_obj["module"]
self.__init_plugin(module, plugin_obj)
else:
# This is the first time to load the plugin
# Check for dependencies
message = utility.check_plugin_dependencies(
plugin["id"], plugin_config, plugin.get("settings", {}), plugin_path
)
if message:
if plugin_config.get("required_plugin", False):
raise RequiredPluginException(
plugin["id"], plugin_config["meta"]["name"], message
)
return
def call_plugin_method(self, method_name: str, num_args=0, *args, **kwargs):
if self.errored:
return None
# Load the plugin module
module = importlib.import_module((plugin["id"] + ".plugin"))
logging.info("Successfully loaded %s", str(module))
plugin_obj = {
"id": plugin["id"],
"module": module,
"config": dict(plugin.get("settings", {})),
"enabled": plugin_enabled,
"break_override_allowed": plugin_config.get(
"break_override_allowed", False
),
}
# Inject the plugin directory into the config
plugin_obj["config"]["path"] = os.path.join(plugin_dir, plugin["id"])
self.__plugins[plugin["id"]] = plugin_obj
if utility.has_method(module, "enable"):
module.enable()
if plugin_enabled:
self.__init_plugin(module, plugin_obj)
if utility.has_method(module, "init", 3):
self.__plugins_on_init.append(plugin_obj)
if utility.has_method(module, "on_pre_break", 1):
self.__plugins_on_pre_break.append(plugin_obj)
if utility.has_method(module, "on_start_break", 1):
self.__plugins_on_start_break.append(plugin_obj)
if utility.has_method(module, "on_stop_break", 0):
self.__plugins_on_stop_break.append(plugin_obj)
if utility.has_method(module, "on_countdown", 2):
self.__plugins_on_countdown.append(plugin_obj)
if utility.has_method(
module, "get_widget_title", 1
) and utility.has_method(module, "get_widget_content", 1):
self.__widget_plugins.append(plugin_obj)
if utility.has_method(module, "get_tray_action", 1):
self.__tray_actions_plugins.append(plugin_obj)
if self.enabled:
return self._call_plugin_method_internal(
method_name, num_args, *args, **kwargs
)
def __init_plugin(self, module, plugin_obj):
"""Collect mandatory methods from the plugin and add them to the life
cycle methods list.
"""
if utility.has_method(module, "on_start"):
self.__plugins_on_start.append(plugin_obj)
if utility.has_method(module, "on_stop"):
self.__plugins_on_stop.append(plugin_obj)
if utility.has_method(module, "on_exit"):
self.__plugins_on_exit.append(plugin_obj)
if utility.has_method(module, "update_next_break", 2):
self.__plugins_update_next_break.append(plugin_obj)
return None
def _call_plugin_method_internal(
self, method_name: str, num_args=0, *args, **kwargs
):
# FIXME: cache if method exists
if utility.has_method(self.module, method_name, num_args):
return getattr(self.module, method_name)(*args, **kwargs)
return None

View File

@ -44,7 +44,8 @@ def validate(plugin_config, plugin_settings):
"Please install service providing tray icons for your desktop"
" environment."
),
link="https://github.com/slgobinath/SafeEyes/wiki/How-to-install-backend-for-Safe-Eyes-tray-icon", # noqa E501
link="https://github.com/slgobinath/SafeEyes/wiki/How-to-install-backend-for-Safe-Eyes-tray-icon",
retryable=True,
)
command = None

View File

@ -51,6 +51,12 @@ SNI_NODE_INFO = Gio.DBusNodeInfo.new_for_xml(
<property name="Status" type="s" access="read"/>
<signal name="NewIcon"/>
<signal name="NewTooltip"/>
<property name="XAyatanaLabel" type="s" access="read"/>
<signal name="XAyatanaNewLabel">
<arg type="s" name="label" direction="out" />
<arg type="s" name="guide" direction="out" />
</signal>
</interface>
</node>"""
).interfaces[0]
@ -361,6 +367,7 @@ class StatusNotifierItemService(DBusService):
IconName = "io.github.slgobinath.SafeEyes-enabled"
IconThemePath = ""
ToolTip = ("", [], "Safe Eyes", "")
XAyatanaLabel = ""
ItemIsMenu = True
Menu = None
@ -409,6 +416,11 @@ class StatusNotifierItemService(DBusService):
self.emit_signal("NewTooltip")
def set_xayatanalabel(self, label):
self.XAyatanaLabel = label
self.emit_signal("XAyatanaNewLabel", (label, ""))
class TrayIcon:
"""Create and show the tray icon along with the tray menu."""
@ -517,11 +529,12 @@ class TrayIcon:
)
continue
ttw = time_in_minutes
disable_items.append(
{
"id": disable_option_dynamic_id,
"label": label,
"callback": lambda: self.on_disable_clicked(time_in_minutes),
"callback": lambda ttw=ttw: self.on_disable_clicked(ttw),
}
)
@ -629,6 +642,7 @@ class TrayIcon:
description = ""
self.sni_service.set_tooltip("Safe Eyes", description)
self.sni_service.set_xayatanalabel(description)
def quit_safe_eyes(self):
"""Handle Quit menu action.

View File

@ -36,7 +36,7 @@ from safeeyes.core import SafeEyesCore
from safeeyes.ui.settings_dialog import SettingsDialog
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio
from gi.repository import Gtk, Gio, GLib
SAFE_EYES_VERSION = "2.2.2"
@ -45,6 +45,7 @@ class SafeEyes(Gtk.Application):
"""This class represents a runnable Safe Eyes instance."""
required_plugin_dialog_active = False
retry_errored_plugins_count = 0
def __init__(self, system_locale, config, cli_args):
super().__init__(
@ -123,10 +124,7 @@ class SafeEyes(Gtk.Application):
try:
self.plugins_manager.init(self.context, self.config)
except RequiredPluginException as e:
self.show_required_plugin_dialog(
e.get_plugin_id(), e.get_plugin_name(), e.get_message()
)
self.required_plugin_dialog_active = True
self.show_required_plugin_dialog(e)
self.hold()
@ -135,7 +133,11 @@ class SafeEyes(Gtk.Application):
if self.config.get("use_rpc_server", True):
self.__start_rpc_server()
if not self.required_plugin_dialog_active and self.safe_eyes_core.has_breaks():
if (
not self.plugins_manager.needs_retry()
and not self.required_plugin_dialog_active
and self.safe_eyes_core.has_breaks()
):
self.active = True
self.context["state"] = State.START
self.plugins_manager.start() # Call the start method of all plugins
@ -145,6 +147,9 @@ class SafeEyes(Gtk.Application):
def do_activate(self):
logging.info("Application activated")
if self.plugins_manager.needs_retry():
GLib.timeout_add_seconds(1, self._retry_errored_plugins)
if self.cli_args.about:
self.show_about()
elif self.cli_args.disable:
@ -156,6 +161,30 @@ class SafeEyes(Gtk.Application):
elif self.cli_args.take_break:
self.take_break()
def _retry_errored_plugins(self):
if not self.plugins_manager.needs_retry():
return
logging.info("Retry loading errored plugin")
self.plugins_manager.retry_errored_plugins()
error = self.plugins_manager.get_retryable_error()
if error is None:
# success
self.restart(self.config, set_active=True)
return
# errored again
if self.retry_errored_plugins_count >= 3:
self.show_required_plugin_dialog(error)
return
timeout = pow(2, self.retry_errored_plugins_count)
self.retry_errored_plugins_count += 1
GLib.timeout_add_seconds(timeout, self._retry_errored_plugins)
def show_settings(self):
"""Listen to tray icon Settings action and send the signal to Settings
dialog.
@ -166,12 +195,16 @@ class SafeEyes(Gtk.Application):
settings_dialog = SettingsDialog(self.config.clone(), self.save_settings)
settings_dialog.show()
def show_required_plugin_dialog(self, plugin_id, plugin_name, message):
def show_required_plugin_dialog(self, error: RequiredPluginException):
self.required_plugin_dialog_active = True
logging.info("Show RequiredPlugin dialog")
plugin_id = error.get_plugin_id()
dialog = RequiredPluginDialog(
plugin_id,
plugin_name,
message,
error.get_plugin_id(),
error.get_plugin_name(),
error.get_message(),
self.quit,
lambda: self.disable_plugin(plugin_id),
)
@ -306,10 +339,7 @@ class SafeEyes(Gtk.Application):
try:
self.plugins_manager.init(self.context, self.config)
except RequiredPluginException as e:
self.show_required_plugin_dialog(
e.get_plugin_id(), e.get_plugin_name(), e.get_message()
)
self.required_plugin_dialog_active = True
self.show_required_plugin_dialog(e)
return
if set_active: