forked from openlp/openlp
Merge branch 'keynote-ppt-mac' into 'master'
Add support for presentation with Keynote and PowerPoint on Mac See merge request openlp/openlp!205
This commit is contained in:
commit
fdf5cce5f2
@ -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
|
||||
|
||||
|
@ -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,
|
||||
|
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'])
|
||||
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)
|
||||
|
Loading…
Reference in New Issue
Block a user