Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions io.github.FaithlifeCommunity.OuDedetai.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ modules:
- type: file
path: ./snap/gui/verbum.png
dest-filename: verbum.png
# FIXME: Also support logos4 and libronixdls URL Schemes
- type: inline
dest-filename: oudedetai.desktop
contents: |
Expand Down
53 changes: 37 additions & 16 deletions ou_dedetai/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,9 @@ def get_logos_user_id(
logos_appdata_dir: str
) -> Optional[str]:
logos_data_path = Path(logos_appdata_dir) / "Data"
# If somehow the directory does not exist - like if the user removed it manually while trying to reset their data.
if not logos_data_path.exists():
return None
contents = os.listdir(logos_data_path)
children = [logos_data_path / child for child in contents]
file_children = [child for child in children if child.is_dir()]
Expand All @@ -523,7 +526,8 @@ class Config:
# prefix with app_ if it's ours (and otherwise not clear)
# prefix with wine_ if it's theirs
# suffix with _binary if it's a linux binary
# suffix with _exe if it's a windows binary
# suffix with _exe if it's a windows binary structured as a linux path
# suffix with _exe_windows_path if it's a windows binary structured as a windows path
# suffix with _path if it's a file path
# suffix with _file_name if it's a file's name (with extension)

Expand Down Expand Up @@ -1161,25 +1165,42 @@ def wine_user(self) -> Optional[str]:
return self._wine_user

@property
def logos_cef_exe(self) -> Optional[str]:
if self.wine_user is not None:
# This name is the same even in Verbum
return f'C:\\users\\{self.wine_user}\\AppData\\Local\\{self.faithlife_product}\\System\\LogosCEF.exe'
return None
def logos_appdata_windows_path(self) -> Optional[str]:
"""Path to the Logos appdata dir within windows.

Structured like: C:\\Users\\user\\AppData\\Local...
"""
if self.wine_user is None:
return None
# We don't want to prompt here.
if self._raw.faithlife_product is None:
return None
return f'C:\\users\\{self.wine_user}\\AppData\\Local\\{self._raw.faithlife_product}'

@property
def logos_indexer_exe(self) -> Optional[str]:
if self.wine_user is not None:
return (f'C:\\users\\{self.wine_user}\\AppData\\Local\\{self.faithlife_product}\\System\\'
f'{self.faithlife_product}Indexer.exe')
return None
def logos_exe_windows_path(self) -> Optional[str]:
if self.logos_appdata_windows_path is None or self._raw.faithlife_product is None:
return None
return f'{self.logos_appdata_windows_path}\\{self._raw.faithlife_product}.exe'

@property
def logos_login_exe(self) -> Optional[str]:
if self.wine_user is not None:
return (f'C:\\users\\{self.wine_user}\\AppData\\Local\\{self.faithlife_product}\\System\\'
f'{self.faithlife_product}.exe')
return None
def logos_cef_exe_windows_path(self) -> Optional[str]:
if self.logos_appdata_windows_path is None:
return None
# This name is the same even in Verbum
return f'{self.logos_appdata_windows_path}\\System\\LogosCEF.exe'

@property
def logos_indexer_exe_windows_path(self) -> Optional[str]:
if self.logos_appdata_windows_path is None or self._raw.faithlife_product is None:
return None
return f'{self.logos_appdata_windows_path}\\System\\{self._raw.faithlife_product}Indexer.exe'

@property
def logos_system_exe_windows_path(self) -> Optional[str]:
if self.logos_appdata_windows_path is None or self._raw.faithlife_product is None:
return None
return f'{self.logos_appdata_windows_path}\\System\\{self._raw.faithlife_product}.exe'

@property
def log_level(self) -> str | int:
Expand Down
118 changes: 91 additions & 27 deletions ou_dedetai/installer.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import logging
import os
import shutil
import subprocess
import sys
from pathlib import Path
from typing import Optional

