forked from openlp/openlp
Started work on support for powerpoint on mac. Changed the script execution approach from osascript to py-applescript. Not tested. At all.
This commit is contained in:
parent
ec9d5b9099
commit
cd22bfaa51
@ -34,7 +34,7 @@ install:
|
|||||||
# Install Windows only dependencies
|
# Install Windows only dependencies
|
||||||
- cmd: python -m pip install pyodbc pypiwin32
|
- cmd: python -m pip install pyodbc pypiwin32
|
||||||
# Mac only dependencies
|
# 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
|
build: off
|
||||||
|
|
||||||
|
@ -286,6 +286,8 @@ class Settings(QtCore.QSettings):
|
|||||||
'presentations/Impress': QtCore.Qt.Checked,
|
'presentations/Impress': QtCore.Qt.Checked,
|
||||||
'presentations/Powerpoint': QtCore.Qt.Checked,
|
'presentations/Powerpoint': QtCore.Qt.Checked,
|
||||||
'presentations/Pdf': QtCore.Qt.Checked,
|
'presentations/Pdf': QtCore.Qt.Checked,
|
||||||
|
'presentations/Keynote': QtCore.Qt.Checked,
|
||||||
|
'presentations/PowerPointMac': QtCore.Qt.Checked,
|
||||||
'presentations/presentations files': [],
|
'presentations/presentations files': [],
|
||||||
'presentations/thumbnail_scheme': '',
|
'presentations/thumbnail_scheme': '',
|
||||||
'presentations/powerpoint slide click advance': QtCore.Qt.Unchecked,
|
'presentations/powerpoint slide click advance': QtCore.Qt.Unchecked,
|
||||||
|
310
openlp/plugins/presentations/lib/applescriptbasecontroller.py
Normal file
310
openlp/plugins/presentations/lib/applescriptbasecontroller.py
Normal file
@ -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 <https://www.gnu.org/licenses/>. #
|
||||||
|
##########################################################################
|
||||||
|
"""
|
||||||
|
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
|
307
openlp/plugins/presentations/lib/keynotecontroller.py
Normal file
307
openlp/plugins/presentations/lib/keynotecontroller.py
Normal file
@ -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 <https://www.gnu.org/licenses/>. #
|
||||||
|
##########################################################################
|
||||||
|
"""
|
||||||
|
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: <sha256>.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
|
347
openlp/plugins/presentations/lib/powerpointmaccontroller.py
Normal file
347
openlp/plugins/presentations/lib/powerpointmaccontroller.py
Normal file
@ -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 <https://www.gnu.org/licenses/>. #
|
||||||
|
##########################################################################
|
||||||
|
"""
|
||||||
|
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
|
@ -120,6 +120,10 @@ class PresentationPlugin(Plugin):
|
|||||||
extension_loader(glob_pattern, ['presentationcontroller.py'])
|
extension_loader(glob_pattern, ['presentationcontroller.py'])
|
||||||
controller_classes = PresentationController.__subclasses__()
|
controller_classes = PresentationController.__subclasses__()
|
||||||
for controller_class in controller_classes:
|
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)
|
controller = controller_class(self)
|
||||||
self.register_controllers(controller)
|
self.register_controllers(controller)
|
||||||
return bool(self.controllers)
|
return bool(self.controllers)
|
||||||
|
Loading…
Reference in New Issue
Block a user