2017-10-07 15:10:31 +02:00
#!/usr/bin/env python
2017-02-01 01:20:17 +01:00
# 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/>.
2017-10-07 15:10:31 +02:00
"""
This module contains utility functions for Safe Eyes and its plugins .
"""
import errno
2020-04-15 23:52:14 +02:00
import inspect
2019-07-14 04:08:15 +02:00
import importlib
2017-10-07 15:10:31 +02:00
import json
import locale
import logging
import os
2019-07-14 04:08:15 +02:00
import re
2020-12-31 14:34:27 +01:00
import sys
2017-10-07 15:10:31 +02:00
import shutil
import subprocess
import threading
from logging . handlers import RotatingFileHandler
2020-12-26 01:22:57 +01:00
from pathlib import Path
2017-02-01 01:20:17 +01:00
2020-07-09 00:35:51 +02:00
import babel . core
2017-10-07 15:10:31 +02:00
import babel . dates
2017-02-06 03:09:04 +01:00
import gi
2017-10-07 15:10:31 +02:00
gi . require_version ( ' Gtk ' , ' 3.0 ' )
from gi . repository import Gtk
from gi . repository import GLib
2019-01-17 00:15:31 +01:00
from gi . repository import GdkPixbuf
2023-12-30 18:42:32 +01:00
from packaging . version import parse
2017-10-07 15:10:31 +02:00
2017-02-06 03:09:04 +01:00
gi . require_version ( ' Gdk ' , ' 3.0 ' )
2017-10-07 15:10:31 +02:00
BIN_DIRECTORY = os . path . dirname ( os . path . realpath ( __file__ ) )
2017-10-14 16:03:39 +02:00
HOME_DIRECTORY = os . environ . get ( ' HOME ' ) or os . path . expanduser ( ' ~ ' )
2018-06-23 00:22:14 +02:00
CONFIG_DIRECTORY = os . path . join ( os . environ . get (
' XDG_CONFIG_HOME ' ) or os . path . join ( HOME_DIRECTORY , ' .config ' ) , ' safeeyes ' )
2023-05-10 11:45:38 +02:00
STYLE_SHEET_DIRECTORY = os . path . join ( CONFIG_DIRECTORY , ' style ' )
2017-10-07 15:10:31 +02:00
CONFIG_FILE_PATH = os . path . join ( CONFIG_DIRECTORY , ' safeeyes.json ' )
CONFIG_RESOURCE = os . path . join ( CONFIG_DIRECTORY , ' resource ' )
SESSION_FILE_PATH = os . path . join ( CONFIG_DIRECTORY , ' session.json ' )
2023-05-10 11:45:38 +02:00
STYLE_SHEET_PATH = os . path . join ( STYLE_SHEET_DIRECTORY , ' safeeyes_style.css ' )
2017-10-07 15:10:31 +02:00
SYSTEM_CONFIG_FILE_PATH = os . path . join ( BIN_DIRECTORY , " config/safeeyes.json " )
2018-06-23 00:22:14 +02:00
SYSTEM_STYLE_SHEET_PATH = os . path . join (
BIN_DIRECTORY , " config/style/safeeyes_style.css " )
2017-12-07 00:45:20 +01:00
LOG_FILE_PATH = os . path . join ( HOME_DIRECTORY , ' safeeyes.log ' )
2017-10-07 15:10:31 +02:00
SYSTEM_PLUGINS_DIR = os . path . join ( BIN_DIRECTORY , ' plugins ' )
USER_PLUGINS_DIR = os . path . join ( CONFIG_DIRECTORY , ' plugins ' )
LOCALE_PATH = os . path . join ( BIN_DIRECTORY , ' config/locale ' )
2023-05-10 11:45:38 +02:00
SYSTEM_DESKTOP_FILE = os . path . join ( BIN_DIRECTORY , " platform/io.github.slgobinath.SafeEyes.desktop " )
2020-12-26 01:22:57 +01:00
SYSTEM_ICONS = os . path . join ( BIN_DIRECTORY , " platform/icons " )
2017-10-07 15:10:31 +02:00
DESKTOP_ENVIRONMENT = None
2019-07-14 04:08:15 +02:00
IS_WAYLAND = False
2017-02-06 03:09:04 +01:00
2019-01-17 00:15:31 +01:00
2017-04-09 02:29:51 +02:00
def get_resource_path ( resource_name ) :
2017-10-07 15:10:31 +02:00
"""
Return the user - defined resource if a system resource is overridden by the user .
Otherwise , return the system resource . Return None if the specified resource does not exist .
"""
if resource_name is None :
return None
resource_location = os . path . join ( CONFIG_RESOURCE , resource_name )
if not os . path . isfile ( resource_location ) :
2018-06-23 00:22:14 +02:00
resource_location = os . path . join (
BIN_DIRECTORY , ' resource ' , resource_name )
2017-10-07 15:10:31 +02:00
if not os . path . isfile ( resource_location ) :
# Resource not found
resource_location = None
return resource_location
2017-02-06 03:09:04 +01:00
2017-02-16 00:29:14 +01:00
def start_thread ( target_function , * * args ) :
2017-10-07 15:10:31 +02:00
"""
Execute the function in a separate thread .
"""
2019-02-23 15:57:53 +01:00
thread = threading . Thread ( target = target_function , name = " WorkThread " , daemon = False , kwargs = args )
2017-10-07 15:10:31 +02:00
thread . start ( )
2017-02-06 03:09:04 +01:00
2024-06-19 02:15:47 +02:00
# def execute_main_thread(target_function, *args, **kwargs):
2020-10-25 13:44:29 +01:00
# """
2024-06-19 02:15:47 +02:00
# Execute the given function in main thread, forwarding positional and keyword arguments.
2020-10-25 13:44:29 +01:00
# """
2024-06-19 02:15:47 +02:00
def execute_main_thread ( target_function , * args , * * kwargs ) :
2017-10-07 15:10:31 +02:00
"""
Execute the given function in main thread .
"""
2024-06-19 02:15:47 +02:00
GLib . idle_add ( lambda : target_function ( * args , * * kwargs ) )
2017-10-07 15:10:31 +02:00
2018-01-17 00:44:40 +01:00
def system_locale ( category = locale . LC_MESSAGES ) :
2017-10-07 15:10:31 +02:00
"""
Return the system locale . If not available , return en_US . UTF - 8.
"""
try :
locale . setlocale ( locale . LC_ALL , ' ' )
2018-01-17 00:44:40 +01:00
sys_locale = locale . getlocale ( category ) [ 0 ]
2017-10-07 15:10:31 +02:00
if not sys_locale :
sys_locale = ' en_US.UTF-8 '
return sys_locale
except BaseException :
# Some systems does not return proper locale
return ' en_US.UTF-8 '
2017-03-13 14:30:44 +01:00
def format_time ( time ) :
2017-10-07 15:10:31 +02:00
"""
Format time based on the system time .
"""
2018-01-17 00:44:40 +01:00
sys_locale = system_locale ( locale . LC_TIME )
2020-07-09 00:35:51 +02:00
try :
return babel . dates . format_time ( time , format = ' short ' , locale = sys_locale )
except babel . core . UnknownLocaleError :
# Some locale types are not supported by the babel library.
# Use 'en' locale format if the system locale is not supported.
return babel . dates . format_time ( time , format = ' short ' , locale = ' en ' )
2017-02-10 02:45:49 +01:00
def mkdir ( path ) :
2017-10-07 15:10:31 +02:00
"""
Create directory if not exists .
"""
try :
os . makedirs ( path )
except OSError as exc :
if exc . errno == errno . EEXIST and os . path . isdir ( path ) :
pass
else :
logging . error ( ' Error while creating ' + str ( path ) )
raise
def load_json ( json_path ) :
"""
Load the JSON file from the given path .
"""
json_obj = None
if os . path . isfile ( json_path ) :
try :
with open ( json_path ) as config_file :
json_obj = json . load ( config_file )
except BaseException :
pass
return json_obj
def write_json ( json_path , json_obj ) :
"""
Write the JSON object at the given path
"""
try :
with open ( json_path , ' w ' ) as json_file :
json . dump ( json_obj , json_file , indent = 4 , sort_keys = True )
except BaseException :
pass
def delete ( file_path ) :
"""
Delete the given file or directory
"""
try :
os . remove ( file_path )
except OSError :
pass
2020-11-15 23:41:13 +01:00
def check_plugin_dependencies ( plugin_id , plugin_config , plugin_settings , plugin_path ) :
2017-10-07 15:10:31 +02:00
"""
Check the plugin dependencies .
"""
# Check the desktop environment
if plugin_config [ ' dependencies ' ] [ ' desktop_environments ' ] :
# Plugin has restrictions on desktop environments
if DESKTOP_ENVIRONMENT not in plugin_config [ ' dependencies ' ] [ ' desktop_environments ' ] :
return _ ( ' Plugin does not support %s desktop environment ' ) % DESKTOP_ENVIRONMENT
# Check the Python modules
for module in plugin_config [ ' dependencies ' ] [ ' python_modules ' ] :
if not module_exist ( module ) :
return _ ( " Please install the Python module ' %s ' " ) % module
# Check the shell commands
for command in plugin_config [ ' dependencies ' ] [ ' shell_commands ' ] :
if not command_exist ( command ) :
return _ ( " Please install the command-line tool ' %s ' " ) % command
# Check the resources
for resource in plugin_config [ ' dependencies ' ] [ ' resources ' ] :
if get_resource_path ( resource ) is None :
return _ ( ' Please add the resource %(resource)s to %(config_resource)s directory ' ) % { ' resource ' : resource , ' config_resource ' : CONFIG_RESOURCE }
2019-07-14 04:08:15 +02:00
plugin_dependency_checker = os . path . join ( plugin_path , ' dependency_checker.py ' )
if os . path . isfile ( plugin_dependency_checker ) :
dependency_checker = importlib . import_module ( ( plugin_id + ' .dependency_checker ' ) )
if dependency_checker and hasattr ( dependency_checker , " validate " ) :
2020-11-15 23:41:13 +01:00
return dependency_checker . validate ( plugin_config , plugin_settings )
2019-07-14 04:08:15 +02:00
2017-10-07 15:10:31 +02:00
return None
def load_plugins_config ( safeeyes_config ) :
"""
Load all the plugins from the given directory .
"""
configs = [ ]
for plugin in safeeyes_config . get ( ' plugins ' ) :
plugin_path = os . path . join ( SYSTEM_PLUGINS_DIR , plugin [ ' id ' ] )
if not os . path . isdir ( plugin_path ) :
# User plugin
plugin_path = os . path . join ( USER_PLUGINS_DIR , plugin [ ' id ' ] )
plugin_config_path = os . path . join ( plugin_path , ' config.json ' )
plugin_icon_path = os . path . join ( plugin_path , ' icon.png ' )
plugin_module_path = os . path . join ( plugin_path , ' plugin.py ' )
if not os . path . isfile ( plugin_module_path ) :
return
icon = None
if os . path . isfile ( plugin_icon_path ) :
icon = plugin_icon_path
else :
icon = get_resource_path ( ' ic_plugin.png ' )
config = load_json ( plugin_config_path )
if config is None :
continue
2020-11-15 23:41:13 +01:00
dependency_description = check_plugin_dependencies ( plugin [ ' id ' ] , config , plugin . get ( ' settings ' , { } ) , plugin_path )
2017-10-07 15:10:31 +02:00
if dependency_description :
config [ ' error ' ] = True
2024-07-16 10:52:31 +02:00
config [ ' meta ' ] [ ' dependency_description ' ] = dependency_description
2017-10-07 15:10:31 +02:00
icon = get_resource_path ( ' ic_warning.png ' )
else :
config [ ' error ' ] = False
config [ ' id ' ] = plugin [ ' id ' ]
config [ ' icon ' ] = icon
config [ ' enabled ' ] = plugin [ ' enabled ' ]
for setting in config [ ' settings ' ] :
setting [ ' safeeyes_config ' ] = plugin [ ' settings ' ]
configs . append ( config )
return configs
2017-04-08 15:30:44 +02:00
2017-06-19 17:23:28 +02:00
def desktop_environment ( ) :
2017-10-07 15:10:31 +02:00
"""
Detect the desktop environment .
"""
global DESKTOP_ENVIRONMENT
desktop_session = os . environ . get ( ' DESKTOP_SESSION ' )
current_desktop = os . environ . get ( ' XDG_CURRENT_DESKTOP ' )
env = ' unknown '
if desktop_session is not None :
desktop_session = desktop_session . lower ( )
if desktop_session in [ ' gnome ' , ' unity ' , ' budgie-desktop ' , ' cinnamon ' , ' mate ' , ' xfce4 ' , ' lxde ' , ' pantheon ' , ' fluxbox ' , ' blackbox ' , ' openbox ' , ' icewm ' , ' jwm ' , ' afterstep ' , ' trinity ' , ' kde ' ] :
env = desktop_session
elif desktop_session . startswith ( ' xubuntu ' ) or ( current_desktop is not None and ' xfce ' in current_desktop ) :
env = ' xfce '
elif desktop_session . startswith ( ' lubuntu ' ) :
env = ' lxde '
elif ' plasma ' in desktop_session or desktop_session . startswith ( ' kubuntu ' ) or os . environ . get ( ' KDE_FULL_SESSION ' ) == ' true ' :
env = ' kde '
2023-11-13 16:01:57 +01:00
elif os . environ . get ( ' GNOME_DESKTOP_SESSION_ID ' ) or desktop_session . startswith ( ' gnome ' ) :
2017-10-07 15:10:31 +02:00
env = ' gnome '
elif desktop_session . startswith ( ' ubuntu ' ) :
env = ' unity '
2021-09-29 13:53:08 +02:00
elif current_desktop is not None :
if current_desktop . startswith ( ' sway ' ) :
env = ' sway '
2017-10-07 15:10:31 +02:00
DESKTOP_ENVIRONMENT = env
return env
2019-07-14 04:08:15 +02:00
def is_wayland ( ) :
"""
Determine if Wayland is running
https : / / unix . stackexchange . com / a / 325972 / 222290
"""
global IS_WAYLAND
2022-10-13 17:52:47 +02:00
# Easy method. Does not depend on loginctl
# https://stackoverflow.com/questions/45536141/how-i-can-find-out-if-a-linux-system-uses-wayland-or-x11/45537237#45537237
if " WAYLAND_DISPLAY " in os . environ :
IS_WAYLAND = True
return IS_WAYLAND
2019-07-14 04:08:15 +02:00
try :
session_id = subprocess . check_output ( [ ' loginctl ' ] ) . split ( b ' \n ' ) [ 1 ] . split ( ) [ 0 ]
output = subprocess . check_output (
[ ' loginctl ' , ' show-session ' , session_id , ' -p ' , ' Type ' ]
)
except BaseException :
logging . warning ( ' Unable to determine if wayland is running. Assuming no. ' )
IS_WAYLAND = False
else :
IS_WAYLAND = bool ( re . search ( b ' wayland ' , output , re . IGNORECASE ) )
return IS_WAYLAND
2017-10-07 15:10:31 +02:00
def execute_command ( command , args = [ ] ) :
"""
Execute the shell command without waiting for its response .
"""
if command :
command_to_execute = [ ]
if isinstance ( command , str ) :
command_to_execute . append ( command )
else :
command_to_execute . extend ( command )
if args :
command_to_execute . extend ( args )
try :
subprocess . Popen ( command_to_execute )
except BaseException :
logging . error ( ' Error in executing the command ' + str ( command ) )
2017-04-19 01:34:22 +02:00
2017-04-19 19:56:06 +02:00
def command_exist ( command ) :
2017-10-07 15:10:31 +02:00
"""
Check whether the given command exist in the system or not .
"""
if shutil . which ( command ) :
return True
return False
def module_exist ( module ) :
"""
Check wther the given Python module exists or not .
"""
try :
2023-11-30 19:01:58 +01:00
importlib . util . find_spec ( module )
2017-10-07 15:10:31 +02:00
return True
except ImportError :
return False
2017-04-19 19:56:06 +02:00
2017-04-25 19:18:37 +02:00
def merge_configs ( new_config , old_config ) :
2017-10-07 15:10:31 +02:00
"""
Merge the values of old_config into the new_config .
"""
new_config = new_config . copy ( )
new_config . update ( old_config )
return new_config
def initialize_safeeyes ( ) :
"""
2023-05-10 11:45:38 +02:00
Create the config file and style sheet in XDG_CONFIG_HOME ( or ~ / . config ) / safeeyes directory .
2017-10-07 15:10:31 +02:00
"""
2023-05-10 11:45:38 +02:00
logging . info ( ' Copy the config files to XDG_CONFIG_HOME(or ~/.config)/safeeyes ' )
2020-12-26 01:22:57 +01:00
2019-02-09 18:46:20 +01:00
# Remove the ~/.config/safeeyes/safeeyes.json file
delete ( CONFIG_FILE_PATH )
2017-10-07 15:10:31 +02:00
2024-07-25 22:39:08 +02:00
if not os . path . isdir ( CONFIG_DIRECTORY ) :
mkdir ( CONFIG_DIRECTORY )
2017-10-07 15:10:31 +02:00
# Copy the safeeyes.json
shutil . copy2 ( SYSTEM_CONFIG_FILE_PATH , CONFIG_FILE_PATH )
2020-12-20 15:25:30 +01:00
os . chmod ( CONFIG_FILE_PATH , 0o777 )
2017-10-07 15:10:31 +02:00
2024-07-25 22:39:08 +02:00
create_user_stylesheet_if_missing ( )
# initialize_safeeyes gets called when the configuration file is not present, which happens just after installation or manual deletion of .config/safeeyes/safeeyes.json file. In these cases, we want to force the creation of a startup entry
create_startup_entry ( force = True )
def create_user_stylesheet_if_missing ( ) :
# Create the XDG_CONFIG_HOME(or ~/.config)/safeeyes/style directory
if not os . path . isdir ( STYLE_SHEET_DIRECTORY ) :
mkdir ( STYLE_SHEET_DIRECTORY )
2017-10-07 15:10:31 +02:00
# Copy the new style sheet
if not os . path . isfile ( STYLE_SHEET_PATH ) :
shutil . copy2 ( SYSTEM_STYLE_SHEET_PATH , STYLE_SHEET_PATH )
2020-12-20 15:25:30 +01:00
os . chmod ( STYLE_SHEET_PATH , 0o777 )
2017-10-07 15:10:31 +02:00
2023-08-18 17:15:30 +02:00
def create_startup_entry ( force = False ) :
2021-01-11 03:39:39 +01:00
"""
Create start up entry .
"""
startup_dir_path = os . path . join ( HOME_DIRECTORY , ' .config/autostart ' )
2023-05-10 11:45:38 +02:00
startup_entry = os . path . join ( startup_dir_path , ' io.github.slgobinath.SafeEyes.desktop ' )
2023-08-18 17:15:30 +02:00
# until SafeEyes 2.1.5 the startup entry had another name
# https://github.com/slgobinath/SafeEyes/commit/684d16265a48794bb3fd670da67283fe4e2f591b#diff-0863348c2143a4928518a4d3661f150ba86d042bf5320b462ea2e960c36ed275L398
obsolete_entry = os . path . join ( startup_dir_path , ' safeeyes.desktop ' )
2021-01-11 03:39:39 +01:00
2023-08-18 17:15:30 +02:00
create_link = False
if force :
# if force is True, just create the link
create_link = True
else :
# if force is False, we want to avoid creating the startup symlink if it was manually deleted by the user, we want to create it only if a broken one is found
if os . path . islink ( startup_entry ) :
# if the link exists, check if it is broken
try :
os . stat ( startup_entry )
except FileNotFoundError :
# a FileNotFoundError will get thrown if the startup symlink is broken
create_link = True
if os . path . islink ( obsolete_entry ) :
# if a link with the old naming exists, delete it and create a new one
create_link = True
delete ( obsolete_entry )
if create_link :
# Create the folder if not exist
mkdir ( startup_dir_path )
2021-01-11 03:39:39 +01:00
2023-08-18 17:15:30 +02:00
# Remove existing files
delete ( startup_entry )
2021-01-11 03:39:39 +01:00
2023-08-18 17:15:30 +02:00
# Create the new startup entry
try :
os . symlink ( SYSTEM_DESKTOP_FILE , startup_entry )
except OSError :
logging . error ( " Failed to create startup entry at %s " % startup_entry )
2021-01-11 03:39:39 +01:00
2020-12-26 01:22:57 +01:00
def initialize_platform ( ) :
"""
Copy icons and generate desktop entries .
"""
logging . debug ( " Initialize the platform " )
applications_dir_path = os . path . join ( HOME_DIRECTORY , ' .local/share/applications ' )
icons_dir_path = os . path . join ( HOME_DIRECTORY , ' .local/share/icons ' )
2023-05-10 11:45:38 +02:00
desktop_entry = os . path . join ( applications_dir_path , ' io.github.slgobinath.SafeEyes.desktop ' )
2020-12-26 01:22:57 +01:00
2021-01-11 03:39:39 +01:00
# Create the folder if not exist
2020-12-26 01:22:57 +01:00
mkdir ( icons_dir_path )
2021-01-11 03:39:39 +01:00
# Create a desktop entry
2023-05-10 11:45:38 +02:00
if not os . path . exists ( os . path . join ( sys . prefix , " share/applications/io.github.slgobinath.SafeEyes.desktop " ) ) :
2020-12-31 14:34:27 +01:00
# Create the folder if not exist
mkdir ( applications_dir_path )
# Remove existing file
delete ( desktop_entry )
# Create a link
try :
os . symlink ( SYSTEM_DESKTOP_FILE , desktop_entry )
except OSError :
logging . error ( " Failed to create desktop entry at %s " % desktop_entry )
2020-12-26 01:22:57 +01:00
# Add links for all icons
for ( path , _ , filenames ) in os . walk ( SYSTEM_ICONS ) :
for filename in filenames :
system_icon = os . path . join ( path , filename )
local_icon = os . path . join ( icons_dir_path , os . path . relpath ( system_icon , SYSTEM_ICONS ) )
2020-12-31 14:34:27 +01:00
global_icon = os . path . join ( sys . prefix , " share/icons " , os . path . relpath ( system_icon , SYSTEM_ICONS ) )
2020-12-26 01:22:57 +01:00
parent_dir = str ( Path ( local_icon ) . parent )
2020-12-31 14:34:27 +01:00
if os . path . exists ( global_icon ) :
# This icon is already added to the /usr/share/icons/hicolor folder
continue
2020-12-26 01:22:57 +01:00
# Create the directory if not exists
mkdir ( parent_dir )
# Remove the link if already exists
delete ( local_icon )
# Add a link for the icon
try :
os . symlink ( system_icon , local_icon )
except OSError :
logging . error ( " Failed to create icon link at %s " % local_icon )
2017-10-07 15:10:31 +02:00
2019-02-09 18:46:20 +01:00
def reset_config ( ) :
2019-02-10 15:37:09 +01:00
# Remove the ~/.config/safeeyes/safeeyes.json and safeeyes_style.css
2019-02-09 18:46:20 +01:00
delete ( CONFIG_FILE_PATH )
2019-02-10 15:37:09 +01:00
delete ( STYLE_SHEET_PATH )
2019-02-09 18:46:20 +01:00
2019-02-10 15:37:09 +01:00
# Copy the safeeyes.json and safeeyes_style.css
2019-02-09 18:46:20 +01:00
shutil . copy2 ( SYSTEM_CONFIG_FILE_PATH , CONFIG_FILE_PATH )
2019-02-10 15:37:09 +01:00
shutil . copy2 ( SYSTEM_STYLE_SHEET_PATH , STYLE_SHEET_PATH )
2020-12-20 15:25:30 +01:00
# Add write permission (e.g. if original file was stored in /nix/store)
os . chmod ( CONFIG_FILE_PATH , 0o777 )
os . chmod ( STYLE_SHEET_PATH , 0o777 )
2019-02-09 18:46:20 +01:00
2021-01-11 03:39:39 +01:00
create_startup_entry ( )
2020-12-26 01:22:57 +01:00
2019-01-19 18:25:19 +01:00
def replace_style_sheet ( ) :
"""
Replace the user style sheet by system style sheet .
"""
delete ( STYLE_SHEET_PATH )
shutil . copy2 ( SYSTEM_STYLE_SHEET_PATH , STYLE_SHEET_PATH )
2020-12-20 15:25:30 +01:00
os . chmod ( STYLE_SHEET_PATH , 0o777 )
2019-01-19 18:25:19 +01:00
2023-09-30 23:03:12 +02:00
def initialize_logging ( debug ) :
2017-10-07 15:10:31 +02:00
"""
Initialize the logging framework using the Safe Eyes specific configurations .
"""
# Configure logging.
root_logger = logging . getLogger ( )
2018-06-23 00:22:14 +02:00
log_formatter = logging . Formatter (
' %(asctime)s [ %(levelname)s ]:[ %(threadName)s ] %(message)s ' )
2017-10-07 15:10:31 +02:00
2017-12-07 00:45:20 +01:00
# Append the logs and overwrite once reached 1MB
2017-10-07 15:10:31 +02:00
if debug :
2017-12-07 00:45:20 +01:00
# Log to file
2018-06-23 00:22:14 +02:00
file_handler = RotatingFileHandler (
LOG_FILE_PATH , maxBytes = 1024 * 1024 , backupCount = 5 , encoding = None , delay = 0 )
2017-12-07 00:45:20 +01:00
file_handler . setFormatter ( log_formatter )
# Log to console
2017-10-07 15:10:31 +02:00
console_handler = logging . StreamHandler ( )
console_handler . setFormatter ( log_formatter )
2017-12-07 00:45:20 +01:00
root_logger . setLevel ( logging . DEBUG )
2017-10-07 15:10:31 +02:00
root_logger . addHandler ( console_handler )
2017-12-07 00:45:20 +01:00
root_logger . addHandler ( file_handler )
2017-10-07 15:10:31 +02:00
else :
2017-12-07 00:45:20 +01:00
root_logger . propagate = False
2017-10-07 15:10:31 +02:00
def __open_plugin_config ( plugins_dir , plugin_id ) :
"""
Open the given plugin ' s configuration.
"""
plugin_config_path = os . path . join ( plugins_dir , plugin_id , ' config.json ' )
plugin_module_path = os . path . join ( plugins_dir , plugin_id , ' plugin.py ' )
if not os . path . isfile ( plugin_config_path ) or not os . path . isfile ( plugin_module_path ) :
# Either the config.json or plugin.py is not available
return None
return load_json ( plugin_config_path )
def __update_plugin_config ( plugin , plugin_config , config ) :
"""
Update the plugin configuration .
"""
if plugin_config is None :
config [ ' plugins ' ] . remove ( plugin )
else :
2023-12-30 18:42:32 +01:00
if parse ( plugin . get ( ' version ' , ' 0.0.0 ' ) ) != parse ( plugin_config [ ' meta ' ] [ ' version ' ] ) :
2017-10-07 15:10:31 +02:00
# Update the configuration
plugin [ ' version ' ] = plugin_config [ ' meta ' ] [ ' version ' ]
setting_ids = [ ]
# Add the new settings
for setting in plugin_config [ ' settings ' ] :
setting_ids . append ( setting [ ' id ' ] )
2018-06-23 00:22:14 +02:00
if ' settings ' not in plugin :
plugin [ ' settings ' ] = { }
2017-10-07 15:10:31 +02:00
if plugin [ ' settings ' ] . get ( setting [ ' id ' ] , None ) is None :
plugin [ ' settings ' ] [ setting [ ' id ' ] ] = setting [ ' default ' ]
# Remove the removed ids
keys_to_remove = [ ]
2017-10-12 15:44:34 +02:00
for key in plugin . get ( ' settings ' , [ ] ) :
2017-10-07 15:10:31 +02:00
if key not in setting_ids :
keys_to_remove . append ( key )
for key in keys_to_remove :
del plugin [ ' settings ' ] [ key ]
def __add_plugin_config ( plugin_id , plugin_config , safe_eyes_config ) :
"""
"""
if plugin_config is None :
return
config = { }
config [ ' id ' ] = plugin_id
config [ ' enabled ' ] = False # By default plugins are disabled
config [ ' version ' ] = plugin_config [ ' meta ' ] [ ' version ' ]
if plugin_config [ ' settings ' ] :
config [ ' settings ' ] = { }
for setting in plugin_config [ ' settings ' ] :
config [ ' settings ' ] [ setting [ ' id ' ] ] = setting [ ' default ' ]
safe_eyes_config [ ' plugins ' ] . append ( config )
def merge_plugins ( config ) :
"""
Merge plugin configurations with Safe Eyes configuration .
"""
system_plugins = None
user_plugins = None
# Load system plugins id
if os . path . isdir ( SYSTEM_PLUGINS_DIR ) :
system_plugins = os . listdir ( SYSTEM_PLUGINS_DIR )
else :
system_plugins = [ ]
# Load user plugins id
if os . path . isdir ( USER_PLUGINS_DIR ) :
user_plugins = os . listdir ( USER_PLUGINS_DIR )
else :
user_plugins = [ ]
# Create a list of existing plugins
for plugin in config [ ' plugins ' ] :
plugin_id = plugin [ ' id ' ]
if plugin_id in system_plugins :
plugin_config = __open_plugin_config ( SYSTEM_PLUGINS_DIR , plugin_id )
__update_plugin_config ( plugin , plugin_config , config )
system_plugins . remove ( plugin_id )
elif plugin_id in user_plugins :
plugin_config = __open_plugin_config ( USER_PLUGINS_DIR , plugin_id )
__update_plugin_config ( plugin , plugin_config , config )
user_plugins . remove ( plugin_id )
else :
config [ ' plugins ' ] . remove ( plugin )
# Add all system plugins
for plugin_id in system_plugins :
plugin_config = __open_plugin_config ( SYSTEM_PLUGINS_DIR , plugin_id )
__add_plugin_config ( plugin_id , plugin_config , config )
# Add all user plugins
for plugin_id in user_plugins :
plugin_config = __open_plugin_config ( USER_PLUGINS_DIR , plugin_id )
__add_plugin_config ( plugin_id , plugin_config , config )
def open_session ( ) :
"""
Open the last session .
"""
logging . info ( ' Reading the session file ' )
session = load_json ( SESSION_FILE_PATH )
if session is None :
session = { ' plugin ' : { } }
return session
def create_gtk_builder ( glade_file ) :
"""
Create a Gtk builder and load the glade file .
"""
builder = Gtk . Builder ( )
builder . set_translation_domain ( ' safeeyes ' )
builder . add_from_file ( glade_file )
# Tranlslate all sub components
for obj in builder . get_objects ( ) :
if ( not isinstance ( obj , Gtk . SeparatorMenuItem ) ) and hasattr ( obj , " get_label " ) :
label = obj . get_label ( )
if label is not None :
obj . set_label ( _ ( label ) )
elif hasattr ( obj , " get_title " ) :
title = obj . get_title ( )
if title is not None :
obj . set_title ( _ ( title ) )
return builder
2019-01-17 00:15:31 +01:00
def load_and_scale_image ( path , width , height ) :
if not os . path . isfile ( path ) :
return None
pixbuf = GdkPixbuf . Pixbuf . new_from_file_at_scale (
filename = path ,
width = width ,
height = height ,
preserve_aspect_ratio = True )
image = Gtk . Image . new_from_pixbuf ( pixbuf )
return image
2020-04-15 23:52:14 +02:00
def has_method ( module , method_name , no_of_args = 0 ) :
"""
Check whether the given function is defined in the module or not .
"""
if hasattr ( module , method_name ) :
2023-04-18 02:06:11 +02:00
if len ( inspect . getfullargspec ( getattr ( module , method_name ) ) . args ) == no_of_args :
2020-04-15 23:52:14 +02:00
return True
return False
def remove_if_exists ( list_of_items , item ) :
"""
Remove the item from the list_of_items it it exists .
"""
if item in list_of_items :
2020-12-20 15:25:30 +01:00
list_of_items . remove ( item )