48 static final String UTILITY_SCRIPTS = "UTILITY_SCRIPTS"; |
49 static final String UTILITY_SCRIPTS = "UTILITY_SCRIPTS"; |
49 |
50 |
50 DesktopIntegration(PlatformPackage thePackage, |
51 DesktopIntegration(PlatformPackage thePackage, |
51 Map<String, ? super Object> params) { |
52 Map<String, ? super Object> params) { |
52 |
53 |
53 associations = FILE_ASSOCIATIONS.fetchFrom(params).stream().filter( |
54 associations = FileAssociation.fetchFrom(params).stream() |
54 a -> { |
55 .filter(fa -> !fa.mimeTypes.isEmpty()) |
55 if (a == null) { |
56 .map(LinuxFileAssociation::new) |
56 return false; |
57 .collect(Collectors.toUnmodifiableList()); |
57 } |
|
58 List<String> mimes = FA_CONTENT_TYPE.fetchFrom(a); |
|
59 return (mimes != null && !mimes.isEmpty()); |
|
60 }).collect(Collectors.toUnmodifiableList()); |
|
61 |
58 |
62 launchers = ADD_LAUNCHERS.fetchFrom(params); |
59 launchers = ADD_LAUNCHERS.fetchFrom(params); |
63 |
60 |
64 this.thePackage = thePackage; |
61 this.thePackage = thePackage; |
65 |
62 |
257 unregisterFileAssociationsCmd = stringifyShellCommands( |
253 unregisterFileAssociationsCmd = stringifyShellCommands( |
258 unregisterFileAssociationsCmd, cleanUpCommand); |
254 unregisterFileAssociationsCmd, cleanUpCommand); |
259 } |
255 } |
260 |
256 |
261 void addIcon(String mimeType, Path iconFile) { |
257 void addIcon(String mimeType, Path iconFile) { |
262 final int imgSize = getSquareSizeOfImage(iconFile.toFile()); |
258 addIcon(mimeType, iconFile, getSquareSizeOfImage(iconFile.toFile())); |
|
259 } |
|
260 |
|
261 void addIcon(String mimeType, Path iconFile, int imgSize) { |
|
262 imgSize = normalizeIconSize(imgSize); |
263 final String dashMime = mimeType.replace('/', '-'); |
263 final String dashMime = mimeType.replace('/', '-'); |
264 registerIconCmds.add(String.join(" ", "xdg-icon-resource", |
264 registerIconCmds.add(String.join(" ", "xdg-icon-resource", |
265 "install", "--context", "mimetypes", "--size ", |
265 "install", "--context", "mimetypes", "--size", |
266 Integer.toString(imgSize), iconFile.toString(), dashMime)); |
266 Integer.toString(imgSize), iconFile.toString(), dashMime)); |
267 unregisterIconCmds.add(String.join(" ", "xdg-icon-resource", |
267 unregisterIconCmds.add(String.join(" ", "xdg-icon-resource", |
268 "uninstall", dashMime)); |
268 "uninstall", dashMime, "--size", Integer.toString(imgSize))); |
269 } |
269 } |
270 |
270 |
271 void applyTo(Map<String, String> data) { |
271 void applyTo(Map<String, String> data) { |
272 List<String> cmds = new ArrayList<>(); |
272 List<String> cmds = new ArrayList<>(); |
273 |
273 |
322 return srcPath; |
322 return srcPath; |
323 } |
323 } |
324 } |
324 } |
325 |
325 |
326 private void appendFileAssociation(XMLStreamWriter xml, |
326 private void appendFileAssociation(XMLStreamWriter xml, |
327 Map<String, ? super Object> assoc) throws XMLStreamException { |
327 FileAssociation assoc) throws XMLStreamException { |
328 |
328 |
329 xml.writeStartElement("mime-type"); |
329 for (var mimeType : assoc.mimeTypes) { |
330 final String thisMime = FA_CONTENT_TYPE.fetchFrom(assoc).get(0); |
330 xml.writeStartElement("mime-type"); |
331 xml.writeAttribute("type", thisMime); |
331 xml.writeAttribute("type", mimeType); |
332 |
332 |
333 final String description = FA_DESCRIPTION.fetchFrom(assoc); |
333 final String description = assoc.description; |
334 if (description != null && !description.isEmpty()) { |
334 if (description != null && !description.isEmpty()) { |
335 xml.writeStartElement("comment"); |
335 xml.writeStartElement("comment"); |
336 xml.writeCharacters(description); |
336 xml.writeCharacters(description); |
337 xml.writeEndElement(); |
337 xml.writeEndElement(); |
338 } |
338 } |
339 |
339 |
340 final List<String> extensions = FA_EXTENSIONS.fetchFrom(assoc); |
340 for (String ext : assoc.extensions) { |
341 if (extensions == null) { |
|
342 Log.error(I18N.getString( |
|
343 "message.creating-association-with-null-extension")); |
|
344 } else { |
|
345 for (String ext : extensions) { |
|
346 xml.writeStartElement("glob"); |
341 xml.writeStartElement("glob"); |
347 xml.writeAttribute("pattern", "*." + ext); |
342 xml.writeAttribute("pattern", "*." + ext); |
348 xml.writeEndElement(); |
343 xml.writeEndElement(); |
349 } |
344 } |
350 } |
345 |
351 |
346 xml.writeEndElement(); |
352 xml.writeEndElement(); |
347 } |
353 } |
348 } |
354 |
349 |
355 private void createFileAssociationsMimeInfoFile() throws IOException { |
350 private void createFileAssociationsMimeInfoFile() throws IOException { |
356 IOUtils.createXml(mimeInfoFile.srcPath(), xml -> { |
351 IOUtils.createXml(mimeInfoFile.srcPath(), xml -> { |
357 xml.writeStartElement("mime-info"); |
352 xml.writeStartElement("mime-info"); |
358 xml.writeDefaultNamespace( |
353 xml.writeDefaultNamespace( |
359 "http://www.freedesktop.org/standards/shared-mime-info"); |
354 "http://www.freedesktop.org/standards/shared-mime-info"); |
360 |
355 |
361 for (var assoc : associations) { |
356 for (var assoc : associations) { |
362 appendFileAssociation(xml, assoc); |
357 appendFileAssociation(xml, assoc.data); |
363 } |
358 } |
364 |
359 |
365 xml.writeEndElement(); |
360 xml.writeEndElement(); |
366 }); |
361 }); |
367 } |
362 } |
368 |
363 |
369 private Map<String, Path> createFileAssociationIconFiles() throws |
364 private void addFileAssociationIconFiles(ShellCommands shellCommands) |
370 IOException { |
365 throws IOException { |
371 Map<String, Path> mimeTypeWithIconFile = new HashMap<>(); |
366 Set<String> processedMimeTypes = new HashSet<>(); |
372 for (var assoc : associations) { |
367 for (var assoc : associations) { |
373 File customFaIcon = FA_ICON.fetchFrom(assoc); |
368 if (assoc.iconSize <= 0) { |
374 if (customFaIcon == null || !customFaIcon.exists() || getSquareSizeOfImage( |
369 // No icon. |
375 customFaIcon) == 0) { |
|
376 continue; |
370 continue; |
377 } |
371 } |
378 |
372 |
379 String fname = iconFile.srcPath().getFileName().toString(); |
373 for (var mimeType : assoc.data.mimeTypes) { |
380 if (fname.indexOf(".") > 0) { |
374 if (processedMimeTypes.contains(mimeType)) { |
381 fname = fname.substring(0, fname.lastIndexOf(".")); |
375 continue; |
382 } |
376 } |
383 |
377 |
384 DesktopFile faIconFile = new DesktopFile( |
378 processedMimeTypes.add(mimeType); |
385 fname + "_fa_" + customFaIcon.getName()); |
379 |
386 |
380 // Create icon name for mime type from mime type. |
387 IOUtils.copyFile(customFaIcon, faIconFile.srcPath().toFile()); |
381 DesktopFile faIconFile = new DesktopFile(mimeType.replace( |
388 |
382 File.separatorChar, '-') + IOUtils.getSuffix( |
389 mimeTypeWithIconFile.put(FA_CONTENT_TYPE.fetchFrom(assoc).get(0), |
383 assoc.data.iconPath)); |
390 faIconFile.installPath()); |
384 |
391 } |
385 IOUtils.copyFile(assoc.data.iconPath.toFile(), |
392 return mimeTypeWithIconFile; |
386 faIconFile.srcPath().toFile()); |
|
387 |
|
388 shellCommands.addIcon(mimeType, faIconFile.installPath(), |
|
389 assoc.iconSize); |
|
390 } |
|
391 } |
393 } |
392 } |
394 |
393 |
395 private void createDesktopFile(Map<String, String> data) throws IOException { |
394 private void createDesktopFile(Map<String, String> data) throws IOException { |
396 List<String> mimeTypes = getMimeTypeNamesFromFileAssociations(); |
395 List<String> mimeTypes = getMimeTypeNamesFromFileAssociations(); |
397 data.put("DESKTOP_MIMES", "MimeType=" + String.join(";", mimeTypes)); |
396 data.put("DESKTOP_MIMES", "MimeType=" + String.join(";", mimeTypes)); |
401 .setSubstitutionData(data) |
400 .setSubstitutionData(data) |
402 .saveToFile(desktopFile.srcPath()); |
401 .saveToFile(desktopFile.srcPath()); |
403 } |
402 } |
404 |
403 |
405 private List<String> getMimeTypeNamesFromFileAssociations() { |
404 private List<String> getMimeTypeNamesFromFileAssociations() { |
406 return associations.stream().map( |
405 return associations.stream() |
407 a -> FA_CONTENT_TYPE.fetchFrom(a).get(0)).collect( |
406 .map(fa -> fa.data.mimeTypes) |
408 Collectors.toUnmodifiableList()); |
407 .flatMap(List::stream) |
|
408 .collect(Collectors.toUnmodifiableList()); |
409 } |
409 } |
410 |
410 |
411 private static int getSquareSizeOfImage(File f) { |
411 private static int getSquareSizeOfImage(File f) { |
412 try { |
412 try { |
413 BufferedImage bi = ImageIO.read(f); |
413 BufferedImage bi = ImageIO.read(f); |
414 if (bi.getWidth() == bi.getHeight()) { |
414 return Math.max(bi.getWidth(), bi.getHeight()); |
415 return bi.getWidth(); |
|
416 } |
|
417 } catch (IOException e) { |
415 } catch (IOException e) { |
418 Log.verbose(e); |
416 Log.verbose(e); |
419 } |
417 } |
420 return 0; |
418 return 0; |
|
419 } |
|
420 |
|
421 private static int normalizeIconSize(int iconSize) { |
|
422 // If register icon with "uncommon" size, it will be ignored. |
|
423 // So find the best matching "common" size. |
|
424 List<Integer> commonIconSizes = List.of(16, 22, 32, 48, 64, 128); |
|
425 |
|
426 int idx = Collections.binarySearch(commonIconSizes, iconSize); |
|
427 if (idx < 0) { |
|
428 // Given icon size is greater than the largest common icon size. |
|
429 return commonIconSizes.get(commonIconSizes.size() - 1); |
|
430 } |
|
431 |
|
432 if (idx == 0) { |
|
433 // Given icon size is less or equal than the smallest common icon size. |
|
434 return commonIconSizes.get(idx); |
|
435 } |
|
436 |
|
437 int commonIconSize = commonIconSizes.get(idx); |
|
438 if (iconSize < commonIconSize) { |
|
439 // It is better to scale down original icon than to scale it up for |
|
440 // better visual quality. |
|
441 commonIconSize = commonIconSizes.get(idx - 1); |
|
442 } |
|
443 |
|
444 return commonIconSize; |
421 } |
445 } |
422 |
446 |
423 private static String stringifyShellCommands(String... commands) { |
447 private static String stringifyShellCommands(String... commands) { |
424 return stringifyShellCommands(Arrays.asList(commands)); |
448 return stringifyShellCommands(Arrays.asList(commands)); |
425 } |
449 } |
427 private static String stringifyShellCommands(List<String> commands) { |
451 private static String stringifyShellCommands(List<String> commands) { |
428 return String.join(System.lineSeparator(), commands.stream().filter( |
452 return String.join(System.lineSeparator(), commands.stream().filter( |
429 s -> s != null && !s.isEmpty()).collect(Collectors.toList())); |
453 s -> s != null && !s.isEmpty()).collect(Collectors.toList())); |
430 } |
454 } |
431 |
455 |
|
456 private static class LinuxFileAssociation { |
|
457 LinuxFileAssociation(FileAssociation fa) { |
|
458 this.data = fa; |
|
459 if (fa.iconPath != null && Files.isReadable(fa.iconPath)) { |
|
460 iconSize = getSquareSizeOfImage(fa.iconPath.toFile()); |
|
461 } else { |
|
462 iconSize = -1; |
|
463 } |
|
464 } |
|
465 |
|
466 final FileAssociation data; |
|
467 final int iconSize; |
|
468 } |
|
469 |
432 private final PlatformPackage thePackage; |
470 private final PlatformPackage thePackage; |
433 |
471 |
434 private final List<Map<String, ? super Object>> associations; |
472 private final List<LinuxFileAssociation> associations; |
435 |
473 |
436 private final List<Map<String, ? super Object>> launchers; |
474 private final List<Map<String, ? super Object>> launchers; |
437 |
475 |
438 private final OverridableResource iconResource; |
476 private final OverridableResource iconResource; |
439 private final OverridableResource desktopFileResource; |
477 private final OverridableResource desktopFileResource; |