from ou_dedetai import system
from ou_dedetai.app import App, UserExitedFromAsk
Expand Down Expand Up @@ -340,24 +342,37 @@ def create_wine_appimage_symlinks(app: App):
def create_desktop_file(
filename: str,
app_name: str,
generic_name: str,
comment: str,
exec_cmd: str,
icon_path: str | Path,
wm_class: str,
generic_name: str | None = None,
comment: str | None = None,
icon_path: str | Path | None = None,
wm_class: str | None = None,
additional_keywords: list[str] | None = None,
mime_types: list[str] | None = None,
terminal: Optional[bool] = None
):
contents = f"""[Desktop Entry]
Name={app_name}
GenericName={generic_name}
Comment={comment}
Exec={exec_cmd}
Icon={icon_path}
Terminal=false
Type=Application
StartupWMClass={wm_class}
Exec={exec_cmd}
Categories=Education;Spirituality;Languages;Literature;Maps;
Keywords=Logos;Verbum;FaithLife;Bible;Control;Christianity;Jesus;
Keywords=Logos;Verbum;FaithLife;Bible;Control;Christianity;Jesus;{";".join(additional_keywords or [])}
"""
contents += f"Terminal={"true" if terminal is True else "false"}\n"
if generic_name:
contents += f"GenericName={generic_name}\n"
if comment:
contents += f"Comment={comment}\n"
if icon_path:
contents += f"Icon={icon_path}\n"
if mime_types:
contents += f"MimeType={";".join(mime_types)}\n"

if wm_class:
contents += f"StartupWMClass={wm_class}\n"
else:
contents += "StartupNotify=false\n"

local_share = Path.home() / '.local' / 'share'
xdg_data_home = Path(os.getenv('XDG_DATA_HOME', local_share))
launcher_path = xdg_data_home / 'applications' / filename
Expand Down Expand Up @@ -388,7 +403,7 @@ def create_launcher_shortcuts(app: App):
app_icon_path = app_dir / app_icon_src.name

if constants.RUNMODE == 'binary':
lli_executable = f"{installdir}/{constants.BINARY_NAME}"
oudedetai_executable = f"{installdir}/{constants.BINARY_NAME}"
elif constants.RUNMODE == "source":
script = Path(sys.argv[0]).expanduser().resolve()
repo_dir = None
Expand All @@ -403,7 +418,7 @@ def create_launcher_shortcuts(app: App):
py_bin = next(repo_dir.glob('*/bin/python'))
if not py_bin.is_file():
app.exit("Could not locate python binary in virtual environment.")
lli_executable = f"env DIALOG=tk {py_bin} {script}"
oudedetai_executable = f"env DIALOG=tk {py_bin} {script}"
elif constants.RUNMODE in ["snap", "flatpak"]:
logging.info(f"Not creating launcher shortcuts, {constants.RUNMODE} already handles this")
return
Expand All @@ -417,23 +432,72 @@ def create_launcher_shortcuts(app: App):

# Create Logos/Verbum desktop file.
logos_path = create_desktop_file(
f"{flproduct}Bible.desktop",
f"{flproduct}",
"Bible",
"Runs Faithlife Bible Software via Wine (snap). Community supported.",
f"{lli_executable} --run-installed-app",
logos_icon_path,
f"{flproduct.lower()}.exe",
filename=f"{flproduct}Bible.desktop",
app_name=f"{flproduct}",
generic_name="Bible",
comment="Runs Faithlife Bible Software via Wine (snap). Community supported.",
exec_cmd=f"{oudedetai_executable} --run-installed-app",
icon_path=logos_icon_path,
wm_class=f"{flproduct.lower()}.exe",
additional_keywords=["Catholic"] if flproduct == "Verbum" else None
)
logging.debug(f"> File exists?: {logos_path}: {logos_path.is_file()}")
# Create Ou Dedetai desktop file.
app_path = create_desktop_file(
f"{constants.BINARY_NAME}.desktop",
constants.APP_NAME,
"FaithLife App Installer",
"Installs and manages either Logos or Verbum via wine. Community supported.",
lli_executable,
app_icon_path,
constants.BINARY_NAME,
filename=f"{constants.BINARY_NAME}.desktop",
app_name=constants.APP_NAME,
generic_name="FaithLife App Installer",
comment="Installs and manages either Logos or Verbum via wine. Community supported.",
exec_cmd=oudedetai_executable,
icon_path=app_icon_path,
wm_class=constants.BINARY_NAME,
)
logging.debug(f"> File exists?: {app_path}: {app_path.is_file()}")

# Register URL scheme handlers:
# logos4 - to facilitate Logos 40.1+ login OAuth flow
# libronixdls - allows opening of bible links from the browser

if not app.conf.logos_exe_windows_path:
logging.error("Failed to register MIME types with system due to missing wine exe path")
return

