XerahS is engineered for the agentic coding era. This project embraces bleeding-edge technologies (.NET 10, Avalonia 11.3+, SkiaSharp 2.88.9) and is architected with AI-assisted development as a first-class concern. We actively seek developers proficient in agentic coding workflows—leveraging AI agents like GitHub Copilot, Claude, and Codex to accelerate feature development, refactoring, and code quality. Our codebase prioritizes clarity, consistency, and comprehensive documentation to maximize AI comprehension and generation capabilities. Strict nullability, exhaustive inline documentation, and standardized patterns (MVVM, dependency injection, plugin architecture) ensure that both human developers and AI agents can navigate, understand, and extend the system efficiently. As AI capabilities rapidly advance, agentic coding is the future—like it or not and XerahS is built to evolve alongside these tools. If you're experienced with AI-powered development tools and ready to push the boundaries of cross-platform screenshot tooling, XerahS is your platform.
Community-Driven Development: XerahS is built collaboratively by developers and contributors from around the world, united by a shared commitment to open-source innovation. This is a project created by the community, for the community—where every contribution, whether code, documentation, or feedback, helps shape a tool that serves users across all platforms.
XerahS depends on the ShareX.ImageEditor and ShareX.VideoEditor Git submodules. To clone the repository with all dependencies:
git clone --recursive https://github.com/ShareX/XerahS.gitIf you've already cloned the repository without the --recursive flag:
cd XerahS
git submodule update --init --recursiveAfter cloning with submodules:
cd XerahS
dotnet restore
dotnet buildTo pull the latest changes for the tracked submodules:
git submodule update --remote --merge ShareX.ImageEditor ShareX.VideoEditorAll projects standardize on Windows SDK 10.0.26100.0 (Windows 11 24H2). Plugins use plain net10.0 for cross-platform compatibility.
| Project | Target Framework(s) | Notes |
|---|---|---|
| Desktop App | ||
| XerahS.App | net10.0-windows10.0.26100.0 / net10.0 |
Entry point; Windows TFM on Windows, plain on others |
| XerahS.UI | net10.0-windows10.0.26100.0 / net10.0 |
Avalonia UI layer |
| XerahS.Bootstrap | net10.0-windows10.0.26100.0 / net10.0 |
DI bootstrap |
| XerahS.CLI | net10.0-windows10.0.26100.0 / net10.0 |
CLI tool |
| Core Libraries | ||
| XerahS.Core | net10.0 + net10.0-windows10.0.26100.0 |
Multi-target; Windows variant uses CsWin32 |
| XerahS.RegionCapture | net10.0 + net10.0-windows10.0.26100.0 |
Multi-target; CsWin32 for DWM/D3D11 |
| XerahS.Common | net10.0 |
Cross-platform |
| XerahS.Uploaders | net10.0 |
Cross-platform |
| XerahS.Media | net10.0 |
Cross-platform |
| XerahS.History | net10.0 |
Cross-platform |
| XerahS.Indexer | net10.0 |
Cross-platform |
| XerahS.ViewModels | net10.0 |
Cross-platform |
| XerahS.Services | net10.0 |
Cross-platform |
| XerahS.Services.Abstractions | net10.0 |
Cross-platform |
| Platform | ||
| XerahS.Platform.Abstractions | net10.0 |
Interfaces only |
| XerahS.Platform.Windows | net10.0-windows10.0.26100.0 |
D3D11, CsWinRT, native APIs |
| XerahS.Platform.MacOS | net10.0 |
SharpHook for hotkeys |
| XerahS.Platform.Linux | net10.0 |
DBus integration |
| XerahS.Platform.Mobile | net10.0 |
Shared mobile abstractions |
| Mobile | ||
| XerahS.Mobile.Maui | net10.0-android / net10.0-ios |
MAUI entry point |
| XerahS.Mobile.Android | net10.0-android |
Android platform services |
| XerahS.Mobile.iOS | net10.0-ios |
iOS platform services |
| XerahS.Mobile.iOS.ShareExtension | net10.0-ios |
iOS Share Extension |
| XerahS.Mobile.UI | net10.0 |
Cross-platform mobile UI |
Plugins (all net10.0) |
Dynamic loading; ExcludeAssets=runtime for shared deps |
|
| XerahS.Auto.Plugin | net10.0 |
|
| XerahS.Imgur.Plugin | net10.0 |
|
| XerahS.AmazonS3.Plugin | net10.0 |
|
| XerahS.Paste2.Plugin | net10.0 |
|
| XerahS.GitHubGist.Plugin | net10.0 |
|
| Tools | ||
| XerahS.PluginExporter | net10.0 |
Plugin packager |
| XerahS.Audits.Tool | net10.0 |
Dev tool |
| XerahS.Tests | net10.0-windows10.0.26100.0 |
NUnit tests |
| Submodules | ||
| ShareX.ImageEditor | net9.0 / net10.0 |
Image editor library used by UI/Core/RegionCapture |
| ShareX.ImageEditor.Loader | net10.0 |
Standalone demo app |
| ShareX.VideoEditor | net10.0 |
Video editor library consumed by XerahS.UI |
This project follows the MVVM (Model-View-ViewModel) pattern using the CommunityToolkit.Mvvm library.
- XerahS.App: Main application entry point
- XerahS.UI: UI layer using Avalonia. Contains Views, ViewModels, and shared UI resources
- XerahS.Core: Core application logic, task management (
WorkerTask), and business models - XerahS.Annotations: Annotation system with 16+ annotation types and serialization support
- XerahS.ImageEffects: 50+ image effects (filters, adjustments, manipulations) using SkiaSharp
- XerahS.Platform.*: Platform-specific implementations (e.g.,
WindowsScreenCaptureService,MacOSScreenshotService, macOS SharpHook hotkeys) - XerahS.ScreenCapture: Screen capture logic and region selection
- XerahS.Uploaders: Upload providers (Imgur, Amazon S3, etc.)
- XerahS.History: Capture history management
- XerahS.Mobile.*: Mobile implementation using .NET MAUI (Android/iOS)
XerahS.Mobile.Maui: Main MAUI application (Android/iOS targets)XerahS.Mobile.UI: Shared mobile UI components and view modelsXerahS.Mobile.Android: Android-specific platform servicesXerahS.Mobile.iOS: iOS-specific platform servicesXerahS.Mobile.iOS.ShareExtension: iOS Share Extension for receiving shared content
Services are initialized in Program.cs and App.axaml.cs. We use a Service Locator pattern via PlatformServices static class for easy access in ViewModels (though Constructor Injection is preferred where possible).
This project now uses the official Avalonia FluentTheme instead of FluentAvaloniaTheme. The migration is tracked in docs/proposals/xip/XIP0050-replace-fluentavaloniaui-with-official-avalonia-primitives.md and removes the third‑party FluentAvaloniaUI dependency in favour of first‑party Avalonia controls and local styles.
Current behavior: With the official FluentTheme, standard ContextMenu controls render correctly and are safe to use for ordinary right‑click menus.
Guideline:
- Use
ContextMenufor plain context menus that just show a list of actions. - Use
ContextFlyout+MenuFlyoutwhen you specifically need flyout behavior (e.g., button‑attached flyouts, shared flyout instances, or richer popup layouts).
<!-- ✅ Plain context menu -->
<Border.ContextMenu>
<ContextMenu>
<MenuItem Header="Action" Command="{Binding MyCommand}"/>
</ContextMenu>
</Border.ContextMenu>
<!-- ✅ Flyout attached to a button -->
<Button.Content>
<StackPanel Orientation="Horizontal">
<TextBlock Text="More" />
<Path Data="{StaticResource IconMore}" />
</StackPanel>
</Button.Content>
<Button.ContextFlyout>
<MenuFlyout>
<MenuItem Header="Advanced action" Command="{Binding AdvancedCommand}" />
</MenuFlyout>
</Button.ContextFlyout>For deeper rationale and platform quirks, see developers/lessons-learnt/general.md under UI & Theming → ContextMenu vs. ContextFlyout.
When using ContextFlyout or ContextMenu inside a DataTemplate, bindings to the parent ViewModel require special syntax because popups/flyouts exist outside the normal visual tree:
<DataTemplate x:DataType="local:MyItem">
<Border>
<Border.ContextFlyout>
<MenuFlyout>
<!-- Bind to parent UserControl's DataContext -->
<MenuItem Header="Edit"
Command="{Binding $parent[UserControl].DataContext.EditCommand}"
CommandParameter="{Binding}"/>
</MenuFlyout>
</Border.ContextFlyout>
</Border>
</DataTemplate>Key Points:
- Use
$parent[UserControl].DataContextto access the View's ViewModel from within a flyout CommandParameter="{Binding}"passes the current item (DataTemplate's DataContext)- Avalonia 11.x properly resolves
$parentin flyouts/overlays - For shared flyouts, define them in
UserControl.Resourcesand reference via{StaticResource}
The annotation system is built on a polymorphic model architecture with UI integration via EditorView:
- Models: Located in
XerahS.Annotations/Models/, inheriting from baseAnnotationclass - Types: 17 annotation types including Rectangle, Ellipse, Arrow, Line, Text, Number, Blur, Pixelate, Magnify, Highlight, Freehand, SpeechBalloon, Image, Spotlight, SmartEraser, Crop, plus BaseEffectAnnotation
- Drawing: Handled in
EditorView.axaml.csfor performance and direct pointer manipulation - State:
MainViewModelmanages tool state (ActiveTool,SelectedColor,StrokeWidth, etc.) - Undo/Redo: Implemented using
Stack<Control>to manage visual elements on canvas - Serialization: JSON-based using
System.Text.Jsonwith[JsonDerivedType]attributes for polymorphism - Keyboard Shortcuts: V(Select), R(Rectangle), E(Ellipse), A(Arrow), L(Line), P(Pen), H(Highlighter), T(Text), B(Balloon), N(Number), C(Crop), M(Magnify), S(Spotlight), F(Effects Panel)
- Auto-Discovery: Effects are discovered via reflection from
XerahS.ImageEffectsassembly - Effect Count: 40+ effects (13 Adjustments, 17 Filters, 10 Manipulations, 6 Drawings)
- Categories: Filters, Adjustments, Manipulations, Drawings
- Parameter Binding: Dynamic UI generation for effect parameters
- Integration:
EffectsPanelViewprovides UI,MainViewModelhandles application logic
The uploader system uses a plugin architecture where each uploader (Imgur, Amazon S3, etc.) is a separate plugin with its own configuration UI.
Issue: Plugin configuration UIs may not load their settings from UploadersConfig on initialization, causing saved settings to appear blank when reopening the settings view.
Root Cause: Plugins are dynamically loaded and their views are created fresh each time. If the plugin's ViewModel doesn't explicitly load configuration in its constructor or OnNavigatedTo, settings won't populate.
Solution Pattern:
// In Plugin ViewModel (e.g., ImgurConfigViewModel.cs)
public class ImgurConfigViewModel : ObservableObject, IUploaderConfigViewModel
{
public ImgurConfigViewModel()
{
// ✅ CRITICAL: Load config on construction
LoadConfiguration();
}
private void LoadConfiguration()
{
var config = SettingManager.UploadersConfig;
// Load plugin-specific settings
ClientId = config.ImgurClientID ?? "";
ClientSecret = config.ImgurClientSecret ?? "";
RefreshToken = config.ImgurRefreshToken ?? "";
// ... load other settings
}
// When settings change, notify host to save
partial void OnClientIdChanged(string value)
{
SettingManager.UploadersConfig.ImgurClientID = value;
RequestConfigSave?.Invoke(); // Trigger save event
}
}Event-Driven Save Pattern:
Plugins should notify the host application when configuration changes:
// In Plugin ViewModel
public event Action? RequestConfigSave;
// In Host (DestinationSettingsViewModel.cs)
private void OnPluginViewChanged(object? sender, EventArgs e)
{
if (SelectedPlugin?.ViewModel is ViewModelBase vm)
{
// Subscribe to save requests
if (vm is IConfigurablePlugin configurable)
{
configurable.RequestConfigSave += () =>
{
SettingManager.SaveUploadersConfig();
};
}
}
}Best Practices:
- Always load config in constructor: Don't rely on external initialization
- Save on property change: Use
OnPropertyChangedpartial methods to trigger saves - Implement safety net: Save config when view is unloaded (
OnNavigatedFromorUnloadedevent) - Test persistence: Restart app and verify settings are retained
Located in Views/RegionCapture/:
RegionCaptureWindow: Spans all monitors (Virtual Screen) with crosshair cursor- Multi-monitor DPI handling
- Uses
System.Drawing.Graphics.CopyFromScreen(GDI+) for pixel capture on Windows - macOS MVP capture uses
screencaptureviaMacOSScreenshotService
Problem: Plugin configuration UI doesn't appear in Destination Settings even though the plugin is loaded.
Root Cause: Duplicate framework DLLs in the plugin output folder. When plugins include Avalonia, CommunityToolkit.Mvvm, or Newtonsoft.Json assemblies, the dynamic loading system may fail to initialize config views due to type identity mismatches.
Solution: Always use ExcludeAssets=runtime on NuGet package references for shared dependencies:
<!-- ✅ CORRECT: Shared framework dependencies -->
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.2.2">
<ExcludeAssets>runtime</ExcludeAssets>
</PackageReference>
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.2">
<ExcludeAssets>runtime</ExcludeAssets>
</PackageReference>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.0">
<ExcludeAssets>runtime</ExcludeAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4">
<ExcludeAssets>runtime</ExcludeAssets>
</PackageReference>
</ItemGroup>Verification: A properly configured plugin folder should contain 4-5 files:
Plugins/myplugin/
├── ShareX.MyPlugin.Plugin.dll
├── XerahS.Platform.Abstractions.dll (if needed)
├── plugin.json
├── ShareX.MyPlugin.Plugin.runtimeconfig.json
└── runtimes/ (platform-specific natives only)
Bad Sign: If you see 20+ files including Avalonia.*.dll, CommunityToolkit.Mvvm.dll, etc., the config view will not load.
Debugging:
- Check plugin folder file count - should be ~4 files, not 20+
- Enable debug logging in
UploaderInstanceViewModel.InitializeConfigViewModel() - Look for
ConfigView created: nullor type loading errors
See also: Destination Plugin Development Guide for complete plugin setup instructions.
- Review project documentation and existing code patterns
- Ensure code compiles with
dotnet build(0 errors) - Follow MVVM separation: UI logic in Views, business logic in ViewModels
- Add XML documentation for public APIs
- Test on multiple platforms when possible
Ensure you have the following installed:
- .NET 10.0 SDK
- Node.js
^20.19.0 || >=22.12.0for theShareX.VideoEditorfrontend build ShareX.ImageEditorandShareX.VideoEditorsubmodules (initialized as described above)
# Clone the main repository with submodules
git clone --recursive https://github.com/ShareX/XerahS.gitOn macOS, build the native ScreenCaptureKit bridge library before building the .NET solution:
cd native/macos
make
ls -l libscreencapturekit_bridge.dylibcd XerahS
dotnet build src/desktop/XerahS.sln# From the XerahS directory
dotnet run --project src/XerahS.App/XerahS.App.csprojdotnet testXerahS includes an experimental .NET MAUI mobile implementation for Android and iOS. This extends ShareX's upload capabilities to mobile devices.
The mobile implementation follows a layered architecture:
┌─────────────────────────────────────┐
│ XerahS.Mobile.Maui (Entry Point) │
│ - App.xaml / AppShell.xaml │
│ - Platform-specific initialization │
├─────────────────────────────────────┤
│ XerahS.Mobile.UI │
│ - Shared Views (MobileUploadPage) │
│ - ViewModels (MobileUploadVM) │
├─────────────────────────────────────┤
│ XerahS.Mobile.Android / iOS │
│ - Platform services │
│ - Native file pickers │
├─────────────────────────────────────┤
│ XerahS.Core / Common / Uploaders │
│ - Shared business logic │
└─────────────────────────────────────┘
- .NET 10.0 SDK
- Android: Android SDK API 21+ (included with .NET MAUI workload)
- iOS: Xcode 16+ (macOS required), iOS 15.0+ deployment target
dotnet workload install mauidotnet build src/XerahS.Mobile.Maui/XerahS.Mobile.Maui.csproj -f net10.0-androiddotnet run --project src/XerahS.Mobile.Maui/XerahS.Mobile.Maui.csproj -f net10.0-androiddotnet build src/XerahS.Mobile.Maui/XerahS.Mobile.Maui.csproj -f net10.0-iosdotnet run --project src/XerahS.Mobile.Maui/XerahS.Mobile.Maui.csproj -f net10.0-ios| Feature | Android | iOS | Notes |
|---|---|---|---|
| File Upload | ✅ | ✅ | Images, videos, documents |
| Share Extension | ❌ | ✅ | iOS Share Sheet integration |
| Amazon S3 | ✅ | ✅ | Full S3 uploader support |
| Custom Uploaders | ✅ | ✅ | HTTP-based uploaders |
| Imgur | ✅ | ✅ | Image hosting |
| Settings | ✅ | ✅ | Mobile-optimized UI |
The iOS Share Extension (XerahS.Mobile.iOS.ShareExtension) allows users to share content from other apps directly to XerahS:
- Built as an app extension embedded in the main iOS app
- Receives shared images, videos, URLs, and text
- Hands off to the main app for upload processing
Build Configuration:
The Share Extension is referenced in the main MAUI project with IsAppExtension=true:
<ProjectReference Include="..\XerahS.Mobile.iOS.ShareExtension\XerahS.Mobile.iOS.ShareExtension.csproj">
<IsAppExtension>true</IsAppExtension>
</ProjectReference>Mobile projects share code with desktop via:
- XerahS.Core: Business logic, uploaders, settings
- XerahS.Common: Utilities, helpers, extensions
- XerahS.Uploaders: Uploader implementations
- XerahS.Platform.Abstractions: Platform interfaces
Platform-specific implementations are in:
XerahS.Mobile.Android: Android file picker, permissionsXerahS.Mobile.iOS: iOS file picker, Share Extension bridgeXerahS.Platform.Mobile: Shared mobile platform abstractions
Avalonia mobile UI now supports runtime adaptive theming for iOS and Android.
Reference docs:
- Architecture:
../docs/architecture/MOBILE_THEMING.md - Styling guide:
guidelines/MOBILE_STYLING_GUIDE.md - Validation report:
../docs/reports/MOBILE_NATIVE_THEMING_VALIDATION_2026-02-17.md