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

Architecture: adding windows on ARM support #10997

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

thirumalai-qcom
Copy link

@thirumalai-qcom thirumalai-qcom commented Jul 16, 2024

Introduces Windows on ARM support to the OBS-studio application. This includes modifications that are guarded with ARM-specific macros to ensure compatibility without affecting the existing codebase.

Description

This pull request introduces several code refactoring changes to enhance WOA support and resolve conflicts in the OBS-Studio application. The key changes include:

  • UI: Added shellapi.h to fix compilation error, Updated min/max to be used from limits header, Ensured WIN32_LEAN_AND_MEAN is defined conditionally if not defined.
  • w32-pthreads: Added additional definitions of ARM macros _M_ARM and _M_ARM64.
  • libobs: updated obs-win-crash-handler.c to handle instruction pointers and stack traces, improving crash reporting on ARM64 systems .
  • obs-filters: Undefined S_THRESHOLD due to a conflicting existing macro with a numeric value.
  • obs-outputs: Replaced __tzcnt_u32() with _CountTrailingZeros() for ARM.

Motivation and Context

The primary motivation for these changes is to expand compatibility with ARM architecture (windows) by addressing conflicts with existing macros and functions. By introducing ARM-specific macros and guarding ARM-specific code, we ensure that the application runs smoothly without affecting the existing codebase.

  • UI Changes:

    • Added shellapi.h in obs-app.cpp to fix compilation errors.
    • Updated min/max values to use limits header for VolumeMeter and VolumeSlider.
    • Ensured WIN32_LEAN_AND_MEAN is included conditionally in window-basic-filters, window-basic-interaction, and window-basic-properties.
    • Updated min/max values in the DrawStripedLine function in window-basic-preview.
    • Updated min/max values for preset setting in window-basic-settings-a11y.cpp.
  • Additional ARM definitions in deps/w32-pthreads/context.h:

    • Update w32-pthreads/context.h to include definitions for _M_ARM and _M_ARM64 in addition to existing ARM definitions.
  • Libobs changes:

    • Adjust obs-win-crash-handler.c to handle instruction pointers & stack traces for crash reporting.
  • Obs-filters changes:

    • Undefines the macro S_THRESHOLD if previously defined, then redefines it with a new value "threshold".
  • Obs-outputs: Use _CountTrailingZeros() in ctz32

    • Replaced tzcnt_u32 with _CountTrailingZeros to ensure compatibility with WOA machines.

How Has This Been Tested?

The changes have been tested by building OBS Studio on an ARM machine (X Elite machine, Win11). All builds were successful, Basic functionality tests confirmed that the OBS Studio application with the modifications is functional.

Types of changes

  • New feature (non-breaking change which adds functionality)

Checklist:

  • My code has been run through clang-format.
  • I have read the contributing document.
  • My code is not on the master branch.
  • The code has been tested.
  • All commit messages are properly formatted and commits squashed where appropriate.
  • I have included updates to all appropriate documentation.

@kkartaltepe
Copy link
Collaborator

Unless you have tested QSV with your arm devices you should drop the proposed changes there and ensure the plugin is not built for arm.

@tytan652
Copy link
Collaborator

tytan652 commented Jul 17, 2024

Please reword the PR as "adding Windows on ARM support", we already support ARM64 on macOS and OBS can be built on Linux aarch64.

Also there is no CI build check which does not help to avoid/find build issue on WoA, which is not good at all since not all contributors will have WoA machines to check if it builds.

Edit: Also, read our contribution guidelines and split your commits.

@thirumalai-qcom thirumalai-qcom changed the title Architecture: Add ARM support Architecture: adding windows on ARM support Jul 17, 2024
Copy link
Member

@derrod derrod left a comment

Choose a reason for hiding this comment

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

Please actually read through the contributing document. Changes for different modules/components should be in separate commits. Also the motivation leaves a lot to be desired, there isn't really any explanation for why some of those changes were made/deemed necessary.

Comment on lines 1156 to 1159
#if defined(_M_ARM64)
constexpr int min = (std::numeric_limits<int>::min)();
constexpr int max = (std::numeric_limits<int>::max)();
#else
Copy link
Member

