Skip to content

Conversation

@1208nn
Copy link

@1208nn 1208nn commented Oct 26, 2024

Fixes #2936

Add new builtin shortcut {active_office_file} to quickly access the active office file.

  • Add a new method GetActiveOfficeFilePath in Flow.Launcher.Infrastructure/Helper.cs to retrieve the active office file path.
  • Add a new builtin shortcut {active_office_file} in Flow.Launcher.Infrastructure/UserSettings/Settings.cs to use the GetActiveOfficeFilePath method.
  • Update the BuiltinShortcuts list in Flow.Launcher.Infrastructure/UserSettings/Settings.cs to include the new {active_office_file} shortcut.

For more details, open the Copilot Workspace session.

Fixes Flow-Launcher#2936

Add new builtin shortcut `{active_office_file}` to quickly access the active office file.

* Add a new method `GetActiveOfficeFilePath` in `Flow.Launcher.Infrastructure/Helper.cs` to retrieve the active office file path.
* Add a new builtin shortcut `{active_office_file}` in `Flow.Launcher.Infrastructure/UserSettings/Settings.cs` to use the `GetActiveOfficeFilePath` method.
* Update the `BuiltinShortcuts` list in `Flow.Launcher.Infrastructure/UserSettings/Settings.cs` to include the new `{active_office_file}` shortcut.

---

For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/Flow-Launcher/Flow.Launcher/issues/2936?shareId=XXXX-XXXX-XXXX-XXXX).
@prlabeler prlabeler bot added bug Something isn't working enhancement New feature or request labels Oct 26, 2024
@github-actions

This comment has been minimized.

@jjw24 jjw24 removed the bug Something isn't working label Nov 1, 2024
@github-actions
Copy link

@check-spelling-bot Report

🔴 Please review

See the 📂 files view, the 📜action log, or 📝 job summary for details.

❌ Errors Count
❌ forbidden-pattern 22
⚠️ ignored-expect-variant 1
⚠️ non-alpha-in-dictionary 19

See ❌ Event descriptions for more information.

Using only_check_changed_files is incompatible with expect.txt.
To accept the items listed, you should add them to allow.txt.

If the flagged items are 🤯 false positives

If items relate to a ...

  • binary file (or some other file you wouldn't want to check at all).

    Please add a file path to the excludes.txt file matching the containing file.

    File paths are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your files.

    ^ refers to the file's path from the root of the repository, so ^README\.md$ would exclude README.md (on whichever branch you're using).

  • well-formed pattern.

    If you can write a pattern that would match it,
    try adding it to the patterns.txt file.

    Patterns are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your lines.

    Note that patterns can't match multiline strings.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds a new builtin shortcut {active_office_file} to retrieve the file path of the currently active Microsoft Office document (Word, Excel, or PowerPoint). However, the implementation contains several critical bugs that prevent it from compiling or functioning correctly.

Key Changes

  • Added GetActiveOfficeFilePath() method in Helper.cs to retrieve the active Office file path
  • Registered the new {active_office_file} shortcut in Settings.cs BuiltinShortcuts collection

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 14 comments.

File Description
Flow.Launcher.Infrastructure/Helper.cs Adds P/Invoke declarations and COM automation logic to detect and retrieve the active Office file path from Word, Excel, or PowerPoint applications
Flow.Launcher.Infrastructure/UserSettings/Settings.cs Registers the new {active_office_file} builtin shortcut with localization key and handler method

Critical Issues Found:
The implementation has fundamental bugs that will prevent compilation:

  1. Incorrect namespace usage - Microsoft.Win32 is used as an alias but doesn't contain the required P/Invoke methods (OpenProcess, GetModuleFileNameEx) or the Dispatch type for COM interop
  2. Missing proper P/Invoke declarations for Win32 APIs
  3. Missing error handling for COM exceptions and edge cases
  4. Resource leak - process handles are not closed
  5. Nullable return type not declared despite returning null

The code needs significant rework using proper P/Invoke declarations and COM interop patterns (similar to FileExplorerHelper.cs) before it can function correctly.

Comments suppressed due to low confidence (1)

