Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add window capture support #43

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 4 additions & 4 deletions SeeShark.Example.Ascii/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ namespace SeeShark.Example.Ascii;

class Program
{
static Camera? karen;
static CameraManager? manager;
static Window? karen;
static WindowManager? manager;
static FrameConverter? converter;

static void Main(string[] args)
Expand Down Expand Up @@ -43,9 +43,9 @@ static void Main(string[] args)
Console.WriteLine("Running in {0}-bit mode.", Environment.Is64BitProcess ? "64" : "32");
Console.WriteLine($"FFmpeg version info: {FFmpegVersion}");

manager = new CameraManager();
manager = new WindowManager();

CameraInfo device;
WindowInfo device;
if (args.Length < 1)
{
/// Select an available camera device.
Expand Down
13 changes: 13 additions & 0 deletions SeeShark/Device/Window.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) The Vignette Authors
// This file is part of SeeShark.
// SeeShark is licensed under the BSD 3-Clause License. See LICENSE for details.

namespace SeeShark.Device;

public class Window : VideoDevice
{
public Window(VideoDeviceInfo info, DeviceInputFormat inputFormat, VideoInputOptions? options = null)
: base(info, inputFormat, options)
{
}
}
14 changes: 14 additions & 0 deletions SeeShark/Device/WindowInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) The Vignette Authors
// This file is part of SeeShark.
// SeeShark is licensed under the BSD 3-Clause License. See LICENSE for details.

using System;

namespace SeeShark.Device;

public class WindowInfo : VideoDeviceInfo
{
public string Title { get; init; } = string.Empty;

public IntPtr Id { get; init; }
}
145 changes: 145 additions & 0 deletions SeeShark/Device/WindowManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Copyright (c) The Vignette Authors
// This file is part of SeeShark.
// SeeShark is licensed under the BSD 3-Clause License. See LICENSE for details.

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using SeeShark.Interop.Windows;
using SeeShark.Interop.X11;

namespace SeeShark.Device;

public class WindowManager : VideoDeviceManager<WindowInfo, Window>
{
public static DeviceInputFormat DefaultInputFormat
{
get
{
return OperatingSystem.IsWindows() ? DeviceInputFormat.GdiGrab
: OperatingSystem.IsLinux() ? DeviceInputFormat.X11Grab
: OperatingSystem.IsMacOS() ? DeviceInputFormat.AVFoundation
: throw new NotSupportedException(
$"Cannot find adequate display input format for RID '{RuntimeInformation.RuntimeIdentifier}'.");
}
}


public WindowManager(DeviceInputFormat? inputFormat = null) : base(inputFormat ?? DefaultInputFormat)
{
}

public override Window GetDevice(WindowInfo info, VideoInputOptions? options = null)
{
if (options is { } o)
{
return new Window(info, InputFormat, o);
}
else
{
return new Window(info, InputFormat, generateInputOptions(info));
}
}

/// <summary>
/// Enumerates available devices.
/// </summary>
protected override WindowInfo[] EnumerateDevices()
{
switch (InputFormat)
{
case DeviceInputFormat.X11Grab:
return enumerateDevicesX11();
case DeviceInputFormat.GdiGrab:
return enumerateDevicesGdi();
default:
return base.EnumerateDevices();
}
}

private WindowInfo[] enumerateDevicesX11()
{
List<WindowInfo> windows = new List<WindowInfo>();
unsafe
{
IntPtr display = XLib.XOpenDisplay(null);
IntPtr rootWindow = XLib.XDefaultRootWindow(display);
findWindowsX11(display, rootWindow, ref windows);
}

return windows.ToArray();
}

void findWindowsX11(IntPtr display, IntPtr window, ref List<WindowInfo> windows)
{
IntPtr[] childWindows = Array.Empty<IntPtr>();

XLib.XQueryTree(display, window, out IntPtr rootWindow, out IntPtr parentWindow, out childWindows,
out int nChildren);

childWindows = new IntPtr[nChildren];

XLib.XQueryTree(display, window,
out rootWindow, out parentWindow,
out childWindows, out nChildren);

XLib.XFetchName(display, window, out string title);

windows.Add(new WindowInfo
{
Path = ":0",
Title = title,
Id = window
});

for (int i = 0; i < childWindows.Length; i++)
{
XLib.XFetchName(display, childWindows[i], out string childTitle);

windows.Add(new WindowInfo
{
Path = ":0",
Title = childTitle,
Id = childWindows[i]
});

findWindowsX11(display, childWindows[i], ref windows);
}
}

private WindowInfo[] enumerateDevicesGdi()
{
List<WindowInfo> windows = new List<WindowInfo>();
User32.EnumWindows(delegate(IntPtr wnd, IntPtr param)
{
int size = User32.GetWindowTextLength(wnd);
string title = string.Empty;
if (size > 0)
{
var builder = new StringBuilder(size + 1);
User32.GetWindowText(wnd, builder, builder.Capacity);
title = builder.ToString();
}

windows.Add(new WindowInfo
{
Path = $"title={title}",
Title = title,
Id = wnd
});
return true;
}, IntPtr.Zero);

return windows.ToArray();
}

private VideoInputOptions generateInputOptions(WindowInfo info)
{
return new VideoInputOptions
{
WindowId = $"0x{new IntPtr(0x3600003).ToString("X2")}"
};
}
}
12 changes: 12 additions & 0 deletions SeeShark/Interop/Windows/User32.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using System;
using System.Runtime.InteropServices;
using System.Text;