url_handler_desktop_filename = f"{flproduct}-url-handler.desktop"
# Create the desktop file to register the MIME types.
app_path = create_desktop_file(
filename=url_handler_desktop_filename,
app_name=f"{flproduct} URL Handler",
comment="Handles logos4: and libronixdls: URL Schemes",
exec_cmd=f"{oudedetai_executable} --wine '{app.conf.logos_exe_windows_path.replace('\\','\\\\')}' '%u'",
icon_path=app_icon_path,
mime_types=["x-scheme-handler/logos4","x-scheme-handler/libronixdls"],
terminal=True
)
# For most users Logos will be "installed" at this point, if we fail here there is no easy
# way in the current flow to re-apply these - and this isn't required for Logos to function,
# more of a nice to have. While it would be nice for support reasons not to branch here -
# which is more important: a passing exit code doing what we could to setup Logos, or
# everything that we do - even that which is optional such as this - passes?

# On most systems these commands have the effect of adding the following to ~/.config/mimetypes:
# ```
# [Default Applications]
# x-scheme-handler/logos4=logos4.desktop
# x-scheme-handler/libronixdls=libronixdls.desktop
# ```
try:
system.run_command([
"xdg-mime",
"default",
url_handler_desktop_filename,
"x-scheme-handler/logos4"
])
system.run_command([
"xdg-mime",
"default",
url_handler_desktop_filename,
"x-scheme-handler/libronixdls"
])
except subprocess.CalledProcessError:
logging.exception("Failed to register MIME types with system")

41 changes: 18 additions & 23 deletions ou_dedetai/logos.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ def __init__(self, app: App):
"""These are processes we discovered already running"""

def monitor_indexing(self):
if self.app.conf.logos_indexer_exe in self.existing_processes:
indexer = self.existing_processes.get(self.app.conf.logos_indexer_exe)
if self.app.conf.logos_indexer_exe_windows_path in self.existing_processes:
indexer = self.existing_processes.get(self.app.conf.logos_indexer_exe_windows_path)
if indexer and isinstance(indexer[0], psutil.Process) and indexer[0].is_running():
self.indexing_state = State.RUNNING
else:
Expand All @@ -47,10 +47,10 @@ def monitor_logos(self):
cef = []
if self.app.conf.logos_exe:
splash = self.existing_processes.get(self.app.conf.logos_exe, [])
if self.app.conf.logos_login_exe:
login = self.existing_processes.get(self.app.conf.logos_login_exe, [])
if self.app.conf.logos_cef_exe:
cef = self.existing_processes.get(self.app.conf.logos_cef_exe, [])
if self.app.conf.logos_system_exe_windows_path:
login = self.existing_processes.get(self.app.conf.logos_system_exe_windows_path, [])
if self.app.conf.logos_cef_exe_windows_path:
cef = self.existing_processes.get(self.app.conf.logos_cef_exe_windows_path, [])

splash_running = splash[0].is_running() if splash else False
login_running = login[0].is_running() if login else False
Expand Down Expand Up @@ -78,19 +78,14 @@ def get_logos_pids(self):
app = self.app
# FIXME: consider refactoring to make one call to get a system pids
# Currently this gets all system pids 4 times
if app.conf.logos_exe:
self.existing_processes[app.conf.logos_exe] = system.get_pids(app.conf.logos_exe)
if app.conf.wine_user:
# Also look for the system's Logos.exe (this may be the login window)
logos_system_exe = (
f"C:\\users\\{app.conf.wine_user}\\AppData\\Local\\{app.conf.faithlife_product}\\System\\"
f"{app.conf.faithlife_product}.exe"
)
self.existing_processes[logos_system_exe] = system.get_pids(logos_system_exe)
if app.conf.logos_indexer_exe:
self.existing_processes[app.conf.logos_indexer_exe] = system.get_pids(app.conf.logos_indexer_exe)
if app.conf.logos_cef_exe:
self.existing_processes[app.conf.logos_cef_exe] = system.get_pids(app.conf.logos_cef_exe)
for exe_path in [
app.conf.logos_exe,
app.conf.logos_system_exe_windows_path,
app.conf.logos_indexer_exe_windows_path,
app.conf.logos_cef_exe_windows_path
]:
if exe_path:
self.existing_processes[exe_path] = system.get_pids(exe_path)

def monitor(self):
if self.app.is_installed():
Expand Down Expand Up @@ -257,15 +252,15 @@ def index(self):
index_finished = threading.Event()

def run_indexing():
if not self.app.conf.logos_indexer_exe:
if not self.app.conf.logos_indexer_exe_windows_path:
raise ValueError("Cannot find installed indexer")
process = wine.run_wine_application(
app=self.app,
wine_binary=self.app.conf.wine_binary,
exe=self.app.conf.logos_indexer_exe
exe=self.app.conf.logos_indexer_exe_windows_path
)
if process is not None:
self.processes[self.app.conf.logos_indexer_exe] = process
self.processes[self.app.conf.logos_indexer_exe_windows_path] = process

