#!/usr/bin/env python3 # == Rapid Develop Macros in LibreOffice == # ~ https://git.cuates.net/elmau/easymacro # ~ easymacro 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. # ~ easymacro 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 easymacro. If not, see . import datetime import getpass import logging import os import platform import socket import subprocess import sys import time import traceback from functools import wraps from pprint import pprint from typing import Any import uno from com.sun.star.awt import MessageBoxButtons as MSG_BUTTONS from com.sun.star.awt.MessageBoxResults import YES from com.sun.star.beans import PropertyValue LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s' LOG_DATE = '%d/%m/%Y %H:%M:%S' logging.addLevelName(logging.ERROR, '\033[1;41mERROR\033[1;0m') logging.addLevelName(logging.DEBUG, '\x1b[33mDEBUG\033[1;0m') logging.addLevelName(logging.INFO, '\x1b[32mINFO\033[1;0m') logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT, datefmt=LOG_DATE) log = logging.getLogger(__name__) # Global variables OS = platform.system() DESKTOP = os.environ.get('DESKTOP_SESSION', '') PC = platform.node() USER = getpass.getuser() IS_WIN = OS == 'Windows' IS_MAC = OS == 'Darwin' _info_debug = f"Python: {sys.version}\n\n{platform.platform()}\n\n" + '\n'.join(sys.path) SALT = b'00a1bfb05353bb3fd8e7aa7fe5efdccc' CTX = uno.getComponentContext() SM = CTX.getServiceManager() # UNO Enum class MessageBoxType(): from com.sun.star.awt.MessageBoxType \ import MESSAGEBOX, INFOBOX, WARNINGBOX, ERRORBOX, QUERYBOX MBT = MessageBoxType def debug(*messages) -> None: """Show messages debug :param messages: List of messages to debug :type messages: list[Any] """ data = [str(m) for m in messages] log.debug('\t'.join(data)) return def error(message: Any) -> None: """Show message error :param message: The message error :type message: Any """ log.error(message) return def info(*messages) -> None: """Show messages info :param messages: List of messages to debug :type messages: list[Any] """ data = [str(m) for m in messages] log.info('\t'.join(data)) return def save_log(path: str, data: Any) -> None: """Save data in file, data append to end and automatic add current time. :param path: Path to save log :type path: str :param data: Data to save in file log :type data: Any """ with open(path, 'a') as f: f.write(f'{str(now())[:19]} - ') pprint(data, stream=f) return def create_instance(name: str, with_context: bool=False, argument: Any=None) -> Any: """Create a service instance :param name: Name of service :type name: str :param with_context: If used context :type with_context: bool :param argument: If needed some argument :type argument: Any :return: PyUno instance :rtype: PyUno Object """ if with_context: instance = SM.createInstanceWithContext(name, CTX) elif argument: instance = SM.createInstanceWithArguments(name, (argument,)) else: instance = SM.createInstance(name) return instance def get_app_config(node_name: str, key: str='') -> Any: """Get any key from any node from LibreOffice configuration. :param node_name: Name of node :type name: str :param key: Name of key :type key: str :return: Any value :rtype: Any `See Api ConfigurationProvider `_ """ name = 'com.sun.star.configuration.ConfigurationProvider' service = 'com.sun.star.configuration.ConfigurationAccess' cp = create_instance(name, True) node = PropertyValue(Name='nodepath', Value=node_name) value = '' try: value = cp.createInstanceWithArguments(service, (node,)) if value and value.hasByName(key): value = value.getPropertyValue(key) except Exception as e: error(e) value = '' return value def set_app_config(node_name: str, key: str, new_value: Any) -> Any: result = True current_value = '' name = 'com.sun.star.configuration.ConfigurationProvider' service = 'com.sun.star.configuration.ConfigurationUpdateAccess' cp = create_instance(name, True) node = PropertyValue(Name='nodepath', Value=node_name) update = cp.createInstanceWithArguments(service, (node,)) try: current_value = update.getPropertyValue(key) update.setPropertyValue(key, new_value) update.commitChanges() except Exception as e: error(e) if update.hasByName(key) and current_value: update.setPropertyValue(key, current_value) update.commitChanges() result = False return result def _set_app_command(command: str, disable: bool): NEW_NODE_NAME = f'zaz_disable_command_{command.lower()}' name = 'com.sun.star.configuration.ConfigurationProvider' service = 'com.sun.star.configuration.ConfigurationUpdateAccess' node_name = '/org.openoffice.Office.Commands/Execute/Disabled' cp = create_instance(name, True) node = PropertyValue(Name='nodepath', Value=node_name) update = cp.createInstanceWithArguments(service, (node,)) if disable: new_node = update.createInstanceWithArguments(()) new_node.setPropertyValue('Command', command) update.insertByName(NEW_NODE_NAME, new_node) else: update.removeByName(NEW_NODE_NAME) update.commitChanges() return class commands(): # ~ https://wiki.documentfoundation.org/Development/DispatchCommands def __init__(self, command): self._command = command def disable(self): return _set_app_command(self._command, True) def enabled(self): return _set_app_command(self._command, False) # Get info LibO NAME = TITLE = get_app_config('org.openoffice.Setup/Product', 'ooName') VERSION = get_app_config('org.openoffice.Setup/Product','ooSetupVersion') LANGUAGE = get_app_config('org.openoffice.Setup/L10N/', 'ooLocale') LANG = LANGUAGE.split('-')[0] INFO_DEBUG = f"{NAME} v{VERSION} {LANGUAGE}\n\n{_info_debug}" node = '/org.openoffice.Office.Calc/Calculate/Other/Date' year = get_app_config(node, 'YY') month = get_app_config(node, 'MM') day = get_app_config(node, 'DD') DATE_OFFSET = datetime.date(year, month, day).toordinal() def mri(obj: Any) -> None: """Inspect object with MRI Extension :param obj: Any pyUno object :type obj: Any `See MRI `_ """ mri = create_instance('mytools.Mri') if m is None: msg = 'Extension MRI not found' error(msg) return if hasattr(obj, 'obj'): obj = obj.obj mri.inspect(obj) return def catch_exception(f): """Catch exception for any function :param f: Any Python function :type f: Function instance """ @wraps(f) def func(*args, **kwargs): try: return f(*args, **kwargs) except Exception as e: name = f.__name__ if IS_WIN: msgbox(traceback.format_exc()) log.error(name, exc_info=True) return func def msgbox(message: Any, title: str=TITLE, buttons=MSG_BUTTONS.BUTTONS_OK, \ type_message_box=MessageBoxType.INFOBOX) -> Any: """Create message box :param message: Any type message, all is converted to string. :type message: Any :param title: The title for message box :type title: str :param buttons: A combination of `com::sun::star::awt::MessageBoxButtons `_ :type buttons: long :param type_message_box: The `message box type `_ :type type_message_box: enum :return: Instance MessageBox :rtype: pyUno `See Api XMessageBoxFactory `_ """ toolkit = create_instance('com.sun.star.awt.Toolkit') parent = toolkit.getDesktopWindow() box = toolkit.createMessageBox(parent, type_message_box, buttons, title, str(message)) return box.execute() def question(message: str, title: str=TITLE) -> Any: """Create message box question, show buttons YES and NO :param message: Message question :type message: str :param title: The title for message box :type title: str :return: True if user click YES and False if click NO :rtype: bool """ result = msgbox(message, title, MSG_BUTTONS.BUTTONS_YES_NO, MessageBoxType.QUERYBOX) return result == YES def warning(message: Any, title: str=TITLE) -> Any: """Create message box with icon warning :param message: Any type message, all is converted to string. :type message: Any :param title: The title for message box :type title: str :return: Instance MessageBox :rtype: pyUno """ return msgbox(message, title, type_message_box=MessageBoxType.WARNINGBOX) def errorbox(message: Any, title: str=TITLE) -> Any: """Create message box with icon error :param message: Any type message, all is converted to string. :type message: Any :param title: The title for message box :type title: str :return: Instance MessageBox :rtype: pyUno """ return msgbox(message, title, type_message_box=MessageBoxType.ERRORBOX) def now(): """Current local date and time :return: Return the current local date and time :rtype: datetime """ return datetime.datetime.now() def today(): """Current local date :return: Return the current local date :rtype: date """ return datetime.date.today() class LOServer(object): HOST = 'localhost' PORT = '8100' ARG = f'socket,host={HOST},port={PORT};urp;StarOffice.ComponentContext' CMD = ['soffice', '-env:SingleAppInstance=false', '-env:UserInstallation=file:///tmp/LO_Process8100', '--headless', '--norestore', '--invisible', f'--accept={ARG}'] def __init__(self): self._server = None self._ctx = None self._sm = None self._start_server() self._init_values() def _init_values(self): global CTX global SM if not self.is_running: return ctx = uno.getComponentContext() service = 'com.sun.star.bridge.UnoUrlResolver' resolver = ctx.ServiceManager.createInstanceWithContext(service, ctx) self._ctx = resolver.resolve('uno:{}'.format(self.ARG)) self._sm = self._ctx.getServiceManager() CTX = self._ctx SM = self._sm return @property def is_running(self): try: s = socket.create_connection((self.HOST, self.PORT), 5.0) s.close() debug('LibreOffice is running...') return True except ConnectionRefusedError: return False def _start_server(self): if self.is_running: return for i in range(3): self._server = subprocess.Popen(self.CMD, stdout=subprocess.PIPE, stderr=subprocess.PIPE) time.sleep(3) if self.is_running: break return def stop(self): if self._server is None: print('Search pgrep soffice') else: self._server.terminate() debug('LibreOffice is stop...') return def create_instance(self, name, with_context=True): if with_context: instance = self._sm.createInstanceWithContext(name, self._ctx) else: instance = self._sm.createInstance(name) return instance