namespace SeeShark.Interop.Windows;

Expand Down Expand Up @@ -145,4 +146,15 @@ internal static partial class User32

[DllImport("Shcore.dll")]
internal static extern int SetProcessDpiAwareness(int awareness);

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
internal static extern int GetWindowText(IntPtr hWnd, StringBuilder strText, int maxCount);

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
internal static extern int GetWindowTextLength(IntPtr hWnd);

[DllImport("user32.dll")]
internal static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam);

internal delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
}
18 changes: 12 additions & 6 deletions SeeShark/Interop/X11/XLib.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,30 @@ internal class XLib

[DllImport(lib_x11, EntryPoint = "XOpenDisplay")]
private static extern unsafe Display sys_XOpenDisplay(sbyte* display);
public static unsafe Display XOpenDisplay(sbyte* display)
internal static unsafe Display XOpenDisplay(sbyte* display)
{
lock (displayLock)
return sys_XOpenDisplay(display);
}

[DllImport(lib_x11, EntryPoint = "XCloseDisplay")]
public static extern int XCloseDisplay(Display display);
internal static extern int XCloseDisplay(Display display);

[DllImport(lib_x11, EntryPoint = "XDefaultRootWindow")]
public static extern Window XDefaultRootWindow(Display display);
internal static extern Window XDefaultRootWindow(Display display);

[DllImport(lib_x11, EntryPoint = "XDisplayWidth")]
public static extern int XDisplayWidth(Display display, int screenNumber);
internal static extern int XDisplayWidth(Display display, int screenNumber);

[DllImport(lib_x11, EntryPoint = "XDisplayHeight")]
public static extern int XDisplayHeight(Display display, int screenNumber);
internal static extern int XDisplayHeight(Display display, int screenNumber);

[DllImport(lib_x11, EntryPoint = "XGetAtomName")]
public static extern IntPtr XGetAtomName(Display display, Atom atom);
internal static extern IntPtr XGetAtomName(Display display, Atom atom);

[DllImport(lib_x11, EntryPoint = "XQueryTree")]
internal static extern int XQueryTree(IntPtr display, IntPtr w, out IntPtr rootReturn, out IntPtr parentReturn, out IntPtr[] childrenReturn, out int nChildrenReturn);

[DllImport(lib_x11, EntryPoint = "XFetchName")]
internal static extern int XFetchName(IntPtr display, IntPtr w, out string windowNameReturn);
}
11 changes: 11 additions & 0 deletions SeeShark/VideoInputOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ public class VideoInputOptions
/// </summary>
public bool DrawMouse { get; set; } = true;

/// <summary>
/// Used in Linux only - The ID of the window to capture
/// </summary>
public string? WindowId { get; set; }
Speykious marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Combines all properties into a dictionary of options that FFmpeg can use.
/// </summary>
Expand Down Expand Up @@ -107,6 +112,12 @@ public class VideoInputOptions
}
}

if (WindowId != null)
{
if (deviceFormat == DeviceInputFormat.X11Grab)
dict.Add("window_id", WindowId);
}

switch (deviceFormat)
{
case DeviceInputFormat.X11Grab:
Expand Down