Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
111 changes: 85 additions & 26 deletions ou_dedetai/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,24 +340,36 @@ 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
):
contents = f"""[Desktop Entry]
Name={app_name}
GenericName={generic_name}
Comment={comment}
Type=Application
Exec={exec_cmd}
Icon={icon_path}
Terminal=false
Type=Application
StartupWMClass={wm_class}
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 [])}
"""
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 +400,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 +415,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 +429,70 @@ 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:
# XXX: handle this case - it shouldn't happen as this should have been installed by now
raise NotImplementedError

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}' '%u'",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing shows the backslashes in the app.conf.logos_exe_window_path need an extra backslash, otherwise the command string is broken on running xdg-open.

icon_path=app_icon_path,
mime_types=["x-scheme-handler/logos4","x-scheme-handler/libronixdls"]
)

# XXX: consider what happens if this command fails.
# 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
# ```
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"
])

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