Skip to content

Commit

Permalink
Call disclaim from cli entry point
Browse files Browse the repository at this point in the history
  • Loading branch information
RhetTbull committed Jul 9, 2024
1 parent f5adecb commit 9333cf3
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 13 deletions.
4 changes: 2 additions & 2 deletions osxphotos.spec
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ block_cipher = None
a = Analysis(
["cli.py"],
pathex=[pathex],
binaries=[("build/libdisclaim.dylib", ".")],
binaries=[("build/libdisclaim.dylib", "osxphotos")],
datas=datas,
hiddenimports=["pkg_resources.py2_warn"],
hookspath=[],
runtime_hooks=["disclaim.py"],
# runtime_hooks=["disclaim.py"],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
Expand Down
6 changes: 6 additions & 0 deletions osxphotos/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from osxphotos._constants import PROFILE_SORT_KEYS
from osxphotos._version import __version__
from osxphotos.disclaim import disclaim, pyinstaller, pyapp
from osxphotos.platform import is_macos

from .about import about
Expand Down Expand Up @@ -106,6 +107,11 @@ def cli_main(ctx, profile, profile_sort, **kwargs):
# the debug options are handled in cli/__init__.py
# before this function is called
ctx.obj = CLI_Obj(group=cli_main)

if pyinstaller() or pyapp():
# Running from executable, run disclaimer
disclaim()

if profile:
click.echo("Profiling...")
profile_sort = profile_sort or ["cumulative"]
Expand Down
22 changes: 11 additions & 11 deletions osxphotos/cli/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
from osxphotos.path_utils import is_valid_filepath, sanitize_filename, sanitize_filepath
from osxphotos.photoexporter import PhotoExporter
from osxphotos.photoinfo import PhotoInfoNone
from osxphotos.photokit_utils import wait_for_photokit_authorization
from osxphotos.photoquery import load_uuid_from_file, query_options_from_kwargs
from osxphotos.phototemplate import PhotoTemplate, RenderOptions
from osxphotos.platform import get_macos_version, is_macos
Expand Down Expand Up @@ -1684,17 +1685,16 @@ def export_cli(
else:
report_writer = ReportWriterNoOp()

# if use_photokit and not check_photokit_authorization():
# click.echo(
# "Requesting access to use your Photos library. Click 'OK' on the dialog box to grant access."
# )
# request_photokit_authorization()
# click.confirm("Have you granted access?")
# if not check_photokit_authorization():
# click.echo(
# "Failed to get access to the Photos library which is needed with `--use-photokit`."
# )
# return
if (use_photokit or use_photos_export) and not check_photokit_authorization():
click.echo(
"Requesting access to use your Photos library. "
"Click 'Allow Access to All Photos' in the dialog box to grant access."
)
if not wait_for_photokit_authorization():
rich_click_echo(
f"[error]Error: could not get authorization to access Photos library.[/]"
)
return 1

# initialize export flags
# by default, will export all versions of photos unless skip flag is set
Expand Down
56 changes: 56 additions & 0 deletions osxphotos/disclaim.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
""" Disclaim the application when running on macOS so that permission requests come from the application itself
instead of the terminal.
To use this, the libdisclaim.dylib library must be built and placed in the same directory as this file
or provided as an argument to the disclaim function.
Reference: http://qt.io/blog/the-curious-case-of-the-responsible-process
"""

import ctypes
import os
import sys


def pyinstaller() -> bool:
"""Return True if the application is running from a PyInstaller bundle."""
# PyInstaller bootloader sets a flag
return hasattr(sys, "_MEIPASS")


def pyapp() -> bool:
"""Check if we are running in a pyapp environment."""
return os.environ.get("PYAPP") == "1"


def disclaim(library_path: str | None = None):
"""Run this function to disclaim the application and set the responsible process to the caller.
Args:
library_path: The path to the libdisclaim.dylib library.
If not provided, libdisclaim.dylib will be loaded from the the same directory as this file.
"""

if sys.platform != "darwin":
return

# Avoid redundant disclaims
env_marker = f"PY_DISCLAIMED-{sys.argv[0]}"
if os.environ.get(env_marker):
return
os.environ[env_marker] = "1"

if pyinstaller():
# If running from pyinstaller, the _MEIPASS2 environment variable is set
# The bootloader has cleared the _MEIPASS2 environment variable by the
# time we get here, which means re-launching the executable disclaimed
# will unpack the binary again. To avoid this we reset _MEIPASS2 again,
# so that our re-launch will pick up at second stage of the bootstrap.
os.environ["_MEIPASS2"] = sys._MEIPASS

# Load the disclaim library and call the disclaim function
library_path = library_path or os.path.join(
os.path.dirname(__file__), "libdisclaim.dylib"
)
libdisclaim = ctypes.cdll.LoadLibrary(library_path)
libdisclaim.disclaim()
29 changes: 29 additions & 0 deletions osxphotos/photokit_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
""" Utilities for working with the Photokit framework on macOS """

import time

from osxphotos.platform import is_macos

if is_macos:
from osxphotos.photokit import (
check_photokit_authorization,
request_photokit_authorization,
)

# seconds to wait for user to grant authorization
WAIT_FOR_AUTHORIZATION_TIMEOUT = 10

# seconds to sleep between authorization check
AUTHORIZATION_SLEEP = 0.25

def wait_for_photokit_authorization() -> bool:
"""Request and wait for authorization to access Photos library."""
if check_photokit_authorization():
return True
start_time = time.time()
request_photokit_authorization()
while not check_photokit_authorization():
time.sleep(AUTHORIZATION_SLEEP)
if time.time() > start_time + WAIT_FOR_AUTHORIZATION_TIMEOUT:
return False
return True

0 comments on commit 9333cf3

Please sign in to comment.