def check_if_indexing(process: threading.Thread):
start_time = time.time()
Expand Down Expand Up @@ -305,7 +300,7 @@ def stop_indexing(self):
self.indexing_state = State.STOPPING
if self.app:
pids = []
for process_name in [self.app.conf.logos_indexer_exe]:
for process_name in [self.app.conf.logos_indexer_exe_windows_path]:
if process_name is None:
continue
process = self.processes.get(process_name)
Expand Down
9 changes: 8 additions & 1 deletion ou_dedetai/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ def get_package_manager() -> PackageManager | None:
"libfuse2 " # appimages
"binutils wget winbind " # wine
"p7zip-full cabextract " # winetricks
"xdg-utils " # For xdg-mime needed for custom url scheme registration
)
# NOTE: Package names changed together for Ubuntu 24+, Debian 13+, and
# derivatives. This does not include an exhaustive list of distros that
Expand Down Expand Up @@ -459,6 +460,7 @@ def get_package_manager() -> PackageManager | None:
"fuse fuse-libs " # appimages
"mod_auth_ntlm_winbind samba-winbind samba-winbind-clients " # wine
"cabextract " # winetricks
"xdg-utils " # For xdg-mime needed for custom url scheme registration
)
incompatible_packages = "" # appimagelauncher handled separately
elif shutil.which('zypper') is not None: # OpenSUSE
Expand All @@ -473,6 +475,7 @@ def get_package_manager() -> PackageManager | None:
"samba wget " # wine
"curl gawk grep " # other
"7zip cabextract " # winetricks
"xdg-utils " # For xdg-mime needed for custom url scheme registration
)
incompatible_packages = "" # appimagelauncher handled separately
elif shutil.which('apk') is not None: # alpine
Expand All @@ -488,6 +491,7 @@ def get_package_manager() -> PackageManager | None:
"wget curl " # network
"7zip cabextract " # winetricks
"samba sed grep gawk bash bash-completion " # other
"xdg-utils " # For xdg-mime needed for custom url scheme registration
)
incompatible_packages = "" # appimagelauncher handled separately
elif shutil.which('pamac') is not None: # manjaro
Expand All @@ -501,6 +505,7 @@ def get_package_manager() -> PackageManager | None:
"samba wget " # wine
"curl gawk grep " # other
"7zip cabextract " # winetricks (7zip used to be called p7zip)
"xdg-utils " # For xdg-mime needed for custom url scheme registration
)
incompatible_packages = "" # appimagelauncher handled separately
elif shutil.which('pacman') is not None: # arch, steamOS
Expand All @@ -520,7 +525,8 @@ def get_package_manager() -> PackageManager | None:
"lib32-alsa-plugins alsa-lib lib32-alsa-lib libjpeg-turbo lib32-libjpeg-turbo sqlite lib32-sqlite "
"libxcomposite lib32-libxcomposite libxinerama lib32-libgcrypt libgcrypt lib32-libxinerama ncurses "
"lib32-ncurses ocl-icd lib32-ocl-icd libxslt lib32-libxslt libva lib32-libva gtk3 lib32-gtk3 "
"gst-plugins-base-libs lib32-gst-plugins-base-libs vulkan-icd-loader lib32-vulkan-icd-loader"
"gst-plugins-base-libs lib32-gst-plugins-base-libs vulkan-icd-loader lib32-vulkan-icd-loader "
"xdg-utils " # For xdg-mime needed for custom url scheme registration
)
else: # arch
packages = (
Expand All @@ -532,6 +538,7 @@ def get_package_manager() -> PackageManager | None:
"alsa-plugins gst-plugins-base-libs libpulse openal " # audio
"libva mpg123 v4l-utils " # video
"libxslt sqlite " # misc
"xdg-utils " # For xdg-mime needed for custom url scheme registration
)
incompatible_packages = "" # appimagelauncher handled separately
elif os_name == "org.freedesktop.platform":
Expand Down
2 changes: 2 additions & 0 deletions snap/snapcraft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ environment:
# https://forum.snapcraft.io/t/libpxbackend-1-0-so-cannot-open-shared-object-file-no-such-file-or-directory/44263/2
LD_LIBRARY_PATH: $SNAP/usr/lib:$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR

# FIXME: Also support logos4 and libronixdls URL Schemes by adding a new .desktop file with MimeType. It may just work from there.

apps:
oudedetai:
extensions: [gnome]
Expand Down