2022-02-21 23:43:58 -06:00
|
|
|
#!/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 <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
2022-02-23 22:49:43 -06:00
|
|
|
import datetime
|
2022-02-21 23:43:58 -06:00
|
|
|
import getpass
|
|
|
|
import logging
|
|
|
|
import os
|
|
|
|
import platform
|
|
|
|
import socket
|
|
|
|
import subprocess
|
|
|
|
import sys
|
|
|
|
import time
|
2022-02-23 22:49:43 -06:00
|
|
|
import traceback
|
2022-02-21 23:43:58 -06:00
|
|
|
|
2022-02-23 22:49:43 -06:00
|
|
|
from functools import wraps
|
|
|
|
from pprint import pprint
|
2022-02-21 23:43:58 -06:00
|
|
|
from typing import Any
|
|
|
|
|
|
|
|
import uno
|
2022-02-23 22:49:43 -06:00
|
|
|
from com.sun.star.awt import MessageBoxButtons as MSG_BUTTONS
|
|
|
|
from com.sun.star.awt.MessageBoxResults import YES
|
2022-02-21 23:43:58 -06:00
|
|
|
from com.sun.star.beans import PropertyValue
|
|
|
|
|
2022-02-24 23:59:04 -06:00
|
|
|
|
2022-02-21 23:43:58 -06:00
|
|
|
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'
|
2022-02-23 22:49:43 -06:00
|
|
|
|
|
|
|
|
2022-02-21 23:43:58 -06:00
|
|
|
_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()
|
|
|
|
|
|
|
|
|
2022-02-24 23:59:04 -06:00
|
|
|
# UNO Enum
|
|
|
|
class MessageBoxType():
|
|
|
|
from com.sun.star.awt.MessageBoxType \
|
|
|
|
import MESSAGEBOX, INFOBOX, WARNINGBOX, ERRORBOX, QUERYBOX
|
|
|
|
MBT = MessageBoxType
|
|
|
|
|
2022-02-23 22:49:43 -06:00
|
|
|
|
2022-02-24 23:59:04 -06:00
|
|
|
def debug(*messages) -> None:
|
|
|
|
"""Show messages debug
|
|
|
|
|
|
|
|
:param messages: List of messages to debug
|
|
|
|
:type messages: list[Any]
|
2022-02-23 22:49:43 -06:00
|
|
|
"""
|
|
|
|
|
2022-02-24 23:59:04 -06:00
|
|
|
data = [str(m) for m in messages]
|
2022-02-21 23:43:58 -06:00
|
|
|
log.debug('\t'.join(data))
|
|
|
|
return
|
|
|
|
|
|
|
|
|
2022-02-23 22:49:43 -06:00
|
|
|
def error(message: Any) -> None:
|
2022-02-24 23:59:04 -06:00
|
|
|
"""Show message error
|
2022-02-23 22:49:43 -06:00
|
|
|
|
2022-02-24 23:59:04 -06:00
|
|
|
:param message: The message error
|
|
|
|
:type message: Any
|
2022-02-23 22:49:43 -06:00
|
|
|
"""
|
|
|
|
|
|
|
|
log.error(message)
|
|
|
|
return
|
|
|
|
|
|
|
|
|
2022-02-24 23:59:04 -06:00
|
|
|
def info(*messages) -> None:
|
|
|
|
"""Show messages info
|
2022-02-23 22:49:43 -06:00
|
|
|
|
2022-02-24 23:59:04 -06:00
|
|
|
:param messages: List of messages to debug
|
|
|
|
:type messages: list[Any]
|
2022-02-23 22:49:43 -06:00
|
|
|
"""
|
|
|
|
|
2022-02-24 23:59:04 -06:00
|
|
|
data = [str(m) for m in messages]
|
2022-02-23 22:49:43 -06:00
|
|
|
log.info('\t'.join(data))
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
def save_log(path: str, data: Any) -> None:
|
2022-02-24 23:59:04 -06:00
|
|
|
"""Save data in file, data append to end and automatic add current time.
|
2022-02-23 22:49:43 -06:00
|
|
|
|
2022-02-24 23:59:04 -06:00
|
|
|
:param path: Path to save log
|
|
|
|
:type path: str
|
|
|
|
:param data: Data to save in file log
|
|
|
|
:type data: Any
|
2022-02-23 22:49:43 -06:00
|
|
|
"""
|
|
|
|
|
|
|
|
with open(path, 'a') as f:
|
|
|
|
f.write(f'{str(now())[:19]} - ')
|
|
|
|
pprint(data, stream=f)
|
2022-02-21 23:43:58 -06:00
|
|
|
return
|
|
|
|
|
|
|
|
|
2022-02-24 23:59:04 -06:00
|
|
|
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
|
|
|
|
"""
|
|
|
|
|
2022-02-21 23:43:58 -06:00
|
|
|
if with_context:
|
|
|
|
instance = SM.createInstanceWithContext(name, CTX)
|
2022-02-24 23:59:04 -06:00
|
|
|
elif argument:
|
|
|
|
instance = SM.createInstanceWithArguments(name, (argument,))
|
2022-02-21 23:43:58 -06:00
|
|
|
else:
|
|
|
|
instance = SM.createInstance(name)
|
2022-02-24 23:59:04 -06:00
|
|
|
|
2022-02-21 23:43:58 -06:00
|
|
|
return instance
|
|
|
|
|
|
|
|
|
2022-02-24 23:59:04 -06:00
|
|
|
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 <https://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1configuration_1_1ConfigurationProvider.html>`_
|
|
|
|
"""
|
|
|
|
|
2022-02-21 23:43:58 -06:00
|
|
|
name = 'com.sun.star.configuration.ConfigurationProvider'
|
|
|
|
service = 'com.sun.star.configuration.ConfigurationAccess'
|
|
|
|
cp = create_instance(name, True)
|
|
|
|
node = PropertyValue(Name='nodepath', Value=node_name)
|
2022-02-24 23:59:04 -06:00
|
|
|
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,))
|
|
|
|
|
2022-02-21 23:43:58 -06:00
|
|
|
try:
|
2022-02-24 23:59:04 -06:00
|
|
|
current_value = update.getPropertyValue(key)
|
|
|
|
update.setPropertyValue(key, new_value)
|
|
|
|
update.commitChanges()
|
2022-02-21 23:43:58 -06:00
|
|
|
except Exception as e:
|
|
|
|
error(e)
|
2022-02-24 23:59:04 -06:00
|
|
|
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)
|
2022-02-21 23:43:58 -06:00
|
|
|
|
|
|
|
|
|
|
|
# Get info LibO
|
2022-02-23 22:49:43 -06:00
|
|
|
NAME = TITLE = get_app_config('org.openoffice.Setup/Product', 'ooName')
|
2022-02-21 23:43:58 -06:00
|
|
|
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}"
|
|
|
|
|
2022-02-23 22:49:43 -06:00
|
|
|
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()
|
|
|
|
|
|
|
|
|
2022-02-24 23:59:04 -06:00
|
|
|
def mri(obj: Any) -> None:
|
|
|
|
"""Inspect object with MRI Extension
|
2022-02-23 22:49:43 -06:00
|
|
|
|
2022-02-24 23:59:04 -06:00
|
|
|
:param obj: Any pyUno object
|
|
|
|
:type obj: Any
|
2022-02-23 22:49:43 -06:00
|
|
|
|
2022-02-24 23:59:04 -06:00
|
|
|
`See MRI <https://github.com/hanya/MRI/releases>`_
|
2022-02-23 22:49:43 -06:00
|
|
|
"""
|
2022-02-24 23:59:04 -06:00
|
|
|
|
|
|
|
mri = create_instance('mytools.Mri')
|
2022-02-23 22:49:43 -06:00
|
|
|
if m is None:
|
|
|
|
msg = 'Extension MRI not found'
|
|
|
|
error(msg)
|
|
|
|
return
|
|
|
|
|
|
|
|
if hasattr(obj, 'obj'):
|
|
|
|
obj = obj.obj
|
2022-02-24 23:59:04 -06:00
|
|
|
mri.inspect(obj)
|
2022-02-23 22:49:43 -06:00
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
def catch_exception(f):
|
2022-02-24 23:59:04 -06:00
|
|
|
"""Catch exception for any function
|
|
|
|
|
|
|
|
:param f: Any Python function
|
|
|
|
:type f: Function instance
|
|
|
|
"""
|
|
|
|
|
2022-02-23 22:49:43 -06:00
|
|
|
@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
|
|
|
|
|
2022-02-21 23:43:58 -06:00
|
|
|
|
2022-02-24 23:59:04 -06:00
|
|
|
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 <https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1awt_1_1MessageBoxButtons.html>`_
|
|
|
|
:type buttons: long
|
|
|
|
:param type_message_box: The `message box type <https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1awt.html#ad249d76933bdf54c35f4eaf51a5b7965>`_
|
|
|
|
:type type_message_box: enum
|
|
|
|
:return: Instance MessageBox
|
|
|
|
:rtype: pyUno
|
|
|
|
|
|
|
|
`See Api XMessageBoxFactory <http://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1awt_1_1XMessageBoxFactory.html>`_
|
|
|
|
"""
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
2022-02-21 23:43:58 -06:00
|
|
|
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
|