Flow.Launcher.Infrastructure/Helper.cs:121

  • This assignment to threadProcessId is useless, since its value is never read.
            var threadProcessId = GetWindowThreadProcessId(window, out var processId);

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}
else
{
return null;
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method has #nullable enable at the top of the file but returns null on line 114 without declaring the return type as nullable. The method signature should be:

public static string? GetActiveOfficeFilePath()

This matches the nullable pattern used by GetActiveExplorerPath() in FileExplorerHelper.cs.

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +10
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Linq;
using System.Collections.Generic;
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several of these using statements appear to be unused based on the actual code:

  • System.Diagnostics is not used
  • System.Linq is not used
  • System.Collections.Generic is not used

Remove unused imports to keep the code clean.

Suggested change
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Linq;
using System.Collections.Generic;
using System.Runtime.InteropServices;

Copilot uses AI. Check for mistakes.
Comment on lines +98 to +99
var handle = win32api.OpenProcess(win32api.PROCESS_QUERY_INFORMATION | win32api.PROCESS_VM_READ, false, pid);
var exePath = win32api.GetModuleFileNameEx(handle, 0);
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These lines attempt to use non-existent methods from Microsoft.Win32. The OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ, and GetModuleFileNameEx do not exist in this namespace.

You need to:

  1. Add proper P/Invoke declarations for OpenProcess and GetModuleFileNameEx from kernel32.dll
  2. Define the PROCESS_QUERY_INFORMATION and PROCESS_VM_READ constants
  3. Properly handle and close the process handle to avoid resource leaks

Example:

[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr OpenProcess(uint processAccess, bool bInheritHandle, int processId);

[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);

private const uint PROCESS_QUERY_INFORMATION = 0x0400;
private const uint PROCESS_VM_READ = 0x0010;

Copilot uses AI. Check for mistakes.
public static string GetActiveOfficeFilePath()
{
var pid = GetActiveWindowProcessId();
var handle = win32api.OpenProcess(win32api.PROCESS_QUERY_INFORMATION | win32api.PROCESS_VM_READ, false, pid);
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The process handle obtained from OpenProcess is never closed, causing a resource leak. Process handles should be closed when no longer needed using CloseHandle.

Consider using a using statement with a SafeHandle wrapper, or ensure the handle is closed in a finally block:

IntPtr handle = IntPtr.Zero;
try
{
    handle = OpenProcess(...);
    // use handle
}
finally
{
    if (handle != IntPtr.Zero)
        CloseHandle(handle);
}

Copilot uses AI. Check for mistakes.
Comment on lines +100 to +108
if (exePath.ToLower().Contains("winword.exe"))
{
return Path.GetFullPath(new win32api.Dispatch("Word.Application").ActiveDocument.FullName);
}
else if (exePath.ToLower().Contains("powerpnt.exe"))
{
return Path.GetFullPath(new win32api.Dispatch("PowerPoint.Application").ActivePresentation.FullName);
}
else if (exePath.ToLower().Contains("excel.exe"))
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using ToLower() with culture-sensitive comparison for file path matching can cause issues in certain locales. Use StringComparison.OrdinalIgnoreCase instead:

if (exePath.Contains("winword.exe", StringComparison.OrdinalIgnoreCase))

Apply the same change to lines 104 and 108 for PowerPoint and Excel checks.

Suggested change
if (exePath.ToLower().Contains("winword.exe"))
{
return Path.GetFullPath(new win32api.Dispatch("Word.Application").ActiveDocument.FullName);
}
else if (exePath.ToLower().Contains("powerpnt.exe"))
{
return Path.GetFullPath(new win32api.Dispatch("PowerPoint.Application").ActivePresentation.FullName);
}
else if (exePath.ToLower().Contains("excel.exe"))
if (exePath.Contains("winword.exe", StringComparison.OrdinalIgnoreCase))
{
return Path.GetFullPath(new win32api.Dispatch("Word.Application").ActiveDocument.FullName);
}
else if (exePath.Contains("powerpnt.exe", StringComparison.OrdinalIgnoreCase))
{
return Path.GetFullPath(new win32api.Dispatch("PowerPoint.Application").ActivePresentation.FullName);
}
else if (exePath.Contains("excel.exe", StringComparison.OrdinalIgnoreCase))

Copilot uses AI. Check for mistakes.
Comment on lines +97 to +113
var pid = GetActiveWindowProcessId();
var handle = win32api.OpenProcess(win32api.PROCESS_QUERY_INFORMATION | win32api.PROCESS_VM_READ, false, pid);
var exePath = win32api.GetModuleFileNameEx(handle, 0);
if (exePath.ToLower().Contains("winword.exe"))
{
return Path.GetFullPath(new win32api.Dispatch("Word.Application").ActiveDocument.FullName);
}
else if (exePath.ToLower().Contains("powerpnt.exe"))
{
return Path.GetFullPath(new win32api.Dispatch("PowerPoint.Application").ActivePresentation.FullName);
}
else if (exePath.ToLower().Contains("excel.exe"))
{
return Path.GetFullPath(new win32api.Dispatch("Excel.Application").ActiveWorkbook.FullName);
}
else
{
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method lacks error handling for several failure scenarios:

  1. No active window (foreground window could be null)
  2. Process access denied
  3. Invalid process ID
  4. Office application not running or COM exceptions
  5. No active document/workbook/presentation

The method should wrap these operations in try-catch blocks and return null on any error, matching the pattern of GetActiveExplorerPath() which safely returns null when no explorer is found.

Consider adding comprehensive error handling:

public static string GetActiveOfficeFilePath()
{
    try
    {
        // existing logic
    }
    catch (Exception)
    {
        return null;
    }
}
Suggested change
var pid = GetActiveWindowProcessId();
var handle = win32api.OpenProcess(win32api.PROCESS_QUERY_INFORMATION | win32api.PROCESS_VM_READ, false, pid);
var exePath = win32api.GetModuleFileNameEx(handle, 0);
if (exePath.ToLower().Contains("winword.exe"))
{
return Path.GetFullPath(new win32api.Dispatch("Word.Application").ActiveDocument.FullName);
}
else if (exePath.ToLower().Contains("powerpnt.exe"))
{
return Path.GetFullPath(new win32api.Dispatch("PowerPoint.Application").ActivePresentation.FullName);
}
else if (exePath.ToLower().Contains("excel.exe"))
{
return Path.GetFullPath(new win32api.Dispatch("Excel.Application").ActiveWorkbook.FullName);
}
else
{
try
{
var pid = GetActiveWindowProcessId();
var handle = win32api.OpenProcess(win32api.PROCESS_QUERY_INFORMATION | win32api.PROCESS_VM_READ, false, pid);
var exePath = win32api.GetModuleFileNameEx(handle, 0);
if (exePath.ToLower().Contains("winword.exe"))
{
return Path.GetFullPath(new win32api.Dispatch("Word.Application").ActiveDocument.FullName);
}
else if (exePath.ToLower().Contains("powerpnt.exe"))
{
return Path.GetFullPath(new win32api.Dispatch("PowerPoint.Application").ActivePresentation.FullName);
}
else if (exePath.ToLower().Contains("excel.exe"))
{
return Path.GetFullPath(new win32api.Dispatch("Excel.Application").ActiveWorkbook.FullName);
}
else
{
return null;
}
}
catch (Exception)
{

Copilot uses AI. Check for mistakes.

private static int GetActiveWindowProcessId()
{
var window = GetForegroundWindow();
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace this call with a call to managed code if possible.

Copilot uses AI. Check for mistakes.
private static int GetActiveWindowProcessId()
{
var window = GetForegroundWindow();
var threadProcessId = GetWindowThreadProcessId(window, out var processId);
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace this call with a call to managed code if possible.

Copilot uses AI. Check for mistakes.
Comment on lines +120 to 130
var window = GetForegroundWindow();
var threadProcessId = GetWindowThreadProcessId(window, out var processId);
return processId;
}

[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();

[DllImport("user32.dll", SetLastError = true)]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
}
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minimise the use of unmanaged code.

Suggested change
var window = GetForegroundWindow();
var threadProcessId = GetWindowThreadProcessId(window, out var processId);
return processId;
}
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", SetLastError = true)]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
}
// Using unmanaged code via NativeMethods to get the foreground window process ID.
var window = NativeMethods.GetForegroundWindow();
var threadProcessId = NativeMethods.GetWindowThreadProcessId(window, out var processId);
return processId;
}
/// <summary>
/// Encapsulates unmanaged code required for interacting with Windows API.
/// This is necessary as .NET does not provide managed alternatives for these calls.
/// </summary>
private static class NativeMethods
{
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", SetLastError = true)]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
}

Copilot uses AI. Check for mistakes.
Comment on lines +121 to 130
var threadProcessId = GetWindowThreadProcessId(window, out var processId);
return processId;
}

[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();

[DllImport("user32.dll", SetLastError = true)]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
}
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minimise the use of unmanaged code.

Suggested change
var threadProcessId = GetWindowThreadProcessId(window, out var processId);
return processId;
}
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", SetLastError = true)]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
}
if (window == IntPtr.Zero)
return 0;
// Use managed code to find the process with this window handle
var process = System.Diagnostics.Process.GetProcesses()
.FirstOrDefault(p => p.MainWindowHandle == window);
return process?.Id ?? 0;
}
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enhancement: new builtin shortcuts: active_office_file

3 participants