Skip to content

Commit

Permalink
Merge pull request #75 from Freeesia/feature/target_window
Browse files Browse the repository at this point in the history
ターゲットウィンドウダイアログを開く機能を追加
  • Loading branch information
Freeesia authored Sep 16, 2024
2 parents d968008 + 262358a commit 87805ca
Show file tree
Hide file tree
Showing 10 changed files with 474 additions and 53 deletions.
7 changes: 1 addition & 6 deletions VdLabel/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,7 @@ record DesktopConfig
public IReadOnlyList<WindowConfig> TargetWindows { get; init; } = [];
}

record WindowConfig
{
public WindowMatchType MatchType { get; set; }
public WindowPatternType PatternType { get; set; }
public string Pattern { get; set; } = string.Empty;
}
record WindowConfig(WindowMatchType MatchType, WindowPatternType PatternType, string Pattern);

enum WindowMatchType
{
Expand Down
37 changes: 37 additions & 0 deletions VdLabel/IPresentationService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Windows;
using Kamishibai;

namespace VdLabel;

public partial interface IPresentationService : IPresentationServiceBase
{
Task<WindowInfo?> OpenTargetWindowDialogAsync();
}

public class PresentationService(IServiceProvider serviceProvider, INavigationFrameProvider navigationFrameProvider, IWindowService windowService)
: PresentationServiceBase(serviceProvider, navigationFrameProvider, windowService), IPresentationService
{
private readonly IServiceProvider _serviceProvider = serviceProvider;

public async Task<WindowInfo?> OpenTargetWindowDialogAsync()
{
var current = Application.Current.Windows.OfType<Window>().SingleOrDefault(x => x.IsActive);
current?.Hide();
try
{
var vm = new TargetWindowViewModel();
if (await OpenDialogAsync(vm) == true)
{
return vm.SelectedWindow;
}
else
{
return null;
}
}
finally
{
current?.Show();
}
}
}
47 changes: 42 additions & 5 deletions VdLabel/MainViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ partial class MainViewModel : ObservableObject
private readonly IContentDialogService dialogService;
private readonly IVirualDesktopService virualDesktopService;
private readonly ICommandLabelService commandLabelService;
private readonly IPresentationService presentationService;
private readonly IUpdateChecker updateChecker;

[ObservableProperty]
Expand Down Expand Up @@ -48,13 +49,15 @@ partial class MainViewModel : ObservableObject

public MainViewModel(
IConfigStore configStore,
IPresentationService presentationService,
IContentDialogService dialogService,
IVirualDesktopService virualDesktopService,
ICommandLabelService commandLabelService,
IUpdateChecker updateChecker)
{
BindingOperations.EnableCollectionSynchronization(this.DesktopConfigs, new());
this.configStore = configStore;
this.presentationService = presentationService;
this.dialogService = dialogService;
this.virualDesktopService = virualDesktopService;
this.commandLabelService = commandLabelService;
Expand Down Expand Up @@ -93,7 +96,7 @@ private async void Load()
this.DesktopConfigs.Clear();
foreach (var desktopConfig in this.Config.DesktopConfigs)
{
this.DesktopConfigs.Add(new(desktopConfig, this.dialogService, this.virualDesktopService, this.commandLabelService));
this.DesktopConfigs.Add(new(desktopConfig, this.presentationService, this.dialogService, this.virualDesktopService, this.commandLabelService));
}
this.SelectedDesktopConfig = this.DesktopConfigs.FirstOrDefault();
}
Expand Down Expand Up @@ -181,8 +184,15 @@ private static bool GetIsStartup()
}
}