Choose a reason for hiding this comment

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

What's the reason for this?

Copy link
Author

Choose a reason for hiding this comment

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

This change was made to address compilation errors related to the use of std::numeric_limits<int>::min() and std::numeric_limits<int>::max(). The () are included to ensure that we are explicitly invoking the functions from the <limits> header rather than the macros that are defined.

This approach is derived from a previous PR #5455 that addressed a similar Issue.

Copy link
Member

Choose a reason for hiding this comment

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

It looks like our options are to #define NOMINMAX whenever doing #include <Windows.h> in C++ files (and making sure there are no issues), using this syntax all the time, or this (only using this syntax within an appropriate ifdef).

Copy link
Author

Choose a reason for hiding this comment

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

Using () explicitly the code ensures clarity and avoids macro conflicts. This inline change only affects the particular line if broken, making it a localized fix.

Defining NOMINMAX affects the entire file, meaning any legitimate use of the min and max macros would be disabled throughout the file. This could lead to potential issues elsewhere in the code where the min and max macros might be needed.

Introducing conditional statements for this purpose adds redundancy and complexity to the code. It again adds () only in specific conditions, which can make the code harder to maintain and understand.

Copy link
Member

Choose a reason for hiding this comment

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

I cannot think of legitimate uses of the min and max macros unless you're working with specific Windows headers that require them (such as perhaps GdiPlus headers, which we only use in two places).

The change shown in this review had already introduced conditional preprocessor statements here. The example you linked to unconditionally adds (). Unfortunately, this code preview was outdated, and I had not seen that the code was changed to unconditionally add (), which is what I was trying to hint at.

Copy link
Collaborator

@tytan652 tytan652 Aug 1, 2024

Choose a reason for hiding this comment

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

@thirumalai-qcom, OBS Studio is a multi-platform application which relies on common API like the C library API (a.k.a. libc) and the C++ library API (a.k.a. C++ STL).

As it is, the min and max macro that are not part of the common C/C++ APIs. And moreover it interferes with the C++ STL that is multi-platform.

Defining NOMINMAX is almost mandatory to make sure that the same C/C++ APIs is present on the 3 main platforms.

Those macros are not a thing on macOS or Linux, so disabling is simpler and more sensible than making workaround for macros that OBS multi-platform code will never use.

Edit: Also defines are scoped so they can be set to not affect other headers if needed

Copy link
Author

Choose a reason for hiding this comment

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

@RytoEX Could you share your thoughts on which approach to implement here? Should we continue using the () explicitly, or would it be more efficient to define NOMINMAX given the context of OBS Studio's multi-platform nature?

UI/obs-app.cpp Outdated
Comment on lines 59 to 61
#if defined(_M_ARM64)
#include <shellapi.h>
#endif
Copy link
Member

Choose a reason for hiding this comment

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

Is this even used anywhere? It should also be included through windows.h anyway unless WIN32_LEAN_AND_MEAN is defined.

Copy link
Author

Choose a reason for hiding this comment

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

This header is used by the ShellExecuteA() function in obs-app.cpp.

