From d2d9593ee28603db35305723472180bb2f0662c6 Mon Sep 17 00:00:00 2001 From: Jonathan Corwin Date: Sat, 8 Nov 2008 23:28:41 +0000 Subject: [PATCH] PowerPoint Viewer library plus test wrappers bzr-revno: 84 --- openlp/libraries/pptviewlib/README.TXT | 116 +++ openlp/libraries/pptviewlib/ppttest.py | 146 ++++ openlp/libraries/pptviewlib/pptviewlib.cpp | 662 ++++++++++++++++++ openlp/libraries/pptviewlib/pptviewlib.dll | Bin 0 -> 78336 bytes openlp/libraries/pptviewlib/pptviewlib.h | 51 ++ openlp/libraries/pptviewlib/pptviewlib.vcproj | 203 ++++++ openlp/libraries/pptviewlib/test.ppt | Bin 0 -> 21504 bytes 7 files changed, 1178 insertions(+) create mode 100644 openlp/libraries/pptviewlib/README.TXT create mode 100644 openlp/libraries/pptviewlib/ppttest.py create mode 100644 openlp/libraries/pptviewlib/pptviewlib.cpp create mode 100644 openlp/libraries/pptviewlib/pptviewlib.dll create mode 100644 openlp/libraries/pptviewlib/pptviewlib.h create mode 100644 openlp/libraries/pptviewlib/pptviewlib.vcproj create mode 100644 openlp/libraries/pptviewlib/test.ppt diff --git a/openlp/libraries/pptviewlib/README.TXT b/openlp/libraries/pptviewlib/README.TXT new file mode 100644 index 000000000..43954d150 --- /dev/null +++ b/openlp/libraries/pptviewlib/README.TXT @@ -0,0 +1,116 @@ + +PPTVIEWLIB - Control PowerPoint Viewer 2003/2007 (for openlp.org) +Copyright (C) 2008 Jonathan Corwin (j@corwin.co.uk) + +This library wrappers the free Microsoft PowerPoint Viewer (2003/2007) program, +allowing it to be more easily controlled from another program. + +The PowerPoint Viewer must already be installed on the destination machine, and is +freely available at microsoft.com. + +The full Microsoft Office PowerPoint and PowerPoint Viewer 97 have a COM interface allowing +automation. This ability was removed from the 2003+ viewer offerings. + +To developers: I am not a C/C++ or Win32 API programmer as you can probably tell. +The code and API of this DLL could certainly do with some tidying up, and the +error trapping, where it exists, is very basic. I'll happily accept patches! + +This library is covered by the GPL (http://www.gnu.org/licenses/) +It is NOT covered by the LGPL, so can only be used in GPL compatable programs. +(http://www.gnu.org/licenses/why-not-lgpl.html) + +This README.TXT must be distributed with the pptviewlib.dll + +This library has a limit of 50 PowerPoints which can be opened simultaneously. + +USAGE +----- +int OpenPPT(char *filename, HWND hParentWnd, RECT rect, char *previewpath); + + Opens the PowerPoint file, counts the number of slides, sizes and positions accordingly + and creates preview images of each slide. Note PowerPoint Viewer only allows the + slideshow to be resized whilst it is being loaded. It can be moved at any time however. + + The only way to count the number of slides is to step through the entire show. Therefore + there will be a delay whilst opening large presentations for the first time. + For pre XP/2003 systems, the slideshow will flicker as the screen snapshots are taken. + + filename: The PowerPoint file to be opened. Full path + hParentWnd: The window which will become the parent of the slideshow window. + Can be NULL. + rect: The location/dimensions of the slideshow window. + If all properties of this structure are zero, the dimensions of the hParentWnd + are used. + previewpath If specified, the prefix to use for snapshot images of each slide, in the + form: previewpath + n + ".bmp", where n is the slide number. + A file called previewpath + "info.txt" will also be created containing information + about the PPT file, to speed up future openings of the unmodified file. + Note it is up the calling program to directly access these images if they + are required. + + RETURNS: An unique identifier to pass to other methods in this library. + If < 0, then the PPT failed to open. + If >=0, ClosePPT must be called when the PPT is no longer being used + or when the calling program is closed to release resources/hooks. + +void ClosePPT(int id); + Closes the presentation, releasing any resources and hooks. + + id: The value returned from OpenPPT. + +int GetCurrentSlide(int id); + Returns the current slide number (from 1) + + id: The value returned from OpenPPT. + +int GetSlideCount(int id); + Returns the total number of slides. + + id: The value returned from OpenPPT. + +void NextStep(int id); + Advances one step (animation) through the slideshow. + + id: The value returned from OpenPPT. + +void PrevStep(int id); + Goes backwards one step (animation) through the slideshow. + + id: The value returned from OpenPPT. + +void GotoSlide(int id, int slideno); + Goes directly to a specific slide in the slideshow + + id: The value returned from OpenPPT. + slideno: The number of the slide (from 1) to go directly to. + + If the slide has already been displayed, then the completed slide with animations performed + will be shown. This is how the PowerPoint Viewer works so have no control over this. + +void RestartShow(int id); + Restarts the show from the beginning. To reset animations, behind the scenes it + has to travel to the end and step backwards though the entire show. Therefore + for large presentations there might be a delay. + + id: The value returned from OpenPPT. + +void Blank(int id); + Blanks the screen, colour black. + + id: The value returned from OpenPPT. + +void Unblank(int id) + Unblanks the screen, restoring it to it's pre-blank state. + + id: The value returned from OpenPPT. + +void Stop(int id) + Moves the slideshow off the screen. (There is no concept of stop show in the PowerPoint Viewer) + + id: The value returned from OpenPPT. + +void Resume(int id) + Moves the slideshow display back onto the screen following a Stop() + + id: The value returned from OpenPPT. + diff --git a/openlp/libraries/pptviewlib/ppttest.py b/openlp/libraries/pptviewlib/ppttest.py new file mode 100644 index 000000000..84f6fb527 --- /dev/null +++ b/openlp/libraries/pptviewlib/ppttest.py @@ -0,0 +1,146 @@ +import sys +from PyQt4 import QtGui, QtCore +from ctypes import * +from ctypes.wintypes import RECT + +class PPTViewer(QtGui.QWidget): + def __init__(self, parent=None): + QtGui.QWidget.__init__(self, parent) + self.pptid = -1 + self.setWindowTitle('PowerPoint Viewer Test') + + PPTLabel = QtGui.QLabel('Open PowerPoint file') + slideLabel = QtGui.QLabel('Go to slide #') + self.PPTEdit = QtGui.QLineEdit() + self.slideEdit = QtGui.QLineEdit() + self.total = QtGui.QLabel() + PPTBtn = QtGui.QPushButton("Open") + PPTDlgBtn = QtGui.QPushButton("...") + slideBtn = QtGui.QPushButton("Go") + prev = QtGui.QPushButton("Prev") + next = QtGui.QPushButton("Next") + blank = QtGui.QPushButton("Blank") + unblank = QtGui.QPushButton("Unblank") + restart = QtGui.QPushButton("Restart") + close = QtGui.QPushButton("Close") + resume = QtGui.QPushButton("Resume") + stop = QtGui.QPushButton("Stop") + pptwindow = QtGui.QWidget() + + grid = QtGui.QGridLayout() + grid.addWidget(PPTLabel, 0, 0) + grid.addWidget(self.PPTEdit, 0, 1) + grid.addWidget(PPTDlgBtn, 0, 2) + grid.addWidget(PPTBtn, 0, 3) + grid.addWidget(slideLabel, 1, 0) + grid.addWidget(self.slideEdit, 1, 1) + grid.addWidget(slideBtn, 1, 3) + grid.addWidget(prev, 2, 0) + grid.addWidget(next, 2, 1) + grid.addWidget(blank, 3, 0) + grid.addWidget(unblank, 3, 1) + grid.addWidget(restart, 4, 0) + grid.addWidget(close, 4, 1) + grid.addWidget(stop, 5, 0) + grid.addWidget(resume, 5, 1) + grid.addWidget(pptwindow, 6, 0, 10, 3) + self.connect(PPTBtn, QtCore.SIGNAL('clicked()'), self.OpenClick) + self.connect(PPTDlgBtn, QtCore.SIGNAL('clicked()'), self.OpenDialog) + self.connect(slideBtn, QtCore.SIGNAL('clicked()'), self.GotoClick) + self.connect(prev, QtCore.SIGNAL('clicked()'), self.PrevClick) + self.connect(next, QtCore.SIGNAL('clicked()'), self.NextClick) + self.connect(blank, QtCore.SIGNAL('clicked()'), self.BlankClick) + self.connect(unblank, QtCore.SIGNAL('clicked()'), self.UnblankClick) + self.connect(restart, QtCore.SIGNAL('clicked()'), self.RestartClick) + self.connect(close, QtCore.SIGNAL('clicked()'), self.CloseClick) + self.connect(stop, QtCore.SIGNAL('clicked()'), self.StopClick) + self.connect(resume, QtCore.SIGNAL('clicked()'), self.ResumeClick) + + self.setLayout(grid) + + self.resize(300, 150) + + def PrevClick(self): + if self.pptid<0: return + pptdll.PrevStep(self.pptid) + self.UpdateCurrSlide() + app.processEvents() + + def NextClick(self): + if(self.pptid<0): return + pptdll.NextStep(self.pptid) + self.UpdateCurrSlide() + app.processEvents() + + def BlankClick(self): + if(self.pptid<0): return + pptdll.Blank(self.pptid) + app.processEvents() + + def UnblankClick(self): + if(self.pptid<0): return + pptdll.Unblank(self.pptid) + app.processEvents() + + def RestartClick(self): + if(self.pptid<0): return + pptdll.RestartShow(self.pptid) + self.UpdateCurrSlide() + app.processEvents() + + def StopClick(self): + if(self.pptid<0): return + pptdll.Stop(self.pptid) + app.processEvents() + + def ResumeClick(self): + if(self.pptid<0): return + pptdll.Resume(self.pptid) + app.processEvents() + + def CloseClick(self): + if(self.pptid<0): return + pptdll.ClosePPT(self.pptid) + self.pptid = -1 + app.processEvents() + + def OpenClick(self): + if(self.pptid>=0): + self.CloseClick() + rect = RECT(100,100,900,700) + filename = str(self.PPTEdit.text()) + print filename + self.pptid = pptdll.OpenPPT(filename, None, rect, "c:\\temp\\slide") + print "id: " + str(self.pptid) + slides = pptdll.GetSlideCount(self.pptid) + print "slidecount: " + str(slides) + self.total.setNum(pptdll.GetSlideCount(self.pptid)) + self.UpdateCurrSlide() + app.processEvents() + + def UpdateCurrSlide(self): + if(self.pptid<0): return + slide = str(pptdll.GetCurrentSlide(self.pptid)) + print "currslide: " + slide + self.slideEdit.setText(slide) + app.processEvents() + + def GotoClick(self): + if(self.pptid<0): return + print self.slideEdit.text() + pptdll.GotoSlide(self.pptid, int(self.slideEdit.text())) + self.UpdateCurrSlide() + app.processEvents() + + def OpenDialog(self): + self.PPTEdit.setText(QtGui.QFileDialog.getOpenFileName(self, 'Open file')) + +if __name__ == '__main__': + pptdll = cdll.LoadLibrary(r"C:\Documents and Settings\jonathan\My Documents\Personal\openlp\openlp-2\trunk\openlp\libraries\pptviewlib\pptviewlib.dll") + pptdll.SetDebug(1) + print "Begin..." + app = QtGui.QApplication(sys.argv) + qb = PPTViewer() + qb.show() + sys.exit(app.exec_()) + diff --git a/openlp/libraries/pptviewlib/pptviewlib.cpp b/openlp/libraries/pptviewlib/pptviewlib.cpp new file mode 100644 index 000000000..0848d2baf --- /dev/null +++ b/openlp/libraries/pptviewlib/pptviewlib.cpp @@ -0,0 +1,662 @@ +/* + * PPTVIEWLIB - Control PowerPoint Viewer 2003/2007 (for openlp.org) + * Copyright (C) 2008 Jonathan Corwin + * + * 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 2 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 . + */ + + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#include +#include +#include "pptviewlib.h" + +// Because of the callbacks used by SetWindowsHookEx, the memory used needs to be +// sharable across processes (the callbacks are done from a different process) +// Therefore use data_seg with RWS memory. +// +// See http://msdn.microsoft.com/en-us/library/aa366551(VS.85).aspx for alternative +// method of holding memory, removing fixed limits which would allow dynamic number +// of items, rather than a fixed number. Use a Local\ mapping, since global has UAC +// issues in Vista. +#pragma data_seg(".PPTVIEWLIB") +PPTVIEWOBJ pptviewobj[MAX_PPTOBJS] = {NULL}; +HHOOK globalhook = NULL; +BOOL debug = FALSE; +#pragma data_seg() +#pragma comment(linker, "/SECTION:.PPTVIEWLIB,RWS") + +#define DEBUG(...) if(debug) printf(__VA_ARGS__) + + +HINSTANCE hInstance = NULL; + +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + hInstance = (HINSTANCE)hModule; + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + case DLL_PROCESS_DETACH: + // Clean up... hopefully there is only the one process attached? + // We'll find out soon enough during tests! + for(int i = 0; i.bmp" will be appended to complete the path. E.g. "c:\temp\slide" would +// create "c:\temp\slide1.bmp" slide2.bmp, slide3.bmp etc. +// It will also create a *info.txt containing information about the ppt +DllExport int OpenPPT(char *filename, HWND hParentWnd, RECT rect, char *previewpath) +{ + STARTUPINFO si; + PROCESS_INFORMATION pi; + char cmdline[MAX_PATH * 2]; + int id; + + DEBUG("OpenPPT start: %s\n", filename); + if(GetPPTViewerPath(cmdline, sizeof(cmdline))==FALSE) + { + DEBUG("OpenPPT: GetPPTViewerPath failed\n"); + return -1; + } + id = -1; + for(int i = 0; ibottom-wndrect->top; + pptviewobj[id].rect.right = wndrect->right-wndrect->left; + } + else + { + pptviewobj[id].rect.top = rect.top; + pptviewobj[id].rect.left = rect.left; + pptviewobj[id].rect.bottom = rect.bottom; + pptviewobj[id].rect.right = rect.right; + } + strcat_s(cmdline, MAX_PATH * 2, "/S \""); + strcat_s(cmdline, MAX_PATH * 2, filename); + strcat_s(cmdline, MAX_PATH * 2, "\""); + memset(&si, 0, sizeof(si)); + memset(&pi, 0, sizeof(pi)); + BOOL gotinfo = GetPPTInfo(id); + if(!CreateProcess(NULL, cmdline, NULL, NULL, FALSE, 0, 0, NULL, &si, &pi)) + { + DEBUG("OpenPPT: CreateProcess failed\n"); + ClosePPT(id); + return -1; + } + + pptviewobj[id].state = PPT_STARTED; + pptviewobj[id].dwProcessId = pi.dwProcessId; + pptviewobj[id].dwThreadId = pi.dwThreadId; + pptviewobj[id].hThread = pi.hThread; + pptviewobj[id].hProcess = pi.hProcess; + /* + * I'd really like to just hook on the new threadid. However this always gives + * error 87. Perhaps I'm hooking to soon? No idea... however can't wait + * since I need to ensure I pick up the WM_CREATE as this is the only + * time the window can be resized in such away the content scales correctly + * + * hook = SetWindowsHookEx(WH_CBT,CbtProc,hInstance,pi.dwThreadId); + */ + if(globalhook!=NULL) + UnhookWindowsHookEx(globalhook); + globalhook = SetWindowsHookEx(WH_CBT,CbtProc,hInstance,NULL); + if(globalhook==0) + { + DEBUG("OpenPPT: SetWindowsHookEx failed\n"); + ClosePPT(id); + return -1; + } + if(gotinfo) + pptviewobj[id].state = PPT_LOADED; + else + { + while(pptviewobj[id].state!=PPT_LOADED&&pptviewobj[id].state!=PPT_CLOSED) + { + NextStep(id); + Sleep(100); // need to be careful not to be too quick, otherwise step to far and close show + } + SavePPTInfo(id); + RestartShow(id); + } + //InvalidateRect(pptviewobj[id].hWnd, NULL, TRUE); + DEBUG("OpenPPT: Exit: id=%i\n", id); + return id; +} +// Load information about the ppt from an info.txt file. +// Format: +// filedate +// filesize +// slidecount +BOOL GetPPTInfo(int id) +{ + struct _stat filestats; + char info[MAX_PATH]; + FILE* pFile; + char buf[100]; + + DEBUG("GetPPTInfo: start\n"); + if(_stat(pptviewobj[id].filename, &filestats)!=0) + return FALSE; + sprintf_s(info, MAX_PATH, "%sinfo.txt", pptviewobj[id].previewpath); + int err = fopen_s(&pFile, info, "r"); + if(err!=0) + return FALSE; + fgets(buf, 100, pFile); + if(filestats.st_mtime!=atoi(buf)) + { + fclose (pFile); + return FALSE; + } + fgets(buf, 100, pFile); + if(filestats.st_size!=atoi(buf)) + { + fclose (pFile); + return FALSE; + } + fgets(buf, 100, pFile); // slidecount + int slidecount = atoi(buf); + // check all the preview images still exist + for(int i = 1; i<=slidecount; i++) + { + sprintf_s(info, MAX_PATH, "%s%i.bmp", pptviewobj[id].previewpath, i); + if(GetFileAttributes(info)==INVALID_FILE_ATTRIBUTES) + return FALSE; + } + pptviewobj[id].slideCount = slidecount; + DEBUG("GetPPTInfo: exit ok\n"); + return TRUE; +} + +BOOL SavePPTInfo(int id) +{ + struct _stat filestats; + char info[MAX_PATH]; + FILE* pFile; + + DEBUG("SavePPTInfo: start\n"); + if(_stat(pptviewobj[id].filename, &filestats)!=0) + { + DEBUG("SavePPTInfo: stat of %s failed\n", pptviewobj[id].filename); + return FALSE; + } + sprintf_s(info, MAX_PATH, "%sinfo.txt", pptviewobj[id].previewpath); + int err = fopen_s(&pFile, info, "w"); + if(err!=0) + { + DEBUG("SavePPTInfo: fopen of %s failed%i\n", info, err); + return FALSE; + } + DEBUG("%u\n%u\n%u\n", filestats.st_mtime, filestats.st_size, pptviewobj[id].slideCount); + fprintf(pFile, "%u\n%u\n%u\n", filestats.st_mtime, filestats.st_size, pptviewobj[id].slideCount); + fclose (pFile); + DEBUG("SavePPTInfo: exit ok\n"); + return TRUE; +} + +// Get the path of the PowerPoint viewer from the registry +BOOL GetPPTViewerPath(char *pptviewerpath, int strsize) +{ + HKEY hkey; + DWORD dwtype, dwsize; + LRESULT lresult; + + DEBUG("GetPPTViewerPath: start\n"); + if(RegOpenKeyEx(HKEY_CLASSES_ROOT, "Applications\\PPTVIEW.EXE\\shell\\open\\command", 0, KEY_READ, &hkey)!=ERROR_SUCCESS) + return FALSE; + dwtype = REG_SZ; + dwsize = (DWORD)strsize; + lresult = RegQueryValueEx(hkey, NULL, NULL, &dwtype, (LPBYTE)pptviewerpath, &dwsize ); + RegCloseKey(hkey); + if(lresult!=ERROR_SUCCESS) + return FALSE; + pptviewerpath[strlen(pptviewerpath)-4] = '\0'; // remove "%1" from end of key value + DEBUG("GetPPTViewerPath: exit ok\n"); + return TRUE; +} + +// Unhook the Windows hook +void Unhook(int id) +{ + DEBUG("Unhook: start\n"); + if(pptviewobj[id].hook!=NULL) + UnhookWindowsHookEx(pptviewobj[id].hook); + pptviewobj[id].hook = NULL; + DEBUG("Unhook: exit ok\n"); +} + +// Close the PowerPoint viewer, release resources +DllExport void ClosePPT(int id) +{ + DEBUG("ClosePPT: start\n"); + pptviewobj[id].state = PPT_CLOSED; + Unhook(id); + if(pptviewobj[id].hWnd==0) + TerminateThread(pptviewobj[id].hWnd, 0); + else + PostMessage(pptviewobj[id].hWnd, WM_CLOSE, 0, 0); + CloseHandle(pptviewobj[id].hThread); + CloseHandle(pptviewobj[id].hProcess); + memset(&pptviewobj[id], 0, sizeof(PPTVIEWOBJ)); + DEBUG("ClosePPT: exit ok\n"); + return; +} +// Moves the show back onto the display +DllExport void Resume(int id) +{ + DEBUG("Resume:\n"); + MoveWindow(pptviewobj[id].hWnd, pptviewobj[id].rect.left, pptviewobj[id].rect.top, + pptviewobj[id].rect.right - pptviewobj[id].rect.left, + pptviewobj[id].rect.bottom - pptviewobj[id].rect.top, TRUE); + Unblank(id); +} +// Moves the show off the screen so it can't be seen +DllExport void Stop(int id) +{ + DEBUG("Stop:\n"); + MoveWindow(pptviewobj[id].hWnd, -32000, -32000, + pptviewobj[id].rect.right - pptviewobj[id].rect.left, + pptviewobj[id].rect.bottom - pptviewobj[id].rect.top, TRUE); +} + +// Return the total number of slides +DllExport int GetSlideCount(int id) +{ + DEBUG("GetSlideCount:\n"); + if(pptviewobj[id].state==0) + return -1; + else + return pptviewobj[id].slideCount; +} + +// Return the number of the slide currently viewing +DllExport int GetCurrentSlide(int id) +{ + DEBUG("GetCurrentSlide:\n"); + if(pptviewobj[id].state==0) + return -1; + else + return pptviewobj[id].currentSlide; +} + +// Take a step forwards through the show +DllExport void NextStep(int id) +{ + DEBUG("NextStep:\n"); + PostMessage(pptviewobj[id].hWnd, WM_MOUSEWHEEL, MAKEWPARAM(0, -WHEEL_DELTA), 0); +} + +// Take a step backwards through the show +DllExport void PrevStep(int id) +{ + DEBUG("PrevStep:\n"); + PostMessage(pptviewobj[id].hWnd, WM_MOUSEWHEEL, MAKEWPARAM(0, WHEEL_DELTA), 0); +} + +// Blank the show (black screen) +DllExport void Blank(int id) +{ + // B just toggles blank on/off. However pressing any key unblanks. + // So send random unmapped letter first (say 'A'), then we can + // better guarantee B will blank instead of trying to guess + // whether it was already blank or not. + DEBUG("Blank:\n"); + HWND h1 = GetForegroundWindow(); + HWND h2 = GetFocus(); + SetForegroundWindow(pptviewobj[id].hWnd2); + SetFocus(pptviewobj[id].hWnd2); + keybd_event((int)'A', 0, 0, 0); + keybd_event((int)'B', 0, 0, 0); + SetForegroundWindow(h1); + SetFocus(h2); + //SendMessage(pptviewobj[id].hWnd2, WM_KEYDOWN, 'A', 0); + //SendMessage(pptviewobj[id].hWnd2, WM_CHAR, 'A', 0); + //SendMessage(pptviewobj[id].hWnd2, WM_KEYUP, 'A', 0); + //SendMessage(pptviewobj[id].hWnd2, WM_KEYDOWN, 'B', 0); + //SendMessage(pptviewobj[id].hWnd2, WM_CHAR, 'B', 0); + //SendMessage(pptviewobj[id].hWnd2, WM_KEYUP, 'B', 0); +} +// Unblank the show +DllExport void Unblank(int id) +{ + DEBUG("Unblank:\n"); + // Pressing any key resumes. + SendMessage(pptviewobj[id].hWnd2, WM_KEYDOWN, 'A', 0); + SendMessage(pptviewobj[id].hWnd2, WM_CHAR, 'A', 0); + SendMessage(pptviewobj[id].hWnd2, WM_KEYUP, 'A', 0); +// HWND h1 = GetForegroundWindow(); +// HWND h2 = GetFocus(); +// SetForegroundWindow(pptviewobj[id].hWnd); +// SetFocus(pptviewobj[id].hWnd); +// keybd_event((int)'A', 0, 0, 0); +// SetForegroundWindow(h1); +// SetFocus(h2); +} + +// Go directly to a slide +DllExport void GotoSlide(int id, int slideno) +{ + DEBUG("GotoSlide %i:\n", slideno); + // Did try WM_KEYDOWN/WM_CHAR/WM_KEYUP with SendMessage but didn't work + // perhaps I was sending to the wrong window? No idea. + // Anyway fall back to keybd_event, which is OK as long we makesure + // the slideshow has focus first + char ch[10]; + + if(slideno<0) return; + _itoa_s(slideno, ch, 10, 10); + HWND h1 = GetForegroundWindow(); + HWND h2 = GetFocus(); + SetForegroundWindow(pptviewobj[id].hWnd); + SetFocus(pptviewobj[id].hWnd); + for(int i=0;i<10;i++) + { + if(ch[i]=='\0') break; + keybd_event((BYTE)ch[i], 0, 0, 0); + } + keybd_event(VK_RETURN, 0, 0, 0); + SetForegroundWindow(h1); + SetFocus(h2); + + //for(int i=0;i<10;i++) + //{ + // if(ch[i]=='\0') break; + // SendMessage(pptviewobj[id].hWnd2, WM_KEYDOWN, ch[i], 0); + // SendMessage(pptviewobj[id].hWnd2, WM_CHAR, ch[i], 0); + // SendMessage(pptviewobj[id].hWnd2, WM_KEYUP, ch[i], 0); + //} + //SendMessage(pptviewobj[id].hWnd2, WM_KEYDOWN, VK_RETURN, 0); + //SendMessage(pptviewobj[id].hWnd2, WM_CHAR, VK_RETURN, 0); + //SendMessage(pptviewobj[id].hWnd2, WM_KEYUP, VK_RETURN, 0); + //keybd_event(VK_RETURN, 0, 0, 0); +} + +// Restart the show from the beginning +DllExport void RestartShow(int id) +{ + // If we just go direct to slide one, then it remembers that all other slides have + // been animated, so ends up just showing the completed slides of those slides that + // have been animated next time we advance. + // Only way I've found to get around this is to step backwards all the way through. + // Lets move the window out of the way first so the audience doesn't see this. + DEBUG("RestartShow:\n"); + Stop(id); + GotoSlide(id, pptviewobj[id].slideCount); + while(pptviewobj[id].currentSlide>1) + { + PrevStep(id); + Sleep(10); + } + for(int i=0;i<=pptviewobj[id].firstSlideSteps;i++) + { + PrevStep(id); + } + Resume(id); +} + +// This hook is started with the PPTVIEW.EXE process and waits for the +// WM_CREATEWND message. At this point (and only this point) can the +// window be resized to the correct size. +// Release the hook as soon as we're complete to free up resources +LRESULT CALLBACK CbtProc(int nCode, WPARAM wParam, LPARAM lParam) +{ + HHOOK hook = globalhook; + if(nCode==HCBT_CREATEWND) + { + char csClassName[16]; + HWND hCurrWnd = (HWND)wParam; + DWORD retProcId = NULL; + GetClassName(hCurrWnd, csClassName, sizeof(csClassName)); + if((strcmp(csClassName, "paneClassDC")==0) + ||(strcmp(csClassName, "screenClass")==0)) + { + int id=-1; + DWORD windowthread = GetWindowThreadProcessId(hCurrWnd,NULL); + for(int i=0; i=0) + { + if(strcmp(csClassName, "paneClassDC")==0) + pptviewobj[id].hWnd2=hCurrWnd; + else + { + pptviewobj[id].hWnd=hCurrWnd; + CBT_CREATEWND* cw = (CBT_CREATEWND*)lParam; + if(pptviewobj[id].hParentWnd!=NULL) + cw->lpcs->hwndParent = pptviewobj[id].hParentWnd; + cw->lpcs->cy=(pptviewobj[id].rect.bottom-pptviewobj[id].rect.top); + cw->lpcs->cx=(pptviewobj[id].rect.right-pptviewobj[id].rect.left); + cw->lpcs->y=-32000; + cw->lpcs->x=-32000; + } + if((pptviewobj[id].hWnd!=NULL)&&(pptviewobj[id].hWnd2!=NULL)) + { + UnhookWindowsHookEx(globalhook); + globalhook=NULL; + pptviewobj[id].state = PPT_OPENED; + pptviewobj[id].hook = SetWindowsHookEx(WH_CALLWNDPROC,CwpProc,hInstance,pptviewobj[id].dwThreadId); + } + } + } + } + return CallNextHookEx(hook,nCode,wParam,lParam); +} + +// This hook exists whilst the slideshow is running but only listens on the +// slideshows thread. It listens out for slide changes, message WM_USER+22. +LRESULT CALLBACK CwpProc(int nCode, WPARAM wParam, LPARAM lParam){ + CWPSTRUCT *cwp; + cwp = (CWPSTRUCT *)lParam; + HHOOK hook = NULL; + char filename[MAX_PATH]; + + DWORD windowthread = GetWindowThreadProcessId(cwp->hwnd,NULL); + int id=-1; + for(int i=0; i=0)&&(nCode==HC_ACTION)) + { + if(cwp->message==WM_USER+22) + { + if((pptviewobj[id].state != PPT_LOADED) + && (pptviewobj[id].currentSlide>0) + && (pptviewobj[id].previewpath!=NULL&&strlen(pptviewobj[id].previewpath)>0)) + { + sprintf_s(filename, MAX_PATH, "%s%i.bmp", pptviewobj[id].previewpath, pptviewobj[id].currentSlide); + CaptureAndSaveWindow(cwp->hwnd, filename); + } + if(cwp->wParam==0) + { + if(pptviewobj[id].currentSlide>0) + { + pptviewobj[id].state = PPT_LOADED; + pptviewobj[id].currentSlide = pptviewobj[id].slideCount+1; + } + } + else + { + if((pptviewobj[id].state != PPT_LOADED)&&cwp->wParam==256) + pptviewobj[id].firstSlideSteps++; + pptviewobj[id].currentSlide = cwp->wParam - 255; + if(pptviewobj[id].currentSlide>pptviewobj[id].slideCount) + pptviewobj[id].slideCount = pptviewobj[id].currentSlide; + } + } + if((pptviewobj[id].state != PPT_CLOSED)&&(cwp->message==WM_CLOSE||cwp->message==WM_QUIT)) + ClosePPT(id); + } + return CallNextHookEx(hook,nCode,wParam,lParam); +} + +VOID CaptureAndSaveWindow(HWND hWnd, CHAR* filename) +{ + HBITMAP hBmp; + if ((hBmp = CaptureWindow(hWnd)) == NULL) + return; + + RECT client; + GetClientRect (hWnd, &client); + UINT uiBytesPerRow = 3 * client.right; // RGB takes 24 bits + UINT uiRemainderForPadding; + + if ((uiRemainderForPadding = uiBytesPerRow % sizeof (DWORD)) > 0) + uiBytesPerRow += (sizeof (DWORD) - uiRemainderForPadding); + + UINT uiBytesPerAllRows = uiBytesPerRow * client.bottom; + PBYTE pDataBits; + + if ((pDataBits = new BYTE [uiBytesPerAllRows]) != NULL) + { + BITMAPINFOHEADER bmi = {0}; + BITMAPFILEHEADER bmf = {0}; + + // Prepare to get the data out of HBITMAP: + bmi.biSize = sizeof (bmi); + bmi.biPlanes = 1; + bmi.biBitCount = 24; + bmi.biHeight = client.bottom; + bmi.biWidth = client.right; + + // Get it: + HDC hDC = GetDC (hWnd); + GetDIBits (hDC, hBmp, 0, client.bottom, pDataBits, + (BITMAPINFO*) &bmi, DIB_RGB_COLORS); + ReleaseDC (hWnd, hDC); + + // Fill the file header: + bmf.bfOffBits = sizeof (bmf) + sizeof (bmi); + bmf.bfSize = bmf.bfOffBits + uiBytesPerAllRows; + bmf.bfType = 0x4D42; + + // Writing: + FILE* pFile; + int err = fopen_s(&pFile, filename, "wb"); + if (err == 0) + { + fwrite (&bmf, sizeof (bmf), 1, pFile); + fwrite (&bmi, sizeof (bmi), 1, pFile); + fwrite (pDataBits, sizeof (BYTE), uiBytesPerAllRows, pFile); + fclose (pFile); + } + delete [] pDataBits; + } + DeleteObject (hBmp); +} +HBITMAP CaptureWindow (HWND hWnd) { + HDC hDC; + BOOL bOk = FALSE; + HBITMAP hImage = NULL; + + hDC = GetDC (hWnd); + RECT rcClient; + GetClientRect (hWnd, &rcClient); + if ((hImage = CreateCompatibleBitmap (hDC, rcClient.right, rcClient.bottom)) != NULL) + { + HDC hMemDC; + HBITMAP hDCBmp; + + if ((hMemDC = CreateCompatibleDC (hDC)) != NULL) + { + hDCBmp = (HBITMAP) SelectObject (hMemDC, hImage); + HMODULE hLib = LoadLibrary("User32"); + if(GetProcAddress(hLib, "PrintWindow")==NULL) + { + SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE); + BitBlt (hMemDC, 0, 0, rcClient.right, rcClient.bottom, hDC, 0, 0, SRCCOPY); + SetWindowPos(hWnd, HWND_NOTOPMOST, -32000, -32000, 0, 0, SWP_NOSIZE); + } + else + { + PrintWindow(hWnd, hMemDC, 0); + } + SelectObject (hMemDC, hDCBmp); + DeleteDC (hMemDC); + bOk = TRUE; + } + } + ReleaseDC (hWnd, hDC); + if (! bOk) + { + if (hImage) + { + DeleteObject (hImage); + hImage = NULL; + } + } + return hImage; +} diff --git a/openlp/libraries/pptviewlib/pptviewlib.dll b/openlp/libraries/pptviewlib/pptviewlib.dll new file mode 100644 index 0000000000000000000000000000000000000000..8ebd58fc39422b1b58ea2e6d76582b517b3ac649 GIT binary patch literal 78336 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openlp/libraries/pptviewlib/test.ppt b/openlp/libraries/pptviewlib/test.ppt new file mode 100644 index 0000000000000000000000000000000000000000..1d90168b16898c8c783c9ac77361fc66ebd84a57 GIT binary patch literal 21504 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 OST%36)inr*p7=l6mS+