/// <summary> /// Creates a new or updates an existing stub EXE that executes the "0install run" command. /// </summary> /// <seealso cref="BuildRunStub"/> /// <param name="target">The application to be launched via the stub.</param> /// <param name="path">The target path to store the generated EXE file.</param> /// <param name="command">The command argument to be passed to the the "0install run" command; can be <c>null</c>.</param> /// <param name="needsTerminal"><c>true</c> to build a CLI stub, <c>false</c> to build a GUI stub.</param> /// <param name="handler">A callback object used when the the user is to be informed about the progress of long-running operations such as downloads.</param> /// <exception cref="OperationCanceledException">The user canceled the task.</exception> /// <exception cref="InvalidOperationException">There was a compilation error while generating the stub EXE.</exception> /// <exception cref="IOException">A problem occurs while writing to the filesystem.</exception> /// <exception cref="WebException">A problem occured while downloading additional data (such as icons).</exception> /// <exception cref="UnauthorizedAccessException">Write access to the filesystem is not permitted.</exception> private static void CreateOrUpdateRunStub(FeedTarget target, [NotNull] string path, [CanBeNull] string command, bool needsTerminal, [NotNull] ITaskHandler handler) { if (File.Exists(path)) { // Existing stub if (File.GetLastWriteTimeUtc(path) < _libraryInstallTimestamp) { // Built by older version of this library, try to rebuild try { File.Delete(path); } #region Error handling catch (IOException ex) { Log.Warn(string.Format(Resources.UnableToReplaceStub, path)); Log.Warn(ex); return; } catch (UnauthorizedAccessException ex) { Log.Warn(string.Format(Resources.UnableToReplaceStub, path)); Log.Warn(ex); return; } #endregion BuildRunStub(target, path, handler, needsTerminal, command); } } else { // No existing stub, build new one BuildRunStub(target, path, handler, needsTerminal, command); } }
/// <summary> /// Creates a new Windows shortcut. /// </summary> /// <param name="path">The location to place the shortcut at.</param> /// <param name="target">The target the shortcut shall point to.</param> /// <param name="command">The command within <paramref name="target"/> the shortcut shall point to; can be <c>null</c>.</param> /// <param name="iconStore">Stores icon files downloaded from the web as local files.</param> private static void Create(string path, FeedTarget target, string?command, IIconStore iconStore) { if (string.IsNullOrEmpty(command)) { command = Command.NameRun; } var entryPoint = target.Feed.GetEntryPoint(command); bool needsTerminal = (entryPoint != null && entryPoint.NeedsTerminal); string targetPath = Path.Combine(Locations.InstallBase, needsTerminal ? "0install.exe" : "0install-win.exe"); string arguments = "run "; if (!needsTerminal) { arguments += "--no-wait "; } if (command != Command.NameRun) { arguments += "--command " + command.EscapeArgument() + " "; } arguments += target.Uri.ToStringRfc().EscapeArgument(); var icon = target.Feed.GetIcon(Icon.MimeTypeIco, command); Create(path, targetPath, arguments, iconLocation: (icon == null) ? null : iconStore.GetPath(icon), description: target.Feed.GetBestSummary(CultureInfo.CurrentUICulture, command)); }
/// <summary> /// Registers a <see cref="Verb"/> in a registry key. /// </summary> /// <param name="verbKey">The registry key to write the new data to.</param> /// <param name="target">The application being integrated.</param> /// <param name="verb">The verb to register.</param> /// <param name="iconStore">Stores icon files downloaded from the web as local files.</param> /// <param name="machineWide">Assume <paramref name="verbKey"/> is effective machine-wide instead of just for the current user.</param> /// <exception cref="OperationCanceledException">The user canceled the task.</exception> /// <exception cref="IOException">A problem occurred while writing to the filesystem or registry.</exception> /// <exception cref="WebException">A problem occurred while downloading additional data (such as icons).</exception> /// <exception cref="UnauthorizedAccessException">Write access to the filesystem or registry is not permitted.</exception> /// <exception cref="InvalidDataException">The data in <paramref name="verb"/> is invalid.</exception> public static void Register(RegistryKey verbKey, FeedTarget target, Verb verb, IIconStore iconStore, bool machineWide) { string?description = verb.Descriptions.GetBestLanguage(CultureInfo.CurrentUICulture); if (!string.IsNullOrEmpty(description)) { verbKey.SetValue("", description); verbKey.SetValue("MUIVerb", description); } if (verb.Extended) { verbKey.SetValue("Extended", ""); } var icon = target.Feed.GetIcon(Icon.MimeTypeIco, verb.Command); if (icon != null) { verbKey.SetValue("Icon", iconStore.GetPath(icon)); } using var commandKey = verbKey.CreateSubKeyChecked("command"); commandKey.SetValue("", GetLaunchCommandLine(target, verb, iconStore, machineWide)); }
/// <summary> /// Creates a new or updates an existing stub EXE that executes the "0install run" command. /// </summary> /// <seealso cref="BuildRunStub"/> /// <param name="target">The application to be launched via the stub.</param> /// <param name="path">The target path to store the generated EXE file.</param> /// <param name="command">The command argument to be passed to the the "0install run" command; can be <see langword="null"/>.</param> /// <param name="needsTerminal"><see langword="true"/> to build a CLI stub, <see langword="false"/> to build a GUI stub.</param> /// <param name="handler">A callback object used when the the user is to be informed about the progress of long-running operations such as downloads.</param> /// <exception cref="OperationCanceledException">The user canceled the task.</exception> /// <exception cref="InvalidOperationException">There was a compilation error while generating the stub EXE.</exception> /// <exception cref="IOException">A problem occurs while writing to the filesystem.</exception> /// <exception cref="WebException">A problem occured while downloading additional data (such as icons).</exception> /// <exception cref="UnauthorizedAccessException">Write access to the filesystem is not permitted.</exception> private static void CreateOrUpdateRunStub(this FeedTarget target, [NotNull] string path, [CanBeNull] string command, bool needsTerminal, [NotNull] ITaskHandler handler) { if (File.Exists(path)) { // Existing stub // TODO: Find better rebuild discriminator if (File.GetLastWriteTime(path) < Process.GetCurrentProcess().StartTime) { // Outdated, try to rebuild try { File.Delete(path); } #region Error handling catch (IOException ex) { Log.Warn(string.Format(Resources.UnableToReplaceStub, path)); Log.Warn(ex); return; } catch (UnauthorizedAccessException ex) { Log.Warn(string.Format(Resources.UnableToReplaceStub, path)); Log.Warn(ex); return; } #endregion target.BuildRunStub(path, handler, needsTerminal, command); } } else { // No existing stub, build new one target.BuildRunStub(path, handler, needsTerminal, command); } }
/// <summary> /// Registers a URL protocol in the current system. /// </summary> /// <param name="target">The application being integrated.</param> /// <param name="urlProtocol">The URL protocol to register.</param> /// <param name="machineWide">Register the URL protocol machine-wide instead of just for the current user.</param> /// <param name="iconStore">Stores icon files downloaded from the web as local files.</param> /// <param name="accessPoint">Indicates that the handler shall become the default handler for the protocol.</param> /// <exception cref="OperationCanceledException">The user canceled the task.</exception> /// <exception cref="IOException">A problem occurred while writing to the filesystem or registry.</exception> /// <exception cref="WebException">A problem occurred while downloading additional data (such as icons).</exception> /// <exception cref="UnauthorizedAccessException">Write access to the filesystem or registry is not permitted.</exception> /// <exception cref="InvalidDataException">The data in <paramref name="urlProtocol"/> is invalid.</exception> public static void Register(FeedTarget target, Model.Capabilities.UrlProtocol urlProtocol, IIconStore iconStore, bool machineWide, bool accessPoint = false) { #region Sanity checks if (urlProtocol == null) { throw new ArgumentNullException(nameof(urlProtocol)); } if (iconStore == null) { throw new ArgumentNullException(nameof(iconStore)); } #endregion if (string.IsNullOrEmpty(urlProtocol.ID)) { throw new InvalidDataException("Missing ID"); } using var classesKey = RegistryClasses.OpenHive(machineWide); if (urlProtocol.KnownPrefixes.Count == 0) { if (accessPoint) { // Can only be registered invasively by registering protocol ProgID (will replace existing and become default) using var progIDKey = classesKey.CreateSubKeyChecked(urlProtocol.ID); progIDKey.SetValue("", urlProtocol.Descriptions.GetBestLanguage(CultureInfo.CurrentUICulture) ?? urlProtocol.ID); RegistryClasses.Register(progIDKey, target, urlProtocol, iconStore, machineWide); progIDKey.SetValue(ProtocolIndicator, ""); } } else { // Can be registered non-invasively by registering custom ProgID (without becoming default) using (var progIDKey = classesKey.CreateSubKeyChecked(RegistryClasses.Prefix + urlProtocol.ID)) { progIDKey.SetValue("", urlProtocol.Descriptions.GetBestLanguage(CultureInfo.CurrentUICulture) ?? urlProtocol.ID); progIDKey.SetValue(accessPoint ? RegistryClasses.PurposeFlagAccessPoint : RegistryClasses.PurposeFlagCapability, ""); RegistryClasses.Register(progIDKey, target, urlProtocol, iconStore, machineWide); progIDKey.SetValue(ProtocolIndicator, ""); } if (accessPoint) { foreach (var prefix in urlProtocol.KnownPrefixes) { if (WindowsUtils.IsWindowsVista && !machineWide) { using var userChoiceKey = Registry.CurrentUser.CreateSubKeyChecked($@"{RegKeyUserVistaUrlAssoc}\{prefix.Value}\UserChoice"); userChoiceKey.SetValue("ProgID", RegistryClasses.Prefix + urlProtocol.ID); } else { // Setting default invasively by registering protocol ProgID using var progIDKey = classesKey.CreateSubKeyChecked(prefix.Value); RegistryClasses.Register(progIDKey, target, urlProtocol, iconStore, machineWide); progIDKey.SetValue(ProtocolIndicator, ""); } } } } }
/// <inheritdoc/> public override void Apply(AppEntry appEntry, Feed feed, ITaskHandler handler, bool machineWide) { #region Sanity checks if (appEntry == null) { throw new ArgumentNullException("appEntry"); } if (handler == null) { throw new ArgumentNullException("handler"); } #endregion var capability = appEntry.GetCapability <Store.Model.Capabilities.UrlProtocol>(Capability); if (capability == null) { return; } var target = new FeedTarget(appEntry.InterfaceUri, feed); if (WindowsUtils.IsWindows) { Windows.UrlProtocol.Register(target, capability, machineWide, handler, accessPoint: true); } }
private void CreateOrUpdateRunStub(string path, FeedTarget target, bool gui, string?command) { if (File.Exists(path)) { // Existing stub if (File.GetLastWriteTimeUtc(path) < _libraryInstallTimestamp) { // Built by older version of this library, try to rebuild try { File.Delete(path); } #region Error handling catch (Exception ex) when(ex is IOException or UnauthorizedAccessException) { Log.Warn(string.Format(Resources.UnableToReplaceStub, path)); Log.Warn(ex); return; } #endregion BuildRunStub(path, target, command, gui); } } else { // No existing stub, build new one BuildRunStub(path, target, command, gui); } }
/// <summary> /// Creates an application alias in the current system. /// </summary> /// <param name="target">The application being integrated.</param> /// <param name="command">The command within <paramref name="target"/> the alias shall point to; can be <see langword="null"/>.</param> /// <param name="aliasName">The name of the alias to be created.</param> /// <param name="machineWide">Create the alias machine-wide instead of just for the current user.</param> /// <param name="handler">A callback object used when the the user is to be informed about the progress of long-running operations such as downloads.</param> /// <exception cref="OperationCanceledException">The user canceled the task.</exception> /// <exception cref="IOException">A problem occurs while writing to the filesystem or registry.</exception> /// <exception cref="WebException">A problem occured while downloading additional data (such as icons).</exception> /// <exception cref="UnauthorizedAccessException">Write access to the filesystem or registry is not permitted.</exception> public static void Create(FeedTarget target, [CanBeNull] string command, [NotNull] string aliasName, bool machineWide, [NotNull] ITaskHandler handler) { #region Sanity checks if (string.IsNullOrEmpty(aliasName)) { throw new ArgumentNullException("aliasName"); } if (handler == null) { throw new ArgumentNullException("handler"); } #endregion if (string.IsNullOrEmpty(aliasName) || aliasName.IndexOfAny(Path.GetInvalidFileNameChars()) != -1) { throw new IOException(string.Format(Resources.NameInvalidChars, aliasName)); } string stubDirPath = Locations.GetIntegrationDirPath("0install.net", machineWide, "desktop-integration", "aliases"); string stubFilePath = Path.Combine(stubDirPath, aliasName + ".exe"); target.BuildRunStub(stubFilePath, handler, needsTerminal: true, command: command); AddToPath(stubDirPath, machineWide); AddToAppPaths(aliasName + ".exe", stubFilePath, machineWide); }
/// <summary> /// Returns a command-line for executing the "0install run" command. Generates and returns a stub EXE if possible, falls back to directly pointing to the "0install" binary otherwise. /// </summary> /// <param name="target">The application to be launched.</param> /// <param name="command">The command argument to be passed to the the "0install run" command; can be <c>null</c>.</param> /// <param name="machineWide"><c>true</c> place the generated stub in a machine-wide location; <c>false</c> to place it in the current user profile.</param> /// <returns></returns> /// <exception cref="OperationCanceledException">The user canceled the task.</exception> /// <exception cref="InvalidOperationException">There was a compilation error while generating the stub EXE.</exception> /// <exception cref="IOException">A problem occurred while writing to the filesystem.</exception> /// <exception cref="WebException">A problem occurred while downloading additional data (such as icons).</exception> /// <exception cref="UnauthorizedAccessException">Write access to the filesystem is not permitted.</exception> public IReadOnlyList <string> GetRunCommandLine(FeedTarget target, string?command = null, bool machineWide = false) { var entryPoint = target.Feed.GetEntryPoint(command); bool gui = entryPoint is not { NeedsTerminal : true }; try { #if NETFRAMEWORK string hash = (target.Uri + "#" + command).Hash(SHA256.Create()); string exeName = (entryPoint == null) ? FeedUri.Escape(target.Feed.Name) : entryPoint.BinaryName ?? entryPoint.Command; string path = Path.Combine( IntegrationManager.GetDir(machineWide, "stubs", hash), exeName + ".exe"); CreateOrUpdateRunStub(path, target, gui, command); return(new[] { path }); #else return(GetArguments(target.Uri, command, gui) .Prepend(GetBinary(gui)) .ToList()); #endif } #region Error handling catch (InvalidOperationException ex) { // Wrap exception since only certain exception types are allowed throw new IOException(ex.Message, ex); } #endregion }
private string?GetIconPath(FeedTarget target, string?command) { var icon = target.Feed.GetBestIcon(Icon.MimeTypeIco, command); if (icon == null) { return(null); } try { string iconPath = _iconStore.GetFresh(icon); new System.Drawing.Icon(iconPath).Dispose(); // Try to parse icon to ensure it is valid return(iconPath); } #region Error handling catch (Exception ex) when(ex is UriFormatException or WebException) { Log.Warn(ex); } catch (Exception ex) when(ex is IOException or UnauthorizedAccessException) { Log.Warn($"Failed to store {icon}"); Log.Warn(ex); } catch (ArgumentException ex) { Log.Warn($"Failed to parse {icon}"); Log.Warn(ex); } #endregion return(null); }
/// <summary> /// Builds a stub EXE that executes the "0install run" command at a specific path. /// </summary> /// <param name="path">The path to store the generated EXE file.</param> /// <param name="target">The application to be launched.</param> /// <param name="command">The command argument to be passed to the the "0install run" command; can be <c>null</c>.</param> /// <param name="gui"><c>true</c> to build a GUI stub, <c>false</c> to build a CLI stub.</param> /// <exception cref="OperationCanceledException">The user canceled the task.</exception> /// <exception cref="InvalidOperationException">There was a compilation error while generating the stub EXE.</exception> /// <exception cref="IOException">A problem occurred while writing to the filesystem.</exception> /// <exception cref="WebException">A problem occurred while downloading additional data (such as icons).</exception> /// <exception cref="UnauthorizedAccessException">Write access to the filesystem is not permitted.</exception> public void BuildRunStub(string path, FeedTarget target, string?command = null, bool gui = false) { #region Sanity checks if (string.IsNullOrEmpty(path)) { throw new ArgumentNullException(nameof(path)); } #endregion var compilerParameters = new CompilerParameters { OutputAssembly = path, GenerateExecutable = true, TreatWarningsAsErrors = true, ReferencedAssemblies = { "System.dll" }, CompilerOptions = gui ? "/target:winexe" : "/target:exe" }; string?iconPath = GetIconPath(target, command); if (iconPath != null) { compilerParameters.CompilerOptions += " /win32icon:" + iconPath.EscapeArgument(); } compilerParameters.CompileCSharp( GetCode( exe: GetBinary(gui), arguments: GetArguments(target.Uri, command, gui), title: target.Feed.GetBestName(CultureInfo.CurrentUICulture, command)), Manifest); }
/// <summary> /// Creates an application alias in the current system. /// </summary> /// <param name="target">The application being integrated.</param> /// <param name="command">The command within <paramref name="target"/> the alias shall point to; can be <c>null</c>.</param> /// <param name="aliasName">The name of the alias to be created.</param> /// <param name="machineWide">Create the alias machine-wide instead of just for the current user.</param> /// <param name="handler">A callback object used when the the user is to be informed about the progress of long-running operations such as downloads.</param> /// <exception cref="OperationCanceledException">The user canceled the task.</exception> /// <exception cref="IOException">A problem occurs while writing to the filesystem or registry.</exception> /// <exception cref="WebException">A problem occured while downloading additional data (such as icons).</exception> /// <exception cref="UnauthorizedAccessException">Write access to the filesystem or registry is not permitted.</exception> public static void Create(FeedTarget target, [CanBeNull] string command, [NotNull] string aliasName, bool machineWide, [NotNull] ITaskHandler handler) { #region Sanity checks if (string.IsNullOrEmpty(aliasName)) { throw new ArgumentNullException(nameof(aliasName)); } if (handler == null) { throw new ArgumentNullException(nameof(handler)); } #endregion if (string.IsNullOrEmpty(aliasName) || aliasName.IndexOfAny(Path.GetInvalidFileNameChars()) != -1) { throw new IOException(string.Format(Resources.NameInvalidChars, aliasName)); } string stubDirPath = GetStubDir(machineWide); PathEnv.AddDir(stubDirPath, machineWide); string stubFilePath = Path.Combine(stubDirPath, aliasName + ".exe"); StubBuilder.BuildRunStub(target, stubFilePath, handler, needsTerminal: true, command: command); AddToAppPaths(aliasName + ".exe", stubFilePath, machineWide); }
/// <summary> /// Creates a new Windows shortcut. /// </summary> /// <param name="path">The location to place the shorcut at.</param> /// <param name="target">The target the shortcut shall point to.</param> /// <param name="command">The command within <paramref name="target"/> the shorcut shall point to; can be <see langword="null"/>.</param> /// <param name="handler">A callback object used when the the user is to be informed about the progress of long-running operations such as downloads.</param> /// <param name="machineWide">Create the shortcut machine-wide instead of just for the current user.</param> private static void Create([NotNull] string path, FeedTarget target, [CanBeNull] string command, [NotNull] ITaskHandler handler, bool machineWide = false) { if (string.IsNullOrEmpty(command)) { command = Command.NameRun; } var entryPoint = target.Feed.GetEntryPoint(command); bool needsTerminal = (entryPoint != null && entryPoint.NeedsTerminal); string arguments = "run "; if (!needsTerminal) { arguments += "--no-wait "; } if (command != Command.NameRun) { arguments += "--command " + command.EscapeArgument() + " "; } arguments += target.Uri.ToStringRfc().EscapeArgument(); var icon = target.Feed.GetIcon(Icon.MimeTypeIco, command); Create(path, targetPath: Path.Combine(Locations.InstallBase, needsTerminal ? "0install.exe" : "0install-win.exe"), arguments: arguments, iconLocation: (icon == null) ? null : IconProvider.GetIconPath(icon, handler, machineWide), description: target.Feed.GetBestSummary(CultureInfo.CurrentUICulture, command)); }
/// <inheritdoc/> public override void Apply(AppEntry appEntry, Feed feed, ITaskHandler handler, bool machineWide) { #region Sanity checks if (appEntry == null) { throw new ArgumentNullException("appEntry"); } if (handler == null) { throw new ArgumentNullException("handler"); } #endregion var capability = appEntry.GetCapability <Store.Model.Capabilities.ContextMenu>(Capability); if (capability == null) { return; } var target = new FeedTarget(appEntry.InterfaceUri, feed); if (WindowsUtils.IsWindows) { Windows.ContextMenu.Apply(target, capability, machineWide, handler); } else if (UnixUtils.IsUnix) { Unix.ContextMenu.Apply(target, capability, machineWide, handler); } }
/// <summary> /// Registers a <see cref="Verb"/> in a registry key. /// </summary> /// <param name="verbKey">The registry key to write the new data to.</param> /// <param name="target">The application being integrated.</param> /// <param name="verb">The verb to register.</param> /// <param name="iconStore">Stores icon files downloaded from the web as local files.</param> /// <param name="machineWide">Assume <paramref name="verbKey"/> is effective machine-wide instead of just for the current user.</param> /// <exception cref="OperationCanceledException">The user canceled the task.</exception> /// <exception cref="IOException">A problem occurred while writing to the filesystem or registry.</exception> /// <exception cref="WebException">A problem occurred while downloading additional data (such as icons).</exception> /// <exception cref="UnauthorizedAccessException">Write access to the filesystem or registry is not permitted.</exception> public static void Register(RegistryKey verbKey, FeedTarget target, Verb verb, IIconStore iconStore, bool machineWide) { string?description = verb.Descriptions.GetBestLanguage(CultureInfo.CurrentUICulture); verbKey.SetOrDelete("", description); verbKey.SetOrDelete("MUIVerb", description); verbKey.SetOrDelete("MultiSelectModel", verb.SingleElementOnly ? "Single" : null); if (verb.Extended) { verbKey.SetValue("Extended", ""); } else { verbKey.DeleteValue("Extended", throwOnMissingValue: false); } var icon = target.Feed.GetBestIcon(Icon.MimeTypeIco, verb.Command) ?? target.Feed.Icons.GetIcon(Icon.MimeTypeIco); verbKey.SetOrDelete("Icon", icon?.To(iconStore.GetFresh)); using var commandKey = verbKey.CreateSubKeyChecked("command"); commandKey.SetValue("", GetLaunchCommandLine(target, verb, iconStore, machineWide)); }
/// <summary> /// Creates an application alias in the current system. /// </summary> /// <param name="target">The application being integrated.</param> /// <param name="command">The command within <paramref name="target"/> the alias shall point to; can be <c>null</c>.</param> /// <param name="aliasName">The name of the alias to be created.</param> /// <param name="machineWide">Create the alias machine-wide instead of just for the current user.</param> /// <param name="iconStore">Stores icon files downloaded from the web as local files.</param> /// <exception cref="OperationCanceledException">The user canceled the task.</exception> /// <exception cref="IOException">A problem occurred while writing to the filesystem or registry.</exception> /// <exception cref="WebException">A problem occurred while downloading additional data (such as icons).</exception> /// <exception cref="UnauthorizedAccessException">Write access to the filesystem or registry is not permitted.</exception> public static void Create(FeedTarget target, string?command, string aliasName, IIconStore iconStore, bool machineWide) { #region Sanity checks if (string.IsNullOrEmpty(aliasName)) { throw new ArgumentNullException(nameof(aliasName)); } if (iconStore == null) { throw new ArgumentNullException(nameof(iconStore)); } #endregion #if NETFRAMEWORK string stubDirPath = GetStubDir(machineWide); PathEnv.AddDir(stubDirPath, machineWide); string stubFilePath = Path.Combine(stubDirPath, aliasName + ".exe"); new StubBuilder(iconStore).BuildRunStub(stubFilePath, target, command); if (machineWide || WindowsUtils.IsWindows7) { var hive = machineWide ? Registry.LocalMachine : Registry.CurrentUser; using var appPathsKey = hive.CreateSubKeyChecked(RegKeyAppPaths); using var exeKey = appPathsKey.CreateSubKeyChecked(aliasName + ".exe"); exeKey.SetValue("", stubFilePath); } #else throw new PlatformNotSupportedException("Generating Windows aliases is not supported by the .NET Core version of Zero Install."); #endif }
/// <summary> /// Generates a command-line string for launching a <see cref="Verb"/>. /// </summary> /// <param name="target">The application being integrated.</param> /// <param name="verb">The verb to get to launch command for.</param> /// <param name="iconStore">Stores icon files downloaded from the web as local files.</param> /// <param name="machineWide">Store the stub in a machine-wide directory instead of just for the current user.</param> /// <exception cref="IOException">A problem occurred while writing to the filesystem.</exception> /// <exception cref="WebException">A problem occurred while downloading additional data (such as icons).</exception> /// <exception cref="InvalidOperationException">Write access to the filesystem is not permitted.</exception> internal static string GetLaunchCommandLine(FeedTarget target, Verb verb, IIconStore iconStore, bool machineWide) { IEnumerable <string> GetCommandLine() { try { return(new StubBuilder(iconStore).GetRunCommandLine(target, verb.Command, machineWide)); } #region Error handling catch (InvalidOperationException ex) { // Wrap exception since only certain exception types are allowed throw new IOException(ex.Message, ex); } #endregion } if (verb.Arguments.Count == 0) { string arguments = string.IsNullOrEmpty(verb.ArgumentsLiteral) ? "\"%V\"" : verb.ArgumentsLiteral; return(GetCommandLine().JoinEscapeArguments() + " " + arguments); } return(GetCommandLine() .Concat(verb.Arguments.Select(x => x.Value)) .JoinEscapeArguments() .Replace("${item}", "\"%V\"")); }
/// <summary> /// Registers a <see cref="VerbCapability"/> in a registry key. /// </summary> /// <param name="registryKey">The registry key to write the new data to.</param> /// <param name="target">The application being integrated.</param> /// <param name="capability">The capability to register.</param> /// <param name="iconStore">Stores icon files downloaded from the web as local files.</param> /// <param name="machineWide">Assume <paramref name="registryKey"/> is effective machine-wide instead of just for the current user.</param> /// <exception cref="OperationCanceledException">The user canceled the task.</exception> /// <exception cref="IOException">A problem occurred while writing to the filesystem or registry.</exception> /// <exception cref="WebException">A problem occurred while downloading additional data (such as icons).</exception> /// <exception cref="UnauthorizedAccessException">Write access to the filesystem or registry is not permitted.</exception> public static void Register(RegistryKey registryKey, FeedTarget target, VerbCapability capability, IIconStore iconStore, bool machineWide) { #region Sanity checks if (capability == null) { throw new ArgumentNullException(nameof(capability)); } if (iconStore == null) { throw new ArgumentNullException(nameof(iconStore)); } #endregion if ((capability.GetIcon(Icon.MimeTypeIco) ?? target.Feed.Icons.GetIcon(Icon.MimeTypeIco)) is {} icon) { using var iconKey = registryKey.CreateSubKeyChecked("DefaultIcon"); iconKey.SetValue("", iconStore.GetFresh(icon) + ",0"); } foreach (var verb in capability.Verbs) { using var verbKey = registryKey.CreateSubKeyChecked($@"shell\{verb.Name}"); Register(verbKey, target, verb, iconStore, machineWide); } // Prevent conflicts with existing entries registryKey.DeleteSubKeyTree(@"shell\ddeexec", throwOnMissingSubKey: false); }
/// <inheritdoc/> public override void Apply(AppEntry appEntry, Feed feed, IIconStore iconStore, bool machineWide) { #region Sanity checks if (appEntry == null) { throw new ArgumentNullException(nameof(appEntry)); } if (iconStore == null) { throw new ArgumentNullException(nameof(iconStore)); } #endregion ValidateName(); var target = new FeedTarget(appEntry.InterfaceUri, feed); if (WindowsUtils.IsWindows) { Windows.AppAlias.Create(target, Command, Name, iconStore, machineWide); } else if (UnixUtils.IsUnix) { Unix.AppAlias.Create(target, Command, Name, iconStore, machineWide); } }
/// <inheritdoc/> public AppEntry AddApp(FeedTarget target) { var appEntry = AddAppInternal(target); Finish(); return(appEntry); }
/// <summary> /// Returns a command-line for executing the "0install run" command. /// Generates and returns a stub EXE if possible, falls back to directly pointing to the "0install" EXE otherwise. /// </summary> /// <param name="target">The application to be launched.</param> /// <param name="command">The command argument to be passed to the the "0install run" command; can be <c>null</c>.</param> /// <param name="machineWide"><c>true</c> place the generated stub in a machine-wide location; <c>false</c> to place it in the current user profile.</param> /// <exception cref="OperationCanceledException">The user canceled the task.</exception> /// <exception cref="InvalidOperationException">There was a compilation error while generating the stub EXE.</exception> /// <exception cref="IOException">A problem occurred while writing to the filesystem.</exception> /// <exception cref="WebException">A problem occurred while downloading additional data (such as icons).</exception> /// <exception cref="UnauthorizedAccessException">Write access to the filesystem is not permitted.</exception> public IReadOnlyList <string> GetRunCommandLine(FeedTarget target, string?command = null, bool machineWide = false) { string targetKey = target.Uri + "#" + command; var entryPoint = target.Feed.GetEntryPoint(command); bool gui = entryPoint is not { NeedsTerminal : true }; string targetHash = targetKey.Hash(SHA256.Create()); string exeName = (entryPoint == null) ? FeedUri.Escape(target.Feed.Name) : entryPoint.BinaryName ?? entryPoint.Command; string path = Path.Combine( IntegrationManager.GetDir(machineWide, "stubs", targetHash), exeName + ".exe"); #if !DEBUG try #endif { CreateOrUpdateRunStub(path, target, gui, command); return(new[] { path }); } #if !DEBUG catch (Exception ex) { var exe = GetExe(gui); Log.Error($"Failed to generate stub EXE for {targetKey}. Falling back to using '{exe}' directly.", ex); return(GetArguments(target.Uri, command, gui) .Prepend(Path.Combine(Locations.InstallBase, exe)) .ToList()); } #endif }
/// <summary> /// Registers a <see cref="Store.Model.Capabilities.VerbCapability"/> in a registry key. /// </summary> /// <param name="registryKey">The registry key to write the new data to.</param> /// <param name="target">The application being integrated.</param> /// <param name="capability">The capability to register.</param> /// <param name="machineWide">Assume <paramref name="registryKey"/> is effective machine-wide instead of just for the current user.</param> /// <param name="handler">A callback object used when the the user is to be informed about the progress of long-running operations such as downloads.</param> /// <exception cref="OperationCanceledException">The user canceled the task.</exception> /// <exception cref="IOException">A problem occurs while writing to the filesystem or registry.</exception> /// <exception cref="WebException">A problem occured while downloading additional data (such as icons).</exception> /// <exception cref="UnauthorizedAccessException">Write access to the filesystem or registry is not permitted.</exception> /// <exception cref="InvalidDataException">The data in <paramref name="capability"/> is invalid.</exception> internal static void RegisterVerbCapability(RegistryKey registryKey, FeedTarget target, Store.Model.Capabilities.VerbCapability capability, bool machineWide, ITaskHandler handler) { #region Sanity checks if (capability == null) { throw new ArgumentNullException(nameof(capability)); } if (handler == null) { throw new ArgumentNullException(nameof(handler)); } #endregion if (capability is Store.Model.Capabilities.UrlProtocol) { registryKey.SetValue(UrlProtocol.ProtocolIndicator, ""); } string description = capability.Descriptions.GetBestLanguage(CultureInfo.CurrentUICulture); if (description != null) { registryKey.SetValue("", description); } // Write verb command information using (var shellKey = registryKey.CreateSubKeyChecked("shell")) { foreach (var verb in capability.Verbs) { using (var verbKey = shellKey.CreateSubKeyChecked(verb.Name)) { string verbDescription = verb.Descriptions.GetBestLanguage(CultureInfo.CurrentUICulture); if (verbDescription != null) { verbKey.SetValue("", verbDescription); } if (verb.Extended) { verbKey.SetValue(RegValueExtended, ""); } using (var commandKey = verbKey.CreateSubKeyChecked("command")) commandKey.SetValue("", GetLaunchCommandLine(target, verb, machineWide, handler)); // Prevent conflicts with existing entries shellKey.DeleteSubKey("ddeexec", throwOnMissingSubKey: false); } } } // Set specific icon if available, fall back to referencing the icon embedded in the stub EXE var icon = capability.GetIcon(Icon.MimeTypeIco) ?? target.Feed.GetIcon(Icon.MimeTypeIco); if (icon != null) { using (var iconKey = registryKey.CreateSubKeyChecked(RegSubKeyIcon)) iconKey.SetValue("", IconProvider.GetIconPath(icon, handler, machineWide) + ",0"); } }
public void TestBuildStubNeedsTerminal() { Skip.IfNot(WindowsUtils.IsWindows, "StubBuilder is only used on Windows"); var target = new FeedTarget(FeedTest.Test1Uri, FeedTest.CreateTestFeed()); using var tempFile = new TemporaryFile("0install-unit-tests"); StubBuilder.BuildRunStub(target, tempFile, _iconStoreMock.Object, needsTerminal: true); }
/// <summary> /// Registers a URL protocol in the current system. /// </summary> /// <param name="target">The application being integrated.</param> /// <param name="urlProtocol">The URL protocol to register.</param> /// <param name="machineWide">Register the URL protocol machine-wide instead of just for the current user.</param> /// <param name="handler">A callback object used when the the user is to be informed about the progress of long-running operations such as downloads.</param> /// <param name="accessPoint">Indicates that the handler shall become the default handler for the protocol.</param> /// <exception cref="OperationCanceledException">The user canceled the task.</exception> /// <exception cref="IOException">A problem occurs while writing to the filesystem or registry.</exception> /// <exception cref="WebException">A problem occured while downloading additional data (such as icons).</exception> /// <exception cref="UnauthorizedAccessException">Write access to the filesystem or registry is not permitted.</exception> /// <exception cref="InvalidDataException">The data in <paramref name="urlProtocol"/> is invalid.</exception> public static void Register(FeedTarget target, [NotNull] Store.Model.Capabilities.UrlProtocol urlProtocol, bool machineWide, [NotNull] ITaskHandler handler, bool accessPoint = false) { #region Sanity checks if (urlProtocol == null) { throw new ArgumentNullException(nameof(urlProtocol)); } if (handler == null) { throw new ArgumentNullException(nameof(handler)); } #endregion if (string.IsNullOrEmpty(urlProtocol.ID)) { throw new InvalidDataException("Missing ID"); } var hive = machineWide ? Registry.LocalMachine : Registry.CurrentUser; if (urlProtocol.KnownPrefixes.Count == 0) { if (accessPoint) { // Can only be registered invasively by registering protocol ProgID (will replace existing and become default) using (var progIDKey = hive.CreateSubKeyChecked(FileType.RegKeyClasses + @"\" + urlProtocol.ID)) FileType.RegisterVerbCapability(progIDKey, target, urlProtocol, machineWide, handler); } } else { // Can be registered non-invasively by registering custom ProgID (without becoming default) using (var progIDKey = hive.CreateSubKeyChecked(FileType.RegKeyClasses + @"\" + FileType.RegKeyPrefix + urlProtocol.ID)) { // Add flag to remember whether created for capability or access point progIDKey.SetValue(accessPoint ? FileType.PurposeFlagAccessPoint : FileType.PurposeFlagCapability, ""); FileType.RegisterVerbCapability(progIDKey, target, urlProtocol, machineWide, handler); } if (accessPoint) { foreach (var prefix in urlProtocol.KnownPrefixes) { if (WindowsUtils.IsWindowsVista && !machineWide) { using (var someKey = Registry.CurrentUser.CreateSubKeyChecked(RegKeyUserVistaUrlAssoc + @"\" + prefix.Value + @"\UserChoice")) someKey.SetValue("ProgID", FileType.RegKeyPrefix + urlProtocol.ID); } else { // Setting default invasively by registering protocol ProgID using (var progIDKey = hive.CreateSubKeyChecked(FileType.RegKeyClasses + @"\" + prefix.Value)) FileType.RegisterVerbCapability(progIDKey, target, urlProtocol, machineWide, handler); } } } } }
/// <summary> /// Registers an application as a candidate for a default program for some service in the current system. This can only be applied machine-wide, not per user. /// </summary> /// <param name="target">The application being integrated.</param> /// <param name="defaultProgram">The default program information to be registered.</param> /// <param name="iconStore">Stores icon files downloaded from the web as local files.</param> /// <param name="accessPoint">Indicates that the program should be set as the current default for the service it provides.</param> /// <exception cref="OperationCanceledException">The user canceled the task.</exception> /// <exception cref="IOException">A problem occurred while writing to the filesystem or registry.</exception> /// <exception cref="WebException">A problem occurred while downloading additional data (such as icons).</exception> /// <exception cref="UnauthorizedAccessException">Write access to the filesystem or registry is not permitted.</exception> /// <exception cref="InvalidDataException">The data in <paramref name="defaultProgram"/> is invalid.</exception> public static void Register(FeedTarget target, Model.Capabilities.DefaultProgram defaultProgram, IIconStore iconStore, bool accessPoint = false) { #region Sanity checks if (defaultProgram == null) { throw new ArgumentNullException(nameof(defaultProgram)); } if (iconStore == null) { throw new ArgumentNullException(nameof(iconStore)); } #endregion if (string.IsNullOrEmpty(defaultProgram.ID)) { throw new InvalidDataException("Missing ID"); } if (string.IsNullOrEmpty(defaultProgram.Service)) { throw new InvalidDataException("Missing Service"); } using var serviceKey = Registry.LocalMachine.CreateSubKeyChecked($@"{RegKeyMachineClients}\{defaultProgram.Service}"); using (var appKey = serviceKey.CreateSubKeyChecked(defaultProgram.ID)) { appKey.SetValue("", target.Feed.Name); appKey.SetValue(accessPoint ? RegistryClasses.PurposeFlagAccessPoint : RegistryClasses.PurposeFlagCapability, ""); RegistryClasses.Register(appKey, target, defaultProgram, iconStore, machineWide: true); // Set callbacks for Windows SPAD using (var installInfoKey = appKey.CreateSubKeyChecked(RegSubKeyInstallInfo)) { string exePath = Path.Combine(Locations.InstallBase, "0install-win.exe"); installInfoKey.SetValue(RegValueReinstallCommand, new[] { exePath, "integrate", "--machine", "--batch", "--add", "defaults", target.Uri.ToStringRfc() }.JoinEscapeArguments()); installInfoKey.SetValue(RegValueShowIconsCommand, new[] { exePath, "integrate", "--machine", "--batch", "--add", MenuEntry.CategoryName, "--add", DesktopIcon.CategoryName, target.Uri.ToStringRfc() }.JoinEscapeArguments()); installInfoKey.SetValue(RegValueHideIconsCommand, new[] { exePath, "integrate", "--machine", "--batch", "--remove", MenuEntry.CategoryName, "--remove", DesktopIcon.CategoryName, target.Uri.ToStringRfc() }.JoinEscapeArguments()); installInfoKey.SetValue(RegValueIconsVisible, 0, RegistryValueKind.DWord); } if (defaultProgram.Service == Model.Capabilities.DefaultProgram.ServiceMail) { var mailToProtocol = new Model.Capabilities.UrlProtocol { Verbs = { new Verb { Name = Verb.NameOpen } } }; using var mailToKey = appKey.CreateSubKeyChecked(@"Protocols\mailto"); RegistryClasses.Register(mailToKey, target, mailToProtocol, iconStore, machineWide: true); } } if (accessPoint) { serviceKey.SetValue("", defaultProgram.ID); } }
public void TestBuildStubNeedsTerminal() { if (!WindowsUtils.IsWindows) { Assert.Ignore("StubBuilder is only used on Windows"); } var target = new FeedTarget(FeedTest.Test1Uri, FeedTest.CreateTestFeed()); using (var tempFile = new TemporaryFile("0install-unit-tests")) StubBuilder.BuildRunStub(target, tempFile, new SilentTaskHandler(), needsTerminal: true); }
private AppEntry CreateAppEntry(IIntegrationManager integrationManager, FeedTarget target) { BackgroundDownload(target.Uri); try { Log.Info("Creating app entry for " + target.Uri.ToStringRfc()); return(integrationManager.AddApp(target)); } catch (InvalidOperationException ex) when(ex.GetType() == typeof(InvalidOperationException)) { Log.Warn("Attempting to handle race condition while creating app entry for " + target.Uri.ToStringRfc()); return(integrationManager.AppList[target.Uri]); } }
/// <summary> /// Registers a COM server in the current system. /// </summary> /// <param name="target">The application being integrated.</param> /// <param name="comServer">The COM server to be registered.</param> /// <param name="machineWide">Register the COM server machine-wide instead of just for the current user.</param> /// <param name="iconStore">Stores icon files downloaded from the web as local files.</param> /// <exception cref="OperationCanceledException">The user canceled the task.</exception> /// <exception cref="IOException">A problem occurred while writing to the filesystem or registry.</exception> /// <exception cref="WebException">A problem occurred while downloading additional data (such as icons).</exception> /// <exception cref="UnauthorizedAccessException">Write access to the filesystem or registry is not permitted.</exception> public static void Register(FeedTarget target, Model.Capabilities.ComServer comServer, IIconStore iconStore, bool machineWide) { #region Sanity checks if (comServer == null) { throw new ArgumentNullException(nameof(comServer)); } if (iconStore == null) { throw new ArgumentNullException(nameof(iconStore)); } #endregion // TODO: Implement }
/// <summary> /// Adds a context menu entry to the current system. /// </summary> /// <param name="target">The application being integrated.</param> /// <param name="contextMenu">The context menu entry to add.</param> /// <param name="machineWide">Add the context menu entry machine-wide instead of just for the current user.</param> /// <param name="iconStore">Stores icon files downloaded from the web as local files.</param> /// <exception cref="OperationCanceledException">The user canceled the task.</exception> /// <exception cref="IOException">A problem occurred while writing to the filesystem.</exception> /// <exception cref="WebException">A problem occurred while downloading additional data (such as icons).</exception> /// <exception cref="UnauthorizedAccessException">Write access to the filesystem is not permitted.</exception> public static void Apply(FeedTarget target, Model.Capabilities.ContextMenu contextMenu, IIconStore iconStore, bool machineWide) { #region Sanity checks if (contextMenu == null) { throw new ArgumentNullException(nameof(contextMenu)); } if (iconStore == null) { throw new ArgumentNullException(nameof(iconStore)); } #endregion // TODO: Implement }
/// <summary> /// Registers an application as a candidate for a default program for some service in the current system. /// </summary> /// <param name="target">The application being integrated.</param> /// <param name="defaultProgram">The default program information to be registered.</param> /// <param name="machineWide">Apply the registration machine-wide instead of just for the current user.</param> /// <param name="iconStore">Stores icon files downloaded from the web as local files.</param> /// <param name="accessPoint">Indicates that the program should be set as the current default for the service it provides.</param> /// <exception cref="OperationCanceledException">The user canceled the task.</exception> /// <exception cref="IOException">A problem occurred while writing to the filesystem.</exception> /// <exception cref="WebException">A problem occurred while downloading additional data (such as icons).</exception> /// <exception cref="UnauthorizedAccessException">Write access to the filesystem is not permitted.</exception> public static void Register(FeedTarget target, Model.Capabilities.DefaultProgram defaultProgram, IIconStore iconStore, bool machineWide, bool accessPoint = false) { #region Sanity checks if (defaultProgram == null) { throw new ArgumentNullException(nameof(defaultProgram)); } if (iconStore == null) { throw new ArgumentNullException(nameof(iconStore)); } #endregion // TODO: Implement }