partial class DesktopConfigViewModel(DesktopConfig desktopConfig, IContentDialogService dialogService, IVirualDesktopService virualDesktopService, ICommandLabelService commandLabelService) : ObservableObject
partial class DesktopConfigViewModel(
DesktopConfig desktopConfig,
IPresentationService presentationService,
IContentDialogService dialogService,
IVirualDesktopService virualDesktopService,
ICommandLabelService commandLabelService)
: ObservableObject
{
private readonly IPresentationService presentationService = presentationService;
private readonly IContentDialogService dialogService = dialogService;
private readonly IVirualDesktopService virualDesktopService = virualDesktopService;
private readonly ICommandLabelService commandLabelService = commandLabelService;
Expand Down Expand Up @@ -217,7 +227,7 @@ partial class DesktopConfigViewModel(DesktopConfig desktopConfig, IContentDialog

public bool IsVisibleImage => this.ImagePath is not null;

public ObservableCollection<WindowConfig> TargetWindows { get; } = new(desktopConfig.TargetWindows);
public ObservableCollection<WindowConfigViewModel> TargetWindows { get; } = new(desktopConfig.TargetWindows.Select(c => new WindowConfigViewModel(c)));

public IReadOnlyList<WindowMatchType> MatchTypes { get; } = Enum.GetValues<WindowMatchType>();

Expand Down Expand Up @@ -273,9 +283,26 @@ public void AddTargetWindow()
=> this.TargetWindows.Add(new());

[RelayCommand]
public void RemoveTargetWindow(WindowConfig target)
public void RemoveTargetWindow(WindowConfigViewModel target)
=> this.TargetWindows.Remove(target);

[RelayCommand]
public async Task FindWindow(WindowConfigViewModel config)
{
var info = await this.presentationService.OpenTargetWindowDialogAsync();
if (info is null)
{
return;
}
config.Pattern = config.MatchType switch
{
WindowMatchType.CommandLine => info.CommandLine,
WindowMatchType.Title => info.Title,
WindowMatchType.Path => info.Path,
_ => throw new NotSupportedException(),
};
}

public DesktopConfig GetSaveConfig()
=> new()
{
Expand All @@ -285,6 +312,16 @@ public DesktopConfig GetSaveConfig()
Utf8Command = this.Utf8Command,
Command = this.Command,
ImagePath = this.ImagePath,
TargetWindows = this.TargetWindows.ToArray(),
TargetWindows = this.TargetWindows.Select(c => new WindowConfig(c.MatchType, c.PatternType, c.Pattern)).ToArray(),
};
}

partial class WindowConfigViewModel(WindowConfig? config = null) : ObservableObject
{
[ObservableProperty]
private WindowMatchType matchType = config?.MatchType ?? default;
[ObservableProperty]
private WindowPatternType patternType = config?.PatternType ?? default;
[ObservableProperty]
private string pattern = config?.Pattern ?? string.Empty;
}
15 changes: 10 additions & 5 deletions VdLabel/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -339,11 +339,11 @@
DockPanel.Dock="Right"
Icon="{ui:SymbolIcon Delete24}" />
<Button
Margin="4,0"
Command="{Binding PickImageCommand}"
Content="選択"
DockPanel.Dock="Right" />
<ui:TextBox
Margin="4"
IsReadOnly="True"
PlaceholderText="画像パス"
Text="{Binding ImagePath}" />
Expand Down Expand Up @@ -375,23 +375,28 @@
ItemsSource="{Binding TargetWindows}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="local:WindowConfig">
<DockPanel Margin="0,0,8,0">
<DockPanel Margin="4,2">
<ComboBox
Width="148"
Margin="4"
ItemsSource="{Binding DataContext.MatchTypes, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
SelectedItem="{Binding MatchType, Mode=TwoWay}" />
<ComboBox
Width="108"
Margin="4"
Margin="4,0"
ItemsSource="{Binding DataContext.PatternTypes, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
SelectedItem="{Binding PatternType, Mode=TwoWay}" />
<ui:Button
Command="{Binding DataContext.RemoveTargetWindowCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding}"
DockPanel.Dock="Right"
Icon="{ui:SymbolIcon Delete24}" />
<ui:TextBox Margin="4" Text="{Binding Pattern, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<ui:Button
Margin="4,0"
Command="{Binding DataContext.FindWindowCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding}"
DockPanel.Dock="Right"
Icon="{ui:SymbolIcon WindowLocationTarget20}" />
<ui:TextBox Text="{Binding Pattern, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DockPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
Expand Down
42 changes: 42 additions & 0 deletions VdLabel/ProcessUtility.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Diagnostics;
using System.Management;
using PInvoke;

namespace VdLabel;
public static class ProcessUtility
{
public static string? GetWindowTitle(nint hWnd)
{
try
{
return User32.GetWindowText(hWnd);
}
catch (Win32Exception)
{
// 仮想デスクトップを切り替えるタイミングで例外が発生することがある
return null;
}
}

public static string? GetProcessPath(int processId)
{
try
{
using var process = Process.GetProcessById(processId);
using var module = process.MainModule;
return module?.FileName ?? string.Empty;
}
catch (Exception)
{
// プロセスが終了している場合がある
return null;
}
}

public static string? GetCommandLine(int processId)
{
using var searcher = new ManagementObjectSearcher($"SELECT CommandLine FROM Win32_Process WHERE ProcessId = '{processId}'");
using var mo = searcher.Get().Cast<ManagementBaseObject>().SingleOrDefault();
return mo?["CommandLine"] as string;
}
}
3 changes: 2 additions & 1 deletion VdLabel/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
.AddSingleton<IConfigStore, ConfigStore>()
.AddSingleton<IContentDialogService, ContentDialogService>()
.AddPresentation<MainWindow, MainViewModel>()
.AddPresentation<OverlayWindow, OverlayViewModel>();
.AddPresentation<OverlayWindow, OverlayViewModel>()
.AddPresentation<TargetWindowOverlay, TargetWindowViewModel>();
var app = builder.Build();
app.Startup += static (s, e) => VirtualDesktop.Configure();

Expand Down
110 changes: 110 additions & 0 deletions VdLabel/TargetWindowOverlay.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<Window
x:Class="VdLabel.TargetWindowOverlay"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:VdLabel"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
d:DataContext="{d:DesignInstance local:TargetWindowViewModel}"
AllowsTransparency="True"
Background="#11808080"
Cursor="Cross"
Loaded="Window_Loaded"
ResizeMode="NoResize"
ShowActivated="False"
ShowInTaskbar="False"
Topmost="True"
WindowStartupLocation="Manual"
WindowState="Normal"
WindowStyle="None"
mc:Ignorable="d">
<Grid>
<ListBox SelectedItem="{Binding SelectedWindow}">
<ListBox.ItemsSource>
<MultiBinding Converter="{x:Static local:WindowOffsetConverter.Default}">
<Binding Path="Windows" />
<Binding RelativeSource="{RelativeSource AncestorType=Window}" />
</MultiBinding>
</ListBox.ItemsSource>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Canvas.Left" Value="{Binding Left, Mode=OneWay}" />
<Setter Property="Canvas.Top" Value="{Binding Top, Mode=OneWay}" />
<Setter Property="Width" Value="{Binding Width, Mode=OneWay}" />
<Setter Property="Height" Value="{Binding Height, Mode=OneWay}" />
<Setter Property="Panel.ZIndex" Value="{Binding ZOrder, Mode=OneWay}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border
Name="Bd"
Background="Transparent"
BorderBrush="Transparent"
BorderThickness="4"
SnapsToDevicePixels="True">
<ContentPresenter
Name="Content"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding Content}"
ContentStringFormat="{TemplateBinding ContentStringFormat}"
ContentTemplate="{TemplateBinding ContentTemplate}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Visibility="Hidden" />
</Border>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Selector.IsSelectionActive" Value="False" />
<Condition Property="Selector.IsSelected" Value="True" />
</MultiTrigger.Conditions>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Selector.IsSelectionActive" Value="True" />
<Condition Property="Selector.IsSelected" Value="True" />
</MultiTrigger.Conditions>
</MultiTrigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Bd" Property="Border.BorderBrush" Value="GreenYellow" />
<Setter TargetName="Content" Property="FrameworkElement.Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate DataType="local:WindowInfo">
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Border Background="{DynamicResource ApplicationBackgroundBrush}" CornerRadius="4">
<Border.Effect>
<BlurEffect Radius="8" />
</Border.Effect>
</Border>
<TextBlock
Padding="4"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Text="{Binding Title, Mode=OneWay}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ui:Button
HorizontalAlignment="Right"
VerticalAlignment="Top"
Background="Red"
Command="{Binding CancelCommand, Mode=OneWay}"
CornerRadius="0"
Cursor="Arrow"
Icon="{ui:SymbolIcon Dismiss48}"
MouseOverBackground="OrangeRed" />
</Grid>
</Window>
Loading

0 comments on commit 87805ca

Please sign in to comment.