ShellExecuteA(NULL, "open", vcRunInstallerUrl, NULL, NULL,

As WIN32_LEAN_AND_MEAN is defined, shellapi.h is not included by windows.h. By explicitly including shellapi.h, we ensure that sources get compiled, preventing compilation errors.

Copy link
Member

Choose a reason for hiding this comment

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

I'm not aware of WIN32_LEAN_AND_MEAN being defined at this point or this currently causing compilation errors. A cursory check inside Visual Studio shows that WIN32_LEAN_AND_MEAN is not defined here. It is defined in other places in the codebase, but not here.

image

Have I misunderstood something?

Copy link
Author

Choose a reason for hiding this comment

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

Hi Ryan, The cursory check on WOA device (X-Elite) points that WIN32_LEAN_AND_MEAN is defined, so i have added Shellapi.h explicitly to resolve compilation issues.

shellapi

UI/window-basic-filters.cpp Outdated Show resolved Hide resolved
Comment on lines 1864 to 1875
#if defined(_M_ARM64)
if (x1 < x2) {
dx = (std::min)(xx1 + 7.5f * offX, x2);
} else {
dx = (std::max)(xx1 + 7.5f * offX, x2);
}
if (y1 < y2) {
dy = (std::min)(yy1 + 7.5f * offY, y2);
} else {
dy = (std::max)(yy1 + 7.5f * offY, y2);
}
#else
Copy link
Member

Choose a reason for hiding this comment

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

See previous comment about changes related to std::max/std::min

Copy link
Author

Choose a reason for hiding this comment

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

I've provided a brief explanation about the std::max/std::min modifications in the previous comment. You can review it here

libobs/graphics/graphics.c Outdated Show resolved Hide resolved
@@ -28,6 +28,9 @@
/* clang-format off */

#define S_RATIO "ratio"
#if defined(_M_ARM64)
#undef S_THRESHOLD
Copy link
Member

Choose a reason for hiding this comment

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

Why is this being undefined here?

Copy link
Author

Choose a reason for hiding this comment

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

The #undef S_THRESHOLD is necessary to avoid a macro redefinition warning.
This is because windows.h, which includes winbase.h, defines S_THRESHOLD with a value of 1. Our source file requires S_THRESHOLD to be defined as "threshold".

Copy link
Collaborator

Choose a reason for hiding this comment

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

Looks like a namespace conflict (here S_), the reasonable change would be to change all compressor-filter.c S_ macros to something like FILTER_S_ in a separate PR.

Copy link
Author

Choose a reason for hiding this comment

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

@RytoEX what are your thoughts on this? Do you think it's better to change all S_ macros to FILTER_S_ as suggested by @tytan652, or do you have another solution in mind to resolve the macro redefinition warning?

Copy link
Contributor

@norihiro norihiro Aug 2, 2024

Choose a reason for hiding this comment

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

The majority of the plugins use S_ as the prefix to define setting names. Changing the macro name makes the history dirty. I hope it can be fixed by a compile flag like WIN32_LEAN_AND_MEAN. Though, IMHO, #undef is reasonable if it cannot be resolved by any compile flags.

There are various ways in which the setting names are written among the 1st-party plugins. Unfortunately, the majority write string literals directly without macros. The 2nd place is S_.

Besides this PR, I want to suggest the style should be unified among the plugins. In my opinion, SETTING_, OPT_, or PROP_ (no one uses this though) would be descriptive rather than S_.

More than 34 plugins directly writing string literals

Image slideshow uses static const char * with the prefix S_. (Probably, this is Lain's preference, isn't this?)

  • static const char *S_TR_SPEED = "transition_speed";
    static const char *S_CUSTOM_SIZE = "use_custom_size";
    static const char *S_SLIDE_TIME = "slide_time";
    static const char *S_TRANSITION = "transition";
    static const char *S_RANDOMIZE = "randomize";
    static const char *S_LOOP = "loop";
    static const char *S_HIDE = "hide";
    static const char *S_FILES = "files";
    static const char *S_BEHAVIOR = "playback_behavior";
    static const char *S_BEHAVIOR_STOP_RESTART = "stop_restart";
    static const char *S_BEHAVIOR_PAUSE_UNPAUSE = "pause_unpause";
    static const char *S_BEHAVIOR_ALWAYS_PLAY = "always_play";
    static const char *S_MODE = "slide_mode";
    static const char *S_MODE_AUTO = "mode_auto";
    static const char *S_MODE_MANUAL = "mode_manual";

15 plugins using macros with the prefix S_

  • #define S_RATIO "ratio"
    #define S_THRESHOLD "threshold"
    #define S_ATTACK_TIME "attack_time"
    #define S_RELEASE_TIME "release_time"
    #define S_OUTPUT_GAIN "output_gain"
    #define S_SIDECHAIN_SOURCE "sidechain_source"
  • #define S_RATIO "ratio"
    #define S_THRESHOLD "threshold"
    #define S_ATTACK_TIME "attack_time"
    #define S_RELEASE_TIME "release_time"
    #define S_OUTPUT_GAIN "output_gain"
    #define S_DETECTOR "detector"
    #define S_PRESETS "presets"
    #define S_KNEE "knee_width"
  • #define S_GAIN_DB "db"
  • #define S_DELAY_MS "delay_ms"
  • #define S_THRESHOLD "threshold"
    #define S_RELEASE_TIME "release_time"
  • #define S_OPEN_THRESHOLD "open_threshold"
    #define S_CLOSE_THRESHOLD "close_threshold"
    #define S_ATTACK_TIME "attack_time"
    #define S_HOLD_TIME "hold_time"
    #define S_RELEASE_TIME "release_time"
  • #define S_SUPPRESS_LEVEL "suppress_level"
    #define S_NVAFX_INTENSITY "intensity"
    #define S_METHOD "method"
    #define S_METHOD_SPEEX "speex"
    #define S_METHOD_RNN "rnnoise"
    #define S_METHOD_NVAFX_DENOISER "denoiser"
    #define S_METHOD_NVAFX_DEREVERB "dereverb"
    #define S_METHOD_NVAFX_DEREVERB_DENOISER "dereverb_denoiser"
  • #define S_MODE "mode"
    #define S_MODE_QUALITY 0
    #define S_MODE_PERF 1
    #define S_THRESHOLDFX "threshold"
    #define S_THRESHOLDFX_DEFAULT 1.0
    #define S_PROCESSING "processing_interval"
  • #define S_RESOLUTION "resolution"
    #define S_SAMPLING "sampling"
    #define S_UNDISTORT "undistort"
    #define T_RESOLUTION obs_module_text("Resolution")
    #define T_NONE obs_module_text("None")
    #define T_SAMPLING obs_module_text("ScaleFiltering")
    #define T_SAMPLING_POINT obs_module_text("ScaleFiltering.Point")
    #define T_SAMPLING_BILINEAR obs_module_text("ScaleFiltering.Bilinear")
    #define T_SAMPLING_BICUBIC obs_module_text("ScaleFiltering.Bicubic")
    #define T_SAMPLING_LANCZOS obs_module_text("ScaleFiltering.Lanczos")
    #define T_SAMPLING_AREA obs_module_text("ScaleFiltering.Area")
    #define T_UNDISTORT obs_module_text("UndistortCenter")
    #define T_BASE obs_module_text("Base.Canvas")
    #define S_SAMPLING_POINT "point"
    #define S_SAMPLING_BILINEAR "bilinear"
    #define S_SAMPLING_BICUBIC "bicubic"
    #define S_SAMPLING_LANCZOS "lanczos"
    #define S_SAMPLING_AREA "area"
  • #define S_FONT "font"
    #define S_USE_FILE "read_from_file"
    #define S_FILE "file"
    #define S_TEXT "text"
    #define S_COLOR "color"
    #define S_GRADIENT "gradient"
    #define S_GRADIENT_COLOR "gradient_color"
    #define S_GRADIENT_DIR "gradient_dir"
    #define S_GRADIENT_OPACITY "gradient_opacity"
    #define S_ALIGN "align"
    #define S_VALIGN "valign"
    #define S_OPACITY "opacity"
    #define S_BKCOLOR "bk_color"
    #define S_BKOPACITY "bk_opacity"
    #define S_VERTICAL "vertical"
    #define S_OUTLINE "outline"
    #define S_OUTLINE_SIZE "outline_size"
    #define S_OUTLINE_COLOR "outline_color"
    #define S_OUTLINE_OPACITY "outline_opacity"
    #define S_CHATLOG_MODE "chatlog"
    #define S_CHATLOG_LINES "chatlog_lines"
    #define S_EXTENTS "extents"
    #define S_EXTENTS_WRAP "extents_wrap"
    #define S_EXTENTS_CX "extents_cx"
    #define S_EXTENTS_CY "extents_cy"
    #define S_TRANSFORM "transform"
    #define S_ANTIALIASING "antialiasing"
    #define S_ALIGN_LEFT "left"
    #define S_ALIGN_CENTER "center"
    #define S_ALIGN_RIGHT "right"
    #define S_VALIGN_TOP "top"
    #define S_VALIGN_CENTER S_ALIGN_CENTER
    #define S_VALIGN_BOTTOM "bottom"
  • #define S_COLOR "color"
    #define S_SWITCH_POINT "switch_point"
    #define S_COLOR_TEXT obs_module_text("Color")
    #define S_SWITCH_POINT_TEXT obs_module_text("SwitchPoint")
  • #define S_LUMA_IMG "luma_image"
    #define S_LUMA_INV "luma_invert"
    #define S_LUMA_SOFT "luma_softness"
  • #define S_DIRECTION "direction"
  • #define S_DIRECTION "direction"
    #define S_SWIPE_IN "swipe_in"
  • #define S_PLAYLIST "playlist"
    #define S_LOOP "loop"
    #define S_SHUFFLE "shuffle"
    #define S_BEHAVIOR "playback_behavior"
    #define S_BEHAVIOR_STOP_RESTART "stop_restart"
    #define S_BEHAVIOR_PAUSE_UNPAUSE "pause_unpause"
    #define S_BEHAVIOR_ALWAYS_PLAY "always_play"
    #define S_NETWORK_CACHING "network_caching"
    #define S_TRACK "track"
    #define S_SUBTITLE_ENABLE "subtitle_enable"
    #define S_SUBTITLE_TRACK "subtitle"

9 plugins using macros with a prefix SETTING_

3 plugins using macros without prefix

  • #define DEVICE_HASH "device_hash"
    #define DEVICE_NAME "device_name"
    #define VIDEO_CONNECTION "video_connection"
    #define AUDIO_CONNECTION "audio_connection"
    #define MODE_ID "mode_id"
    #define MODE_NAME "mode_name"
    #define CHANNEL_FORMAT "channel_format"
    #define PIXEL_FORMAT "pixel_format"
    #define COLOR_SPACE "color_space"
    #define COLOR_RANGE "color_range"
    #define BUFFERING "buffering"
    #define DEACTIVATE_WNS "deactivate_when_not_showing"
    #define AUTO_START "auto_start"
    #define FORCE_SDR "force_sdr"
    #define KEYER "keyer"
    #define SWAP "swap"
    #define ALLOW_10_BIT "allow_10_bit"
  • #define VIDEO_DEVICE_ID "video_device_id"
    #define RES_TYPE "res_type"
    #define RESOLUTION "resolution"
    #define FRAME_INTERVAL "frame_interval"
    #define VIDEO_FORMAT "video_format"
    #define LAST_VIDEO_DEV_ID "last_video_device_id"
    #define LAST_RESOLUTION "last_resolution"
    #define BUFFERING_VAL "buffering"
    #define FLIP_IMAGE "flip_vertically"
    #define AUDIO_OUTPUT_MODE "audio_output_mode"
    #define USE_CUSTOM_AUDIO "use_custom_audio_device"
    #define AUDIO_DEVICE_ID "audio_device_id"
    #define COLOR_SPACE "color_space"
    #define COLOR_RANGE "color_range"
    #define DEACTIVATE_WNS "deactivate_when_not_showing"
    #define AUTOROTATION "autorotation"
    #define HW_DECODE "hw_decode"
  • #define OPT_DEVICE_ID "device_id"
    #define OPT_USE_DEVICE_TIMING "use_device_timing"
    #define OPT_WINDOW "window"
    #define OPT_PRIORITY "priority"

2 plugins using macros with a prefix OPT_

Only AJA plugin has a structure.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Changing the macro name makes the history dirty.

Undefining macros that are not ours is not a good idea either.

plugins/obs-qsv11/common_utils_windows.cpp Outdated Show resolved Hide resolved
plugins/vlc-video/vlc-video-plugin.c Outdated Show resolved Hide resolved
@WizardCM WizardCM added New Feature New feature or plugin Windows Affects Windows labels Jul 20, 2024
@thirumalai-qcom thirumalai-qcom force-pushed the aarch64_support branch 2 times, most recently from d7abd70 to d2976cc Compare July 24, 2024 04:57
@thirumalai-qcom thirumalai-qcom marked this pull request as ready for review July 24, 2024 04:57
@norihiro
Copy link
Contributor

The commit messages need to follow the guideline. https://github.com/obsproject/obs-studio/blob/master/CONTRIBUTING.rst#commit-guidelines

Add shellapi.h to fix compilation error
Update min/max to be used from limits header
Ensure WIN32_LEAN_AND_MEAN is defined conditionally if not defined
Update w32-pthreads/context.h to include definitions for _M_ARM and
_M_ARM64 in addition to existing ARM definitions.
Adjust obs-win-crash-handler.c to handle instruction pointers and
stack traces, improving crash reporting on ARM64 systems.
To prevent warnings related to redefinition of the threshold macro,
the previously defined threshold value is undefined when compiling
for ARM64 architecture. This ensures that the code remains free of
redundant macro redefinitions.
This change modifies the `ctz32` function to use `_CountTrailingZeros`
when `_M_ARM64` is defined, as `_tzcnt_u32` is not available on WOA.
@thirumalai-qcom
Copy link
Author

thirumalai-qcom commented Aug 2, 2024

@RytoEX I would like to know if AJA functionality is planned to be included in the Phase-1 release of the arm64 version of OBS. Currently, in AJA the __cpuid method is not supported on ARM devices, Could you provide some insights into what functionality we will miss in OBS on ARM due to this limitation ?

Edit: Identified an alternative way to retrieve the CPU string from the register (as done in libobs/obs-windows.c log_processor_info()). Can this be a replacement for the __cpuid method on ARM devices?

@Fenrirthviti
Copy link
Member

@RytoEX I would like to know if AJA functionality is planned to be included in the Phase-1 release of the arm64 version of OBS. Currently, in AJA the __cpuid method is not supported on ARM devices, Could you provide some insights into what functionality we will miss in OBS on ARM due to this limitation ?

Edit: Identified an alternative way to retrieve the CPU string from the register (as done in libobs/obs-windows.c log_processor_info()). Can this be a replacement for the __cpuid method on ARM devices?

Poking @paulh-aja for AJA's current stats on ARM support. (sorry for double ping here, accidentally replied to the wrong comment!)

@RytoEX
Copy link
Member

RytoEX commented Aug 14, 2024

@RytoEX I would like to know if AJA functionality is planned to be included in the Phase-1 release of the arm64 version of OBS. Currently, in AJA the __cpuid method is not supported on ARM devices, Could you provide some insights into what functionality we will miss in OBS on ARM due to this limitation ?
Edit: Identified an alternative way to retrieve the CPU string from the register (as done in libobs/obs-windows.c log_processor_info()). Can this be a replacement for the __cpuid method on ARM devices?

Poking @paulh-aja for AJA's current stats on ARM support. (sorry for double ping here, accidentally replied to the wrong comment!)

My thinking on this would be that if there's an ARM64 equivalent to __cpuid, then it should (probably) be trivial to ifdef appropriately, which should be fine. That said, the linked code is in AJA's SDK, so the changes will have to be made there, not in OBS. It is worth noting that you cannot simply lift existing code from the obs-studio repo to drop into AJA's repo as the licenses, while compatible, are different. The correct way to address this is likely to file an Issue (or PR) on their repo.

It may also be worth noting that the obs-qsv11 plugin also uses __cpuid.

@paulh-aja
Copy link
Contributor

Currently, in AJA the __cpuid method is not supported on ARM devices.

Poking @paulh-aja for AJA's current stats on ARM support.

Please open a PR or at least raise an issue in the libajantv2 repo as RytoEX mentioned.

We do not have Windows ARM support on our current release roadmap. However, if the only thing blocking it is replacing the use of __cpuid in the ajabase code, then it may be trivial to add it in. Our Mac and Linux drivers and SDK do support ARM64 today.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
New Feature New feature or plugin Windows Affects Windows
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants