From cd22bfaa51a519df9cd06fe50254ea57492cbabd Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Sun, 13 Sep 2020 14:49:02 +0000 Subject: [PATCH] Started work on support for powerpoint on mac. Changed the script execution approach from osascript to py-applescript. Not tested. At all. --- appveyor.yml | 2 +- openlp/core/common/settings.py | 2 + .../lib/applescriptbasecontroller.py | 310 ++++++++++++++++ .../presentations/lib/keynotecontroller.py | 307 ++++++++++++++++ .../lib/powerpointmaccontroller.py | 347 ++++++++++++++++++ .../presentations/presentationplugin.py | 4 + 6 files changed, 971 insertions(+), 1 deletion(-) create mode 100644 openlp/plugins/presentations/lib/applescriptbasecontroller.py create mode 100644 openlp/plugins/presentations/lib/keynotecontroller.py create mode 100644 openlp/plugins/presentations/lib/powerpointmaccontroller.py diff --git a/appveyor.yml b/appveyor.yml index ed622df04..d54bd9ff6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ install: # Install Windows only dependencies - cmd: python -m pip install pyodbc pypiwin32 # Mac only dependencies - - sh: python -m pip install pyobjc-core pyobjc-framework-Cocoa + - sh: python -m pip install pyobjc-core pyobjc-framework-Cocoa py-applescript build: off diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index b167ed3ab..ce84416cf 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -286,6 +286,8 @@ class Settings(QtCore.QSettings): 'presentations/Impress': QtCore.Qt.Checked, 'presentations/Powerpoint': QtCore.Qt.Checked, 'presentations/Pdf': QtCore.Qt.Checked, + 'presentations/Keynote': QtCore.Qt.Checked, + 'presentations/PowerPointMac': QtCore.Qt.Checked, 'presentations/presentations files': [], 'presentations/thumbnail_scheme': '', 'presentations/powerpoint slide click advance': QtCore.Qt.Unchecked, diff --git a/openlp/plugins/presentations/lib/applescriptbasecontroller.py b/openlp/plugins/presentations/lib/applescriptbasecontroller.py new file mode 100644 index 000000000..3b2ec3fa7 --- /dev/null +++ b/openlp/plugins/presentations/lib/applescriptbasecontroller.py @@ -0,0 +1,310 @@ +# -*- coding: utf-8 -*- + +########################################################################## +# OpenLP - Open Source Lyrics Projection # +# ---------------------------------------------------------------------- # +# Copyright (c) 2008-2020 OpenLP Developers # +# ---------------------------------------------------------------------- # +# This program 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. # +# # +# This program 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 this program. If not, see . # +########################################################################## +""" +This module is a base to be used for Mac OS X presentation modules using applescript +to control presentation application, such as keynote and powerpoint for mac +""" +import logging +import applescript + +from openlp.core.common import is_macosx +from openlp.core.display.screens import ScreenList +from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument + + +log = logging.getLogger(__name__) + +DEFAULT_APPLESCRIPT = """ +on check_available() + return false +end check_available +""" + + +class AppleScriptBaseController(PresentationController): + """ + Class to control interactions with a Presentation program like Keynote or Powerpoint. + It uses py-applescript to compile and execute applescript to Load and Close the Presentation. + As well as triggering the correct activities based on the users input. + """ + log.info('ApplescriptController loaded') + + base_class = True + + def __init__(self, plugin, name, doc): + """ + Initialise the class + """ + super(AppleScriptBaseController, self).__init__(plugin, name, doc) + # Script expected to be overwritten by subclasses + self.applescript = applescript.AppleScript(DEFAULT_APPLESCRIPT) + + def check_available(self): + """ + Program is able to run on this machine. + """ + log.debug('check_available') + if not is_macosx(): + return False + try: + return self.applescript.call('check_available') + except Exception: + log.exception('script execution failed') + return False + + def start_process(self): + """ + Loads the presentation application + """ + log.debug('start_process') + try: + self.applescript.call('start_application') + except Exception: + log.exception('script execution failed') + + def kill(self): + """ + Called at system exit to clean up any running presentations. + """ + log.debug('Kill presentation program') + while self.docs: + self.docs[0].close_presentation() + try: + self.applescript.call('stop_application') + except Exception: + log.exception('script execution failed') + + +class AppleScriptBaseDocument(PresentationDocument): + """ + Class which holds information and controls a single presentation. + """ + + def __init__(self, controller, document_path): + """ + Constructor, store information about the file and initialise. + + :param controller: + :param pathlib.Path document_path: Path to the document to load + :rtype: None + """ + log.debug('Init Presentation Applescript') + super().__init__(controller, document_path) + self.presentation_loaded = False + self.is_blanked = False + self.slide_count = 0 + + def load_presentation(self): + """ + Called when a presentation is added to the SlideController. Opens the presentation file. + """ + log.debug('load_presentation') + # check if there already exists thumbnails etc. If so skip loading the presentation. + files = self.get_thumbnail_folder().glob('*') + if len(list(files)) > 0: + self.presentation_loaded = True + return self.presentation_loaded + try: + self.controller.applescript.call('create_thumbs', str(self.file_path), str(self.get_thumbnail_folder())) + self.presentation_loaded = True + except Exception: + log.exception('script execution failed') + self.presentation_loaded = False + return self.presentation_loaded + + def create_thumbnails(self): + """ + Create the thumbnail images for the current presentation. + """ + log.debug('create_thumbnails') + # No need to do anything, already created thumbs in load_presentation + + def close_presentation(self): + """ + Close presentation and clean up objects. This is triggered by a new object being added to SlideController or + OpenLP being shut down. + """ + log.debug('close_presentation') + if self.presentation_loaded: + try: + self.controller.applescript.call('close_presentation') + except Exception: + log.exception('script execution failed') + self.presentation_loaded = False + self.controller.remove_doc(self) + + def is_loaded(self): + """ + Returns ``True`` if a presentation is loaded. + """ + self.presentation_loaded = False + log.debug('is_loaded') + try: + self.presentation_loaded = self.controller.applescript.call('is_loaded', str(self.file_path)) + except Exception: + log.exception('script execution failed') + return self.presentation_loaded + + def is_active(self): + """ + Returns ``True`` if a presentation is currently active. + """ + log.debug('is_active') + if not self.is_loaded(): + return False + try: + return self.controller.applescript.call('is_active') + except Exception: + log.exception('script execution failed') + return False + + def unblank_screen(self): + """ + Unblanks (restores) the presentation. + """ + log.debug('unblank_screen') + try: + self.controller.applescript.call('unblank') + except Exception: + log.exception('script execution failed') + self.is_blanked = False + + def blank_screen(self): + """ + Blanks the screen. + """ + log.debug('blank_screen') + try: + self.controller.applescript.call('blank') + except Exception: + log.exception('script execution failed') + + def is_blank(self): + """ + Returns ``True`` if screen is blank. + """ + log.debug('is_blank') + return self.is_blanked + + def stop_presentation(self): + """ + Stops the current presentation and hides the output. Used when blanking to desktop. + """ + try: + self.controller.applescript.call('stop_presentation') + except Exception: + log.exception('script execution failed') + + def start_presentation(self): + """ + Starts a presentation from the beginning. + """ + log.debug('start_presentation') + try: + fullscreen = (ScreenList().current.custom_geometry is None) + size = ScreenList().current.display_geometry + self.controller.applescript.call('start_presentation', str(self.file_path), size.top(), size.left(), + size.width(), size.height(), fullscreen) + except Exception: + log.exception('script execution failed') + + def get_slide_number(self): + """ + Returns the current slide number. + """ + log.debug('get_slide_number') + ret = 0 + try: + ret = int(self.controller.applescript.call('get_current_slide')) + except Exception: + log.exception('script execution failed') + return ret + + def get_slide_count(self): + """ + Returns total number of slides. + """ + log.debug('get_slide_count') + return self.slide_count + + def goto_slide(self, slide_no): + """ + Moves to a specific slide in the presentation. + + :param slide_no: The slide the text is required for, starting at 1 + """ + log.debug('goto_slide') + try: + self.controller.applescript.call('goto_slide', slide_no) + except Exception: + log.exception('script execution failed') + + def next_step(self): + """ + Triggers the next effect of slide on the running presentation. + """ + log.debug('next_step') + past_end = False + try: + self.controller.applescript.call('next_slide') + # TODO: detect past end + except Exception: + log.exception('script execution failed') + return past_end + + def previous_step(self): + """ + Triggers the previous slide on the running presentation. + """ + log.debug('previous_step') + before_start = False + try: + self.controller.applescript.call('previous_slide') + # TODO: detect before start + except Exception: + log.exception('script execution failed') + return before_start + + def get_slide_text(self, slide_no): + """ + Returns the text on the slide. + + :param slide_no: The slide the text is required for, starting at 1 + """ + return '' + + def get_slide_notes(self, slide_no): + """ + Returns the text on the slide. + + :param slide_no: The slide the text is required for, starting at 1 + """ + return '' + + def create_titles_and_notes(self): + """ + Writes the list of titles (one per slide) + to 'titles.txt' + and the notes to 'slideNotes[x].txt' + in the thumbnails directory + """ + # Already done in create thumbs + pass diff --git a/openlp/plugins/presentations/lib/keynotecontroller.py b/openlp/plugins/presentations/lib/keynotecontroller.py new file mode 100644 index 000000000..76b086396 --- /dev/null +++ b/openlp/plugins/presentations/lib/keynotecontroller.py @@ -0,0 +1,307 @@ +# -*- coding: utf-8 -*- + +########################################################################## +# OpenLP - Open Source Lyrics Projection # +# ---------------------------------------------------------------------- # +# Copyright (c) 2008-2020 OpenLP Developers # +# ---------------------------------------------------------------------- # +# This program 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. # +# # +# This program 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 this program. If not, see . # +########################################################################## +""" +This module is for controlling keynote. +""" +import logging +import shutil +import applescript + +from openlp.plugins.presentations.lib.applescriptbasecontroller import AppleScriptBaseController,\ + AppleScriptBaseDocument + + +log = logging.getLogger(__name__) + +KEYNOTE_APPLESCRIPT = """ +on check_available() + try + set appID to "com.apple.iWork.Keynote" + if exists application id appID then + return true + end if + return false + on error + return false + end try +end check_available + +on get_version() + return (version of application "Keynote") +end get_version + +on start_application() + tell application "Keynote" + launch + end tell +end start_application + +on close_presentation() + try + tell application "Keynote" + -- close presentation + close front document + end tell + end try +end close_presentation + +on create_thumbs(documentName, outputPath) + set outputPathHfs to POSIX file outputPath + tell application "Keynote" + if playing is true then tell the front document to stop + -- Open document + open documentName + -- hide keynote + tell application "System Events" + set visible of application process "Keynote" to false + end tell + -- Export slides to png + export front document as slide images to file outputPathHfs with properties ¬ + {image format:PNG, skipped slides:false} + -- Get the Presentation Notes and slide title + set the slideTitles to "" + tell the front document + repeat with i from 1 to the count of slides + tell slide i + set noteFilename to outputPath & "/slideNotes" & (slide number as string) & ".txt" + if skipped is false then + -- Save the notes to file + set noteText to presenter notes of it + set noteFile to open for access noteFilename with write permission + write noteText to noteFile + close access noteFile + -- if title is available, save it + if title showing is true then + set the slideTitle to object text of default title item + set the slideTitles to slideTitles & slideTitle & return + else + set the slideTitles to "slide " & (slide number as string) & return + end if + end if + end tell + end repeat + end tell + -- Save the titles to file + set titlesFilename to outputPath & "/titles.txt" + set titlesFile to open for access titlesFilename with write permission + write slideTitles to titlesFile + close access titlesFile + -- close presentation + close front document + end tell +end create_thumbs +--create_thumbs("/Users/tgc/Downloads/keynote-pres.key", "/Users/tgc/Downloads/tmp/") + +on start_presentation(documentName, new_top, new_left_pos, new_width, new_height, fullscreen) + tell application "Keynote" + if playing is true then tell the front document to stop + -- Open document + open documentName + --start front document from the first slide of front document + --activate + end tell + -- There seems to be no applescript command to start presentation in window mode + -- so we that by faking mouse clicks (requires accessability rights) + tell application "System Events" + tell process "Keynote" + click (menu bar item 10 of menu bar 1) + click menu item 2 of menu 1 of menu bar item 10 of menu bar 1 + end tell + end tell + tell application "Keynote" + -- set the size and postion on presentation window + if fullscreen is true then + -- move window to right screen + set bounds of front window to {new_top, new_left_pos, new_width, new_height} + -- put it in fullscreen mode + tell application "System Events" to tell process "Keynote" + set value of attribute "AXFullScreen" of front window to true + end tell + else + set bounds of front window to {new_top, new_left_pos, new_width, new_height} + end if + activate + end tell +end start_presentation +--start_presentation("Users:solva_a:Downloads:tomas:applescript:keynote-pres.key", 10, 10, 640, 480, false) + +on stop_presentation() + tell application "Keynote" + --activate + -- stop presenting + if playing is true then tell the front document to stop + -- close presentation + close front document + -- hide keynote + tell application "System Events" + set visible of application process "Keynote" to false + end tell + end tell +end stop_presentation + +on is_loaded(documentPath) + set documentOldStylePath to POSIX file documentPath as alias + tell application "Keynote" + return (file of front document as string is (documentOldStylePath as string)) + end tell +end is_loaded + +on is_active() + tell application "Keynote" + -- print(?) true/false playing state + return playing + end tell +end is_active + + +on next_slide() + tell application "Keynote" + --activate + if playing is false then start front document from the first slide of front document + show next + -- Print(?) slide number + slide number + end tell +end next_slide + +on previous_slide() + tell application "Keynote" + if playing is false then start front document from the first slide of front document + show previous + -- Print(?) slide number + slide number + end tell +end previous_slide + +on goto_slide(slideNumber) + tell application "Keynote" + if playing is false then + start front document from slide slideNumber of front document + else + set the current slide to the first slide whose slide number is slideNumber + end if + end tell +end goto_slide + +on get_current_slide() + tell application "Keynote" + -- Print(?) slide number + slide number + end tell +end get_current_slide + +on blank() + tell application "Keynote" + tell application "System Events" to keystroke "b" + end tell +end blank + +on unblank() + tell application "Keynote" + tell application "System Events" to keystroke "b" + end tell +end blank +""" + + +class KeynoteController(AppleScriptBaseController): + """ + Class to control interactions with Keynote. + As well as triggering the correct activities based on the users input. + """ + log.info('KeynoteController loaded') + base_class = False + + def __init__(self, plugin): + """ + Initialise the class + """ + log.debug('Initialising') + super(KeynoteController, self).__init__(plugin, 'Keynote', KeynoteDocument) + # Compiled script expected to be set by subclasses + try: + self.applescript = applescript.AppleScript(KEYNOTE_APPLESCRIPT) + except applescript.ScriptError: + log.exception('Compilation of Keynote applescript failed') + self.supports = ['key'] + self.also_supports = ['ppt', 'pps', 'pptx', 'ppsx', 'pptm'] + + def check_available(self): + """ + Program is able to run on this machine. + """ + ret = super().check_available() + if ret: + # Powerpoint is available! now check if the version is 10.1 or above + try: + version = self.applescript.call('get_version') + except Exception: + log.exception('script execution failed') + return False + try: + versions = version.split('.') + major_version = int(versions[0]) + if major_version == 10: + minor_version = int(versions[1]) + if minor_version >= 1: + return True + elif major_version > 10: + return True + except ValueError: + pass + return False + + +class KeynoteDocument(AppleScriptBaseDocument): + """ + Class which holds information and controls a single presentation. + """ + + def __init__(self, controller, document_path): + """ + Constructor, store information about the file and initialise. + + :param controller: + :param pathlib.Path document_path: Path to the document to load + :rtype: None + """ + log.debug('Init Presentation KeynoteDocument') + super().__init__(controller, document_path) + self.presentation_loaded = False + self.is_blank = False + + def load_presentation(self): + ret = super().load_presentation() + if ret: + # Keynote will name the thumbnails in the format: .001.png, we need to convert to slide1.png + old_images = list(self.get_thumbnail_folder().glob('*.png')) + # if the convertion was already done, don't do it again + if self.get_thumbnail_folder() / 'slide1.png' in old_images: + return True + self.slide_count = len(old_images) + for old_image in old_images: + # get the image name in format 001.png + number_name = old_image.name.split('.', 1)[1] + # get the image name in format 1.png + number_name = number_name.lstrip('0') + new_name = 'slide' + number_name + shutil.move(old_image, self.get_thumbnail_folder() / new_name) + return ret diff --git a/openlp/plugins/presentations/lib/powerpointmaccontroller.py b/openlp/plugins/presentations/lib/powerpointmaccontroller.py new file mode 100644 index 000000000..1662a7f32 --- /dev/null +++ b/openlp/plugins/presentations/lib/powerpointmaccontroller.py @@ -0,0 +1,347 @@ +# -*- coding: utf-8 -*- + +########################################################################## +# OpenLP - Open Source Lyrics Projection # +# ---------------------------------------------------------------------- # +# Copyright (c) 2008-2020 OpenLP Developers # +# ---------------------------------------------------------------------- # +# This program 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. # +# # +# This program 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 this program. If not, see . # +########################################################################## +""" +This module is for controlling keynote. +""" +import logging +import applescript + +try: + import fitz + PYMUPDF_AVAILABLE = True +except ImportError: + PYMUPDF_AVAILABLE = False + +from openlp.plugins.presentations.lib.applescriptbasecontroller import AppleScriptBaseController,\ + AppleScriptBaseDocument + + +log = logging.getLogger(__name__) + + +POWERPOINT_MAC_APPLESCRIPT = """ +on check_available() + set appID to "com.microsoft.PowerPoint" + set doesExist to false + try + tell application "Finder" to get application file id appID + set doesExist to true + end try + return doesExist +end check_available + +on get_version() + return (version of application "Microsoft PowerPoint") +end get_version + +on start_application() + tell application "Microsoft PowerPoint" + launch + -- Wait for powerpoint to start + tell application "System Events" + repeat while true + set PowerPointUp to (count of (every process whose name is "Microsoft PowerPoint")) > 0 + if PowerPointUp then + exit repeat + else + delay 0.2 + end if + end repeat + end tell + end tell +end start_application + +-- helper function to write text to files +on write_file(fileName, theData) + set fileDescriptor to open for access the fileName with write permission + write theData to fileDescriptor as «class utf8» + close access fileDescriptor +end write_file + +-- helper function to create empty file +on create_empty_file(f) + write_file(f, "") + return (POSIX path of f) as POSIX file +end create_empty_file + +on create_thumbs(documentName, outputPath) + tell application "Microsoft PowerPoint" + -- Open document + open documentName + -- wait for document to load + repeat until document window 1 exists + delay 0.2 + end repeat + -- Export slides to jpg + --save active presentation in outputPath as save as PNG + -- Export slides to pdf + save active presentation in my create_empty_file(outputPath & "/tmp.pdf") as save as PDF + -- Get the Presentation Notes and slide title + set the slideTitles to "" + set titleTypes to {placeholder type title placeholder, placeholder type center title placeholder, ¬ + placeholder type vertical title placeholder} + repeat with tSlide in (get slides of active presentation) + -- Only handle non-hidden slides + if hidden of slide show transition of tSlide is false then + --text = slide.Shapes.Title.TextFrame.TextRange.Text + -- Get the title of the current slide + repeat with currentShape in (get shapes of tSlide) + tell currentShape to if has text frame then tell its text frame to if has text then + set aType to placeholder type of currentShape + if aType is not missing value and aType is in titleTypes then + set slideTitles to slideTitles & (content of its text range) & return + exit repeat + end if + end if + end repeat + set the noteText to "" + repeat with t_shape in (get shapes of notes page of tSlide) + tell t_shape to if has text frame then tell its text frame to if has text then + -- get the note of this slide + set the noteText to (content of its text range) + exit repeat + end if + end repeat + -- Save note to file + set noteFilename to outputPath & "/slideNotes" & (slide index of tSlide as string) & ".txt" + my write_file(noteFilename, noteText) + end if + end repeat + -- Save the titles to file + set titlesFilename to outputPath & "/titles.txt" + my write_file(titlesFilename, slideTitles) + close active presentation + end tell +end create_thumbs + +on close_presentation() + tell application "Microsoft PowerPoint" + -- close presentation + close active presentation + -- Hide powerpoint + tell application "System Events" + set visible of application process "Microsoft PowerPoint" to false + end tell + end tell +end close_presentation + +on start_presentation(documentName, new_top, new_left_pos, new_width, new_height, fullscreen) + tell application "Microsoft PowerPoint" + -- Open document + open documentName + -- wait for document to load + repeat until document window 1 exists + delay 0.2 + end repeat + -- disable presenter console + set showpresenterview of slide show settings of active presentation to 0 + -- set presentation to show presentation in window + set show type of slide show settings of active presentation to slide show type window + -- start presentation + run slide show slide show settings of active presentation + -- set the size and postion on presentation window + set top of slide show window of active presentation to new_top + set left position of slide show window of active presentation to new_left_pos + if fullscreen is true then + -- put it in fullscreen mode + tell application "System Events" to tell process "Microsoft PowerPoint" + set value of attribute "AXFullScreen" of front window to true + end tell + else + set height of slide show window of active presentation to new_height + set width of slide show window of active presentation to new_width + end if + -- move to the front + activate + end tell +end start_presentation + +on stop_presentation() + tell application "Microsoft PowerPoint" + -- stop presenting - self.presentation.SlideShowWindow.View.Exit() + exit slide show slide show view of slide show window 1 + -- close presentation + close active presentation + -- Hide powerpoint + tell application "System Events" + set visible of application process "Microsoft PowerPoint" to false + end tell + end tell +end stop_presentation + +on is_loaded(documentPath) + tell application "Microsoft PowerPoint" + return (full name of active presentation is (documentPath as string)) + end tell +end is_loaded + +on is_active() + tell application "Microsoft PowerPoint" + -- when the presentation window is out of focus it is paused, so check for both paused and running + return (slide state of slide show view of slide show window of active presentation is ¬ + slide show state running) ¬ + or (slide state of slide show view of slide show window of active presentation is ¬ + slide show state paused) + end tell +end is_active + +on next_slide() + tell application "Microsoft PowerPoint" + --activate + -- self.presentation.SlideShowWindow.View.Next() + go to next slide slide show view of slide show window 1 + -- Print(?) slide number + -- slide number of slide of slideshow view of slide show window of active presentation + end tell +end next_slide + +on previous_slide() + tell application "Microsoft PowerPoint" + --activate + go to previous slide slide show view of slide show window 1 + -- Print(?) slide number + -- self.presentation.SlideShowWindow.View.Slide.SlideNumber + -- slide number of slide of view of slide show window of active presentation + end tell +end previous_slide + +on goto_slide(slideNumber) + tell application "Microsoft PowerPoint" + activate + -- self.presentation.SlideShowWindow.View.GotoSlide(slideNumber) + --go to slide slideNumber of slide show window of active presentation + -- there seems to be no applescipt-way of going to a specific slide, so send keystroke + tell application "System Events" + keystroke (slideNumber as string) + key code 36 + end tell + end tell +end goto_slide + +on get_current_slide() + tell application "Microsoft PowerPoint" + --activate + -- Print(?) slide number + return slide number of slide of slide show view of slide show window of active presentation + end tell +end get_current_slide + +on blank() + tell application "Microsoft PowerPoint" + --activate + -- self.presentation.SlideShowWindow.View.State = 3 + --set state of slide of view of slide show window of active presentation to 3 + set slide state of slide show view of slide show window 1 to slide show state black screen + end tell +end blank + +on unblank() + tell application "Microsoft PowerPoint" + --activate + -- self.presentation.SlideShowWindow.View.State = 1 + set slide state of slide show view of slide show window 1 to slide show state running + end tell +end unblank +""" + + +class PowerPointMacController(AppleScriptBaseController): + """ + Class to control interactions with a Powerpoint for Mac. + As well as triggering the correct activities based on the users input. + """ + log.info('PowerPointMacController loaded') + base_class = False + + def __init__(self, plugin): + """ + Initialise the class + """ + log.debug('Initialising') + super(PowerPointMacController, self).__init__(plugin, 'PowerPointMac', PowerPointMacDocument) + # Compiled script expected to be set by subclasses + try: + self.applescript = applescript.AppleScript(POWERPOINT_MAC_APPLESCRIPT) + except applescript.ScriptError: + log.exception('Compilation of Powerpoint applescript failed') + self.supports = ['ppt', 'pps', 'pptx', 'ppsx', 'pptm'] + self.also_supports = ['odp'] + + def check_available(self): + """ + Program is able to run on this machine. + """ + ret = super().check_available() + if ret: + # Powerpoint is available! now check if the version is 15 (Powerpoint 2016) or above + try: + version = self.applescript.call('get_version') + except Exception: + log.exception('script execution failed') + return False + try: + major_version = int(version.split('.')[0]) + except ValueError: + return False + if major_version >= 15: + return True + + +class PowerPointMacDocument(AppleScriptBaseDocument): + """ + Class which holds information and controls a single presentation. + """ + + def __init__(self, controller, document_path): + """ + Constructor, store information about the file and initialise. + + :param controller: + :param pathlib.Path document_path: Path to the document to load + :rtype: None + """ + log.debug('Init Presentation Applescript') + super().__init__(controller, document_path) + self.presentation_loaded = False + self.is_blank = False + + def load_presentation(self): + ret = super().load_presentation() + if ret: + # we make powerpoint export to PDF and then convert the pdf to thumbnails + if PYMUPDF_AVAILABLE: + pdf_file = self.get_thumbnail_folder() / 'tmp.pdf' + # if the pdf file does not exists anymore it is assume the convertion has been done + if not pdf_file.exists(): + return True + log.debug('converting pdf to png thumbnails using PyMuPDF') + pdf = fitz.open(str(pdf_file)) + for i, page in enumerate(pdf, start=1): + src_size = page.bound().round() + # keep aspect ratio + scale = min(640 / src_size.width, 480 / src_size.height) + m = fitz.Matrix(scale, scale) + page.getPixmap(m, alpha=False).writeImage(str(self.get_thumbnail_folder() / + 'slide{num}.png'.format(num=i))) + pdf.close() + # delete pdf + pdf_file.unlink() + return ret diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index bbfe5cd62..c4b50824f 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -120,6 +120,10 @@ class PresentationPlugin(Plugin): extension_loader(glob_pattern, ['presentationcontroller.py']) controller_classes = PresentationController.__subclasses__() for controller_class in controller_classes: + # Don't use classes marked as base + if hasattr(controller_class, 'base_class') and controller_class.base_class: + controller_classes.extend(controller_class.__subclasses__()) + continue controller = controller_class(self) self.register_controllers(controller) return bool(self.controllers)