openPDF.js (8527B)
1 /* 2 ***** BEGIN LICENSE BLOCK ***** 3 4 Copyright © 2018 Center for History and New Media 5 George Mason University, Fairfax, Virginia, USA 6 https://zotero.org 7 8 This file is part of Zotero. 9 10 Zotero is free software: you can redistribute it and/or modify 11 it under the terms of the GNU Affero General Public License as published by 12 the Free Software Foundation, either version 3 of the License, or 13 (at your option) any later version. 14 15 Zotero is distributed in the hope that it will be useful, 16 but WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 GNU Affero General Public License for more details. 19 20 You should have received a copy of the GNU Affero General Public License 21 along with Zotero. If not, see <http://www.gnu.org/licenses/>. 22 23 ***** END LICENSE BLOCK ***** 24 */ 25 26 Zotero.OpenPDF = { 27 openToPage: async function (path, page) { 28 var handler = Zotero.Prefs.get("fileHandler.pdf"); 29 var opened = false; 30 if (Zotero.isMac) { 31 if (handler.includes('Preview')) { 32 this._openWithPreview(path, page); 33 } 34 else if (handler.includes('Skim')) { 35 this._openWithSkim(path, page); 36 } 37 else if (handler.includes('PDF Expert')) { 38 this._openWithPDFExpert(path, page); 39 } 40 else { 41 // Try to detect default app 42 handler = this._getPDFHandlerName(); 43 Zotero.debug(`Handler is ${handler}`); 44 if (handler && handler == 'Skim') { 45 this._openWithSkim(path, page); 46 } 47 else if (handler && handler == 'PDF Expert') { 48 this._openWithPDFExpert(path, page); 49 } 50 // Fall back to Preview 51 else { 52 this._openWithPreview(path, page); 53 } 54 } 55 opened = true; 56 } 57 else if (Zotero.isWin) { 58 handler = handler || this._getPDFHandlerWindows(); 59 // Include flags to open the PDF on a given page in various apps 60 // 61 // Adobe Acrobat: http://partners.adobe.com/public/developer/en/acrobat/PDFOpenParameters.pdf 62 // PDF-XChange: http://help.tracker-software.com/eu/default.aspx?pageid=PDFXView25:command_line_options 63 let args = ['/A', 'page=' + page, path]; 64 Zotero.Utilities.Internal.exec(handler, args); 65 opened = true; 66 } 67 else if (Zotero.isLinux) { 68 if (handler.includes('evince') || handler.includes('okular')) { 69 this._openWithEvinceOrOkular(handler, path, page); 70 opened = true; 71 } 72 else { 73 let handler = await this._getPDFHandlerLinux(); 74 if (handler.includes('evince') || handler.includes('okular')) { 75 this._openWithEvinceOrOkular(handler, path, page); 76 opened = true; 77 } 78 // Fall back to okular and then evince if unknown handler 79 else if (await OS.File.exists('/usr/bin/okular')) { 80 this._openWithEvinceOrOkular('/usr/bin/okular', path, page); 81 opened = true; 82 } 83 else if (await OS.File.exists('/usr/bin/evince')) { 84 this._openWithEvinceOrOkular('/usr/bin/evince', path, page); 85 opened = true; 86 } 87 else { 88 Zotero.debug("No handler found"); 89 } 90 } 91 } 92 return opened; 93 }, 94 95 _getPDFHandlerName: function () { 96 var handlerService = Cc["@mozilla.org/uriloader/handler-service;1"] 97 .getService(Ci.nsIHandlerService); 98 var handlers = handlerService.enumerate(); 99 var handler; 100 while (handlers.hasMoreElements()) { 101 let handlerInfo = handlers.getNext().QueryInterface(Ci.nsIHandlerInfo); 102 if (handlerInfo.type == 'application/pdf') { 103 handler = handlerInfo; 104 break; 105 } 106 } 107 if (!handler) { 108 // We can't get the name of the system default handler unless we add an entry 109 Zotero.debug("Default handler not found -- adding default entry"); 110 let mimeService = Components.classes["@mozilla.org/mime;1"] 111 .getService(Components.interfaces.nsIMIMEService); 112 let mimeInfo = mimeService.getFromTypeAndExtension("application/pdf", ""); 113 mimeInfo.preferredAction = 4; 114 mimeInfo.alwaysAskBeforeHandling = false; 115 handlerService.store(mimeInfo); 116 117 // And once we do that, we can get the name (but not the path, unfortunately) 118 let handlers = handlerService.enumerate(); 119 while (handlers.hasMoreElements()) { 120 let handlerInfo = handlers.getNext().QueryInterface(Ci.nsIHandlerInfo); 121 if (handlerInfo.type == 'application/pdf') { 122 handler = handlerInfo; 123 break; 124 } 125 } 126 } 127 if (handler) { 128 Zotero.debug(`Default handler is ${handler.defaultDescription}`); 129 return handler.defaultDescription; 130 } 131 return false; 132 }, 133 134 // 135 // Mac 136 // 137 _openWithPreview: async function (filePath, page) { 138 await Zotero.Utilities.Internal.exec('/usr/bin/open', ['-a', 'Preview', filePath]); 139 // Go to page using AppleScript 140 let args = [ 141 '-e', 'tell app "Preview" to activate', 142 '-e', 'tell app "System Events" to keystroke "g" using {option down, command down}', 143 '-e', `tell app "System Events" to keystroke "${page}"`, 144 '-e', 'tell app "System Events" to keystroke return' 145 ]; 146 await Zotero.Utilities.Internal.exec('/usr/bin/osascript', args); 147 }, 148 149 _openWithSkim: async function (filePath, page) { 150 // Escape double-quotes in path 151 var quoteRE = /"/g; 152 filePath = filePath.replace(quoteRE, '\\"'); 153 let filename = OS.Path.basename(filePath).replace(quoteRE, '\\"'); 154 let args = [ 155 '-e', 'tell app "Skim" to activate', 156 '-e', `tell app "Skim" to open "${filePath}"` 157 ]; 158 args.push('-e', `tell document "${filename}" of application "Skim" to go to page ${page}`); 159 await Zotero.Utilities.Internal.exec('/usr/bin/osascript', args); 160 }, 161 162 _openWithPDFExpert: async function (filePath, page) { 163 await Zotero.Utilities.Internal.exec('/usr/bin/open', ['-a', 'PDF Expert', filePath]); 164 // Go to page using AppleScript (same as Preview) 165 let args = [ 166 '-e', 'tell app "PDF Expert" to activate', 167 '-e', 'tell app "System Events" to keystroke "g" using {option down, command down}', 168 '-e', `tell app "System Events" to keystroke "${page}"`, 169 '-e', 'tell app "System Events" to keystroke return' 170 ]; 171 await Zotero.Utilities.Internal.exec('/usr/bin/osascript', args); 172 }, 173 174 // 175 // Windows 176 // 177 /** 178 * Get path to default pdf reader application on windows 179 * @return {string} Path to default pdf reader application 180 * 181 * From getPDFReader() in ZotFile (GPL) 182 * https://github.com/jlegewie/zotfile/blob/master/chrome/content/zotfile/utils.js 183 */ 184 _getPDFHandlerWindows: function () { 185 var wrk = Components.classes["@mozilla.org/windows-registry-key;1"] 186 .createInstance(Components.interfaces.nsIWindowsRegKey); 187 // Get handler for PDFs 188 var tryKeys = [ 189 { 190 root: wrk.ROOT_KEY_CURRENT_USER, 191 path: 'Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\.pdf\\UserChoice', 192 value: 'Progid' 193 }, 194 { 195 root: wrk.ROOT_KEY_CLASSES_ROOT, 196 path: '.pdf', 197 value: '' 198 } 199 ]; 200 var progId; 201 for (let i = 0; !progId && i < tryKeys.length; i++) { 202 try { 203 wrk.open( 204 tryKeys[i].root, 205 tryKeys[i].path, 206 wrk.ACCESS_READ 207 ); 208 progId = wrk.readStringValue(tryKeys[i].value); 209 } 210 catch (e) {} 211 } 212 213 if (!progId) { 214 wrk.close(); 215 return; 216 } 217 218 // Get version specific handler, if it exists 219 try { 220 wrk.open( 221 wrk.ROOT_KEY_CLASSES_ROOT, 222 progId + '\\CurVer', 223 wrk.ACCESS_READ 224 ); 225 progId = wrk.readStringValue('') || progId; 226 } 227 catch (e) {} 228 229 // Get command 230 var success = false; 231 tryKeys = [ 232 progId + '\\shell\\Read\\command', 233 progId + '\\shell\\Open\\command' 234 ]; 235 for (let i = 0; !success && i < tryKeys.length; i++) { 236 try { 237 wrk.open( 238 wrk.ROOT_KEY_CLASSES_ROOT, 239 tryKeys[i], 240 wrk.ACCESS_READ 241 ); 242 success = true; 243 } 244 catch (e) {} 245 } 246 247 if (!success) { 248 wrk.close(); 249 return; 250 } 251 252 var command = wrk.readStringValue('').match(/^(?:".+?"|[^"]\S+)/); 253 254 wrk.close(); 255 256 if (!command) return; 257 return command[0].replace(/"/g, ''); 258 }, 259 260 // 261 // Linux 262 // 263 _getPDFHandlerLinux: async function () { 264 var name = this._getPDFHandlerName(); 265 switch (name.toLowerCase()) { 266 case 'okular': 267 return `/usr/bin/${name}`; 268 269 // It's "Document Viewer" on stock Ubuntu 270 case 'document viewer': 271 case 'evince': 272 return `/usr/bin/evince`; 273 } 274 275 // TODO: Try to get default from mimeapps.list, etc., in case system default is okular 276 // or evince somewhere other than /usr/bin 277 var homeDir = OS.Constants.Path.homeDir; 278 279 return false; 280 281 }, 282 283 _openWithEvinceOrOkular: function (appPath, filePath, page) { 284 var args = ['-p', page, filePath]; 285 Zotero.Utilities.Internal.exec(appPath, args); 286 } 287 }