Repository: ElectronNET/Electron.NET
Branch: main
Commit: c8268fae0eab
Files: 477
Total size: 1.6 MB
Directory structure:
gitextract_uhomav4p/
├── .gitattributes
├── .github/
│ ├── CONTRIBUTING.md
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── feature_request.md
│ │ └── question.md
│ └── workflows/
│ ├── Build and Publish.yml
│ ├── PR Validation.yml
│ ├── integration-tests.yml
│ ├── pr-comment.yml
│ ├── publish-wiki.yml
│ ├── retry-test-jobs.yml
│ └── trailing-whitespace-check.yml
├── .gitignore
├── .nuke/
│ ├── build.schema.json
│ └── parameters.json
├── .vscode/
│ ├── launch.json
│ └── tasks.json
├── Changelog.md
├── LICENSE
├── NuGet.config
├── README.md
├── artifacts/
│ └── .gitkeep
├── assets/
│ └── images/
│ └── electron.net-logo.psd
├── build.cmd
├── build.ps1
├── build.sh
├── docs/
│ ├── .docproj/
│ │ ├── DocProj.props
│ │ └── DocProj.targets
│ ├── API/
│ │ ├── App.md
│ │ ├── AutoUpdater.md
│ │ ├── Clipboard.md
│ │ ├── Dialog.md
│ │ ├── Dock.md
│ │ ├── GlobalShortcut.md
│ │ ├── HostHook.md
│ │ ├── IpcMain.md
│ │ ├── Menu.md
│ │ ├── NativeTheme.md
│ │ ├── Notification.md
│ │ ├── Overview.md
│ │ ├── PowerMonitor.md
│ │ ├── Screen.md
│ │ ├── Shell.md
│ │ ├── Tray.md
│ │ ├── WebContents.md
│ │ └── WindowManager.md
│ ├── About.md
│ ├── Core/
│ │ ├── Advanced-Migration-Topics.md
│ │ ├── Migration-Checks.md
│ │ ├── Migration-Guide.md
│ │ └── What's-New.md
│ ├── Docs.shproj
│ ├── GettingStarted/
│ │ ├── ASP.Net.md
│ │ ├── Console-App.md
│ │ └── System-Requirements.md
│ ├── Home.md
│ ├── RelInfo/
│ │ └── Package-Description.md
│ ├── Using/
│ │ ├── Configuration.md
│ │ ├── Custom_main.md
│ │ ├── Debugging.md
│ │ ├── Package-Building.md
│ │ └── Startup-Methods.md
│ ├── _Footer.md
│ ├── _Sidebar.md
│ └── md-styles.css
├── global.json
├── nuke/
│ ├── .editorconfig
│ ├── Build.cs
│ ├── CommonPropsParser.cs
│ ├── Configuration.cs
│ ├── Directory.Build.props
│ ├── Directory.Build.targets
│ ├── Extensions/
│ │ └── StringExtensions.cs
│ ├── ReleaseNotes.cs
│ ├── ReleaseNotesParser.cs
│ ├── SemVersion.cs
│ └── _build.csproj
└── src/
├── .editorconfig
├── ElectronNET/
│ ├── .electron/
│ │ └── .gitkeep
│ ├── ElectronNET.csproj
│ └── build/
│ ├── ElectronNET.Core.props
│ ├── ElectronNET.Core.targets
│ ├── ElectronNET.DesignTime.targets
│ ├── ElectronNET.LateImport.targets
│ ├── ElectronNET.MigrationChecks.targets
│ ├── ElectronNETRules.Project.xaml
│ ├── ElectronNETRules.Project2.xaml
│ ├── electron-builder.json
│ ├── package.template.json
│ └── update_electron_versions.py
├── ElectronNET.API/
│ ├── API/
│ │ ├── ApiBase.cs
│ │ ├── App.cs
│ │ ├── AutoUpdater.cs
│ │ ├── BrowserView.cs
│ │ ├── BrowserWindow.cs
│ │ ├── Clipboard.cs
│ │ ├── CommandLine.cs
│ │ ├── Cookies.cs
│ │ ├── Dialog.cs
│ │ ├── Dock.cs
│ │ ├── Electron.cs
│ │ ├── Entities/
│ │ │ ├── AboutPanelOptions.cs
│ │ │ ├── AddRepresentationOptions.cs
│ │ │ ├── AppDetailsOptions.cs
│ │ │ ├── AutoResizeOptions.cs
│ │ │ ├── BitmapOptions.cs
│ │ │ ├── Blob.cs
│ │ │ ├── BlockMapDataHolder.cs
│ │ │ ├── BrowserViewConstructorOptions.cs
│ │ │ ├── BrowserWindowOptions.cs
│ │ │ ├── CPUUsage.cs
│ │ │ ├── Certificate.cs
│ │ │ ├── CertificatePrincipal.cs
│ │ │ ├── CertificateTrustDialogOptions.cs
│ │ │ ├── ChromeExtensionInfo.cs
│ │ │ ├── ClearStorageDataOptions.cs
│ │ │ ├── Cookie.cs
│ │ │ ├── CookieChangedCause.cs
│ │ │ ├── CookieDetails.cs
│ │ │ ├── CookieFilter.cs
│ │ │ ├── CreateFromBitmapOptions.cs
│ │ │ ├── CreateFromBufferOptions.cs
│ │ │ ├── CreateInterruptedDownloadOptions.cs
│ │ │ ├── Data.cs
│ │ │ ├── DefaultFontFamily.cs
│ │ │ ├── DevToolsMode.cs
│ │ │ ├── Display.cs
│ │ │ ├── DisplayBalloonOptions.cs
│ │ │ ├── DockBounceType.cs
│ │ │ ├── EnableNetworkEmulationOptions.cs
│ │ │ ├── Extension.cs
│ │ │ ├── FileFilter.cs
│ │ │ ├── FileIconOptions.cs
│ │ │ ├── FileIconSize.cs
│ │ │ ├── FocusOptions.cs
│ │ │ ├── GPUFeatureStatus.cs
│ │ │ ├── IPostData.cs
│ │ │ ├── ImportCertificateOptions.cs
│ │ │ ├── InputEvent.cs
│ │ │ ├── InputEventType.cs
│ │ │ ├── JumpListCategory.cs
│ │ │ ├── JumpListCategoryType.cs
│ │ │ ├── JumpListItem.cs
│ │ │ ├── JumpListItemType.cs
│ │ │ ├── JumpListSettings.cs
│ │ │ ├── LoadURLOptions.cs
│ │ │ ├── LoginItemLaunchItem.cs
│ │ │ ├── LoginItemSettings.cs
│ │ │ ├── LoginItemSettingsOptions.cs
│ │ │ ├── LoginSettings.cs
│ │ │ ├── Margins.cs
│ │ │ ├── MemoryInfo.cs
│ │ │ ├── MenuItem.cs
│ │ │ ├── MenuRole.cs
│ │ │ ├── MenuType.cs
│ │ │ ├── MessageBoxOptions.cs
│ │ │ ├── MessageBoxResult.cs
│ │ │ ├── MessageBoxType.cs
│ │ │ ├── ModifierType.cs
│ │ │ ├── NativeImage.cs
│ │ │ ├── NativeImageJsonConverter.cs
│ │ │ ├── NotificationAction.cs
│ │ │ ├── NotificationOptions.cs
│ │ │ ├── OnDidFailLoadInfo.cs
│ │ │ ├── OnDidNavigateInfo.cs
│ │ │ ├── OnTopLevel.cs
│ │ │ ├── OpenDevToolsOptions.cs
│ │ │ ├── OpenDialogOptions.cs
│ │ │ ├── OpenDialogProperty.cs
│ │ │ ├── OpenExternalOptions.cs
│ │ │ ├── PageSize.cs
│ │ │ ├── PathName.cs
│ │ │ ├── Point.cs
│ │ │ ├── PrintOptions.cs
│ │ │ ├── PrintToPDFOptions.cs
│ │ │ ├── PrinterInfo.cs
│ │ │ ├── ProcessMetric.cs
│ │ │ ├── ProcessVersions.cs
│ │ │ ├── ProgressBarMode.cs
│ │ │ ├── ProgressBarOptions.cs
│ │ │ ├── ProgressInfo.cs
│ │ │ ├── ProxyConfig.cs
│ │ │ ├── ReadBookmark.cs
│ │ │ ├── Rectangle.cs
│ │ │ ├── RelaunchOptions.cs
│ │ │ ├── ReleaseNoteInfo.cs
│ │ │ ├── RemovePassword.cs
│ │ │ ├── ResizeOptions.cs
│ │ │ ├── SaveDialogOptions.cs
│ │ │ ├── SaveDialogProperty.cs
│ │ │ ├── Scheme.cs
│ │ │ ├── SemVer.cs
│ │ │ ├── SharingItem.cs
│ │ │ ├── ShortcutDetails.cs
│ │ │ ├── ShortcutLinkOperation.cs
│ │ │ ├── Size.cs
│ │ │ ├── ThemeSourceMode.cs
│ │ │ ├── ThumbarButton.cs
│ │ │ ├── ThumbarButtonFlag.cs
│ │ │ ├── TitleBarOverlay.cs
│ │ │ ├── TitleBarStyle.cs
│ │ │ ├── ToBitmapOptions.cs
│ │ │ ├── ToDataUrlOptions.cs
│ │ │ ├── ToPNGOptions.cs
│ │ │ ├── TrayClickEventArgs.cs
│ │ │ ├── UpdateCancellationToken.cs
│ │ │ ├── UpdateCheckResult.cs
│ │ │ ├── UpdateFileInfo.cs
│ │ │ ├── UpdateInfo.cs
│ │ │ ├── UploadFile.cs
│ │ │ ├── UploadRawData.cs
│ │ │ ├── UserTask.cs
│ │ │ ├── Vibrancy.cs
│ │ │ └── WebPreferences.cs
│ │ ├── Extensions/
│ │ │ ├── MenuItemExtensions.cs
│ │ │ └── ThumbarButtonExtensions.cs
│ │ ├── GlobalShortcut.cs
│ │ ├── HostHook.cs
│ │ ├── HybridSupport.cs
│ │ ├── IpcMain.cs
│ │ ├── Menu.cs
│ │ ├── NativeTheme.cs
│ │ ├── Notification.cs
│ │ ├── PowerMonitor.cs
│ │ ├── Process.cs
│ │ ├── QuitEventArgs.cs
│ │ ├── Screen.cs
│ │ ├── Session.cs
│ │ ├── Shell.cs
│ │ ├── Tray.cs
│ │ ├── WebContents.cs
│ │ ├── WebRequest.cs
│ │ ├── WindowManager.cs
│ │ └── web-request.md
│ ├── Bridge/
│ │ ├── BridgeConnector.cs
│ │ ├── Events.cs
│ │ └── SocketIOFacade.cs
│ ├── Common/
│ │ ├── Extensions.cs
│ │ ├── ProcessRunner.cs
│ │ ├── RunnerParams.cs
│ │ └── TimeSpanExtensions.cs
│ ├── Converter/
│ │ ├── ModifierTypeListConverter.cs
│ │ ├── PageSizeConverter.cs
│ │ └── TitleBarOverlayConverter.cs
│ ├── ElectronNET.API.csproj
│ ├── ElectronNET.API.csproj.DotSettings
│ ├── ElectronNetRuntime.cs
│ ├── Runtime/
│ │ ├── Controllers/
│ │ │ ├── RuntimeControllerBase.cs
│ │ │ ├── RuntimeControllerDotNetFirst.cs
│ │ │ └── RuntimeControllerElectronFirst.cs
│ │ ├── Data/
│ │ │ ├── BuildInfo.cs
│ │ │ ├── DotnetAppType.cs
│ │ │ ├── LifetimeState.cs
│ │ │ └── StartupMethod.cs
│ │ ├── Helpers/
│ │ │ ├── LaunchOrderDetector.cs
│ │ │ ├── PortHelper.cs
│ │ │ └── UnpackagedDetector.cs
│ │ ├── IElectronNetRuntimeController.cs
│ │ ├── Services/
│ │ │ ├── ElectronProcess/
│ │ │ │ ├── ElectronProcessActive.cs
│ │ │ │ ├── ElectronProcessBase.cs
│ │ │ │ └── ElectronProcessPassive.cs
│ │ │ ├── LifetimeServiceBase.cs
│ │ │ └── SocketBridge/
│ │ │ └── SocketBridgeService.cs
│ │ └── StartupManager.cs
│ └── Serialization/
│ ├── ElectronJson.cs
│ └── JsonToBoxedPrimitivesConverter.cs
├── ElectronNET.AspNet/
│ ├── API/
│ │ ├── ServiceCollectionExtensions.cs
│ │ ├── WebApplicationBuilderExtensions.cs
│ │ └── WebHostBuilderExtensions.cs
│ ├── ElectronNET.AspNet.csproj
│ └── Runtime/
│ ├── Controllers/
│ │ ├── RuntimeControllerAspNetBase.cs
│ │ ├── RuntimeControllerAspNetDotnetFirst.cs
│ │ └── RuntimeControllerAspNetElectronFirst.cs
│ ├── Helpers/
│ │ └── ServerReadyStartupFilter.cs
│ └── Services/
│ └── AspNetLifetimeAdapter.cs
├── ElectronNET.Build/
│ ├── ElectronNET.Build.csproj
│ ├── ElectronNET.Build.csproj.DotSettings
│ ├── PrintItemMetadata.cs
│ ├── RemoveEnvironmentVariables.cs
│ └── ReplaceMsBuildPropertiesTask.cs
├── ElectronNET.ConsoleApp/
│ ├── ElectronNET.ConsoleApp.csproj
│ ├── Program.cs
│ └── Properties/
│ ├── PublishProfiles/
│ │ ├── linux-x64.pubxml
│ │ ├── publish-win-x64.pubxml
│ │ └── win-x64.pubxml
│ ├── electron-builder.json
│ └── launchSettings.json
├── ElectronNET.Host/
│ ├── .gitignore
│ ├── .vscode/
│ │ ├── launch.json
│ │ └── tasks.json
│ ├── ElectronHostHook/
│ │ ├── .gitignore
│ │ ├── connector.js
│ │ ├── connector.ts
│ │ ├── index.js
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── ElectronNET.Host.esproj
│ ├── api/
│ │ ├── app.js
│ │ ├── app.ts
│ │ ├── autoUpdater.js
│ │ ├── autoUpdater.ts
│ │ ├── browserView.js
│ │ ├── browserView.ts
│ │ ├── browserWindows.js
│ │ ├── browserWindows.ts
│ │ ├── clipboard.js
│ │ ├── clipboard.ts
│ │ ├── commandLine.js
│ │ ├── commandLine.ts
│ │ ├── dialog.js
│ │ ├── dialog.ts
│ │ ├── dock.js
│ │ ├── dock.ts
│ │ ├── globalShortcut.js
│ │ ├── globalShortcut.ts
│ │ ├── ipc.js
│ │ ├── ipc.ts
│ │ ├── menu.js
│ │ ├── menu.ts
│ │ ├── nativeTheme.js
│ │ ├── nativeTheme.ts
│ │ ├── notification.js
│ │ ├── notification.ts
│ │ ├── powerMonitor.js
│ │ ├── powerMonitor.ts
│ │ ├── process.js
│ │ ├── process.ts
│ │ ├── screen.js
│ │ ├── screen.ts
│ │ ├── shell.js
│ │ ├── shell.ts
│ │ ├── tray.js
│ │ ├── tray.ts
│ │ ├── webContents.js
│ │ └── webContents.ts
│ ├── eslint.config.js
│ ├── globals.d.ts
│ ├── main.js
│ ├── package.json
│ ├── scripts/
│ │ └── blazor-preload.js
│ ├── splashscreen/
│ │ └── index.html
│ ├── tsconfig.json
│ ├── tsconfig.tsbuildinfo
│ └── types-shims.d.ts
├── ElectronNET.IntegrationTests/
│ ├── Common/
│ │ ├── IntegrationFactAttribute.cs
│ │ └── IntegrationTestBase.cs
│ ├── ElectronFixture.cs
│ ├── ElectronNET.IntegrationTests.csproj
│ ├── GlobalUsings.cs
│ ├── Properties/
│ │ └── electron-builder.json
│ ├── Tests/
│ │ ├── AppTests.cs
│ │ ├── AutoUpdaterTests.cs
│ │ ├── BrowserViewTests.cs
│ │ ├── BrowserWindowTests.cs
│ │ ├── ClipboardTests.cs
│ │ ├── CookiesTests.cs
│ │ ├── GlobalShortcutTests.cs
│ │ ├── HostHookTests.cs
│ │ ├── IpcMainTests.cs
│ │ ├── MenuTests.cs
│ │ ├── MultiEventRegistrationTests.cs
│ │ ├── NativeImageTests.cs
│ │ ├── NativeThemeTests.cs
│ │ ├── NotificationTests.cs
│ │ ├── ProcessTests.cs
│ │ ├── ScreenTests.cs
│ │ ├── SessionTests.cs
│ │ ├── ShellTests.cs
│ │ ├── ThumbarButtonTests.cs
│ │ ├── TrayTests.cs
│ │ └── WebContentsTests.cs
│ └── xunit.runner.json
├── ElectronNET.Lean.sln
├── ElectronNET.Samples.ElectronHostHook/
│ ├── Controllers/
│ │ └── HomeController.cs
│ ├── ElectronHostHook/
│ │ ├── .gitignore
│ │ ├── connector.ts
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── ElectronNET.Samples.ElectronHostHook.csproj
│ ├── Program.cs
│ ├── Properties/
│ │ ├── electron-builder.json
│ │ └── launchSettings.json
│ └── Views/
│ └── Home/
│ └── Index.cshtml
├── ElectronNET.WebApp/
│ ├── Controllers/
│ │ ├── AboutController.cs
│ │ ├── AppSysInformationController.cs
│ │ ├── ClipboardController.cs
│ │ ├── CrashHangController.cs
│ │ ├── DesktopCapturerController.cs
│ │ ├── DialogsController.cs
│ │ ├── HomeController.cs
│ │ ├── HostHookController.cs
│ │ ├── IpcController.cs
│ │ ├── ManageWindowsController.cs
│ │ ├── MenusController.cs
│ │ ├── NotificationsController.cs
│ │ ├── PdfController.cs
│ │ ├── ShellController.cs
│ │ ├── ShortcutsController.cs
│ │ ├── TrayController.cs
│ │ ├── UpdateController.cs
│ │ └── WindowsController.cs
│ ├── ElectronHostHook/
│ │ ├── .gitignore
│ │ ├── connector.js
│ │ ├── connector.ts
│ │ ├── excelCreator.js
│ │ ├── excelCreator.ts
│ │ ├── index.js
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── ElectronNET.WebApp.csproj
│ ├── Program.cs
│ ├── Properties/
│ │ ├── PublishProfiles/
│ │ │ ├── linux-x64.pubxml
│ │ │ └── win-x64.pubxml
│ │ ├── electron-builder.json
│ │ └── launchSettings.json
│ ├── Startup.cs
│ ├── Views/
│ │ ├── About/
│ │ │ └── Index.cshtml
│ │ ├── AppSysInformation/
│ │ │ └── Index.cshtml
│ │ ├── Clipboard/
│ │ │ └── Index.cshtml
│ │ ├── CrashHang/
│ │ │ ├── Index.cshtml
│ │ │ ├── ProcessCrash.cshtml
│ │ │ └── ProcessHang.cshtml
│ │ ├── DesktopCapturer/
│ │ │ └── Index.cshtml
│ │ ├── Dialogs/
│ │ │ └── Index.cshtml
│ │ ├── Home/
│ │ │ └── Index.cshtml
│ │ ├── HostHook/
│ │ │ └── Index.cshtml
│ │ ├── Ipc/
│ │ │ └── Index.cshtml
│ │ ├── Menus/
│ │ │ └── Index.cshtml
│ │ ├── Notifications/
│ │ │ └── Index.cshtml
│ │ ├── Pdf/
│ │ │ └── Index.cshtml
│ │ ├── Shell/
│ │ │ └── Index.cshtml
│ │ ├── Shortcuts/
│ │ │ └── Index.cshtml
│ │ ├── Tray/
│ │ │ └── Index.cshtml
│ │ ├── Update/
│ │ │ └── Index.cshtml
│ │ └── Windows/
│ │ ├── DemoWindow.cshtml
│ │ ├── HandleErrorCrashes.cshtml
│ │ └── Index.cshtml
│ ├── appsettings.Development.json
│ ├── appsettings.json
│ └── wwwroot/
│ └── assets/
│ ├── app-icon/
│ │ └── mac/
│ │ └── app.icns
│ ├── code-blocks.js
│ ├── css/
│ │ ├── about.css
│ │ ├── demo.css
│ │ ├── fonts/
│ │ │ ├── SourceSansPro-Black.otf
│ │ │ ├── SourceSansPro-BlackIt.otf
│ │ │ ├── SourceSansPro-Bold.otf
│ │ │ ├── SourceSansPro-BoldIt.otf
│ │ │ ├── SourceSansPro-ExtraLight.otf
│ │ │ ├── SourceSansPro-ExtraLightIt.otf
│ │ │ ├── SourceSansPro-It.otf
│ │ │ ├── SourceSansPro-Light.otf
│ │ │ ├── SourceSansPro-LightIt.otf
│ │ │ ├── SourceSansPro-Regular.otf
│ │ │ ├── SourceSansPro-Semibold.otf
│ │ │ └── SourceSansPro-SemiboldIt.otf
│ │ ├── github.css
│ │ ├── global.css
│ │ ├── nativize.css
│ │ ├── nav.css
│ │ ├── print.css
│ │ ├── section.css
│ │ └── variables.css
│ ├── demo-btns.js
│ ├── ex-links.js
│ ├── imports.js
│ ├── mac/
│ │ ├── child.plist
│ │ ├── info.plist
│ │ └── parent.plist
│ └── nav.js
├── ElectronNET.sln
├── ElectronNET.sln.DotSettings
├── common.props
└── testEnvironments.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing
## Project Scope
The Electron.NET project ultimately tries to provide a framework for developing cross-platform client applications on the basis of .NET and Electron. Anything that is related to this goal will be considered. The project aims to be as close to Electron with .NET as a basis as possible. If your contribution does not reflect that goal, the chances of accepting it are limited.
## Code License
This is an open source project falling under the [MIT License](../LICENSE). By using, distributing, or contributing to this project, you accept and agree that all code within the Electron.NET project and its libraries are licensed under MIT license.
## Becoming a Contributor
Usually appointing someone as a contributor follows this process:
1. An individual contributes actively via discussions (reporting bugs, giving feedback to existing or opening new issues) and / or pull requests
2. The individual is either directly asked, invited or asks for contributor rights on the project
3. The individual uses the contribution rights to sustain or increase the active contributions
Every contributor might have to sign the contributor's license agreement (CLA) to establish a legal trust between the project and its contributors.
## Working on Electron.NET
### Issue Discussion
Discussion of issues should be placed transparently in the issue tracker here on GitHub.
* [General issues, bugs, new features](https://github.com/ElectronNET/Electron.NET/issues)
* [General discussions, help, exchange of ideas](https://github.com/ElectronNET/Electron.NET/discussions)
### Modifying the code
Electron.NET and its libraries uses features from the latest versions of C# (e.g., C# 10). You will therefore need a C# compiler that is up for the job.
1. Fork and clone the repo.
2. First try to build the ElectronNET.Core library and see if you get the tests running.
3. You will be required to resolve some dependencies via NuGet.
The build system of Electron.NET uses NUKE.
### Code Conventions
Most parts in the Electron.NET project are fairly straight forward. Among these are:
* Always use statement blocks for control statements, e.g., in a for-loop, if-condition, ...
* You may use a simple (throw) statement in case of enforcing contracts on argument
* Be explicit about modifiers (some files follow an older convention of the code base, but we settled on the explicit style)
### Development Workflow
1. If no issue already exists for the work you'll be doing, create one to document the problem(s) being solved and self-assign.
2. Otherwise please let us know that you are working on the problem. Regular status updates (e.g. "still in progress", "no time anymore", "practically done", "pull request issued") are highly welcome.
3. Create a new branch—please don't work in the `main` branch directly. It is reserved for releases. We recommend naming the branch to match the issue being addressed (`feature/#777` or `issue-777`).
4. Add failing tests for the change you want to make. Tests are crucial and should be taken from W3C (or other specification).
5. Fix stuff. Always go from edge case to edge case.
6. All tests should pass now. Also your new implementation should not break existing tests.
7. Update the documentation to reflect any changes. (or document such changes in the original issue)
8. Push to your fork or push your issue-specific branch to the main repository, then submit a pull request against `develop`.
Just to illustrate the git workflow for Electron.NET a little bit more we've added the following graphs.
Initially, Electron.NET starts at the `main` branch. This branch should contain the latest stable (or released) version.
Here we now created a new branch called `develop`. This is the development branch.
Now active work is supposed to be done. Therefore a new branch should be created. Let's create one:
```sh
git checkout -b feature/#777
```
There may be many of these feature branches. Most of them are also pushed to the server for discussion or synchronization.
```sh
git push -u origin feature/#777
```
Now feature branches may be closed when they are done. Here we simply merge with the feature branch(es). For instance the following command takes the `feature/#777` branch from the server and merges it with the `develop` branch.
```sh
git checkout develop
git pull
git pull origin feature/#777
git push
```
Finally, we may have all the features that are needed to release a new version of Electron.NET. Here we tag the release. For instance for the 1.0 release we use `v1.0`.
```sh
git checkout main
git merge develop
git tag v1.0
```
(The last part is automatically performed by our CI system. Don't tag manually.)
### Versioning
The rules of [semver](http://semver.org/) don't necessarily apply here, but we will try to stay quite close to them.
Prior to version 1.0.0 we use the following scheme:
1. MINOR versions for reaching a feature milestone potentially combined with dramatic API changes
2. PATCH versions for refinements (e.g. performance improvements, bug fixes)
After releasing version 1.0.0 the scheme changes to become:
1. MAJOR versions at maintainers' discretion following significant changes to the codebase (e.g., API changes)
2. MINOR versions for backwards-compatible enhancements (e.g., performance improvements)
3. PATCH versions for backwards-compatible bug fixes (e.g., spec compliance bugs, support issues)
#### Code style
Regarding code style like indentation and whitespace, **follow the conventions you see used in the source already.** In general most of the [C# coding guidelines from Microsoft](https://msdn.microsoft.com/en-us/library/ff926074.aspx) are followed. This project prefers type inference with `var` to explicitly stating (redundant) information.
It is also important to keep a certain `async`-flow and to always use `ConfigureAwait(false)` in conjunction with an `await` expression.
## Backwards Compatibility
We always try to remain backwards compatible beyond the currently supported versions of .NET.
For instance, in December 2025 there have been activity to remove .NET 6 support from the codebase. We rejected this. Key points:
1. We have absolutely no need to drop `.net6` support. It doesn't hurt us in any way.
2. Many are still using `.net6`, including Electron.NET (non-Core) users. It doesn't make sense to force them to update two things at the same time (.NET + Electron.NET).
3. We MUST NOT and NEVER update `Microsoft.Build.Utilities.Core`. This will make Electron.NET stop working on older Visual Studio and MSBuild versions. There's are also no reasons to update it in the first place.
It's important to note that the Microsoft label of "Out of support" on .NET has almost no practical meaning. We've rarely (if ever) seen any bugs fixed in the same .NET version which mattered. The bugs that all new .NET versions have are much worse than mature .NET versions which are declared as "out of support". Keep in mind that the LTS matters most for active development / ongoing supported projects. If, e.g., a TV has been released a decade ago it most likely won't be patched. Still, you might want to deploy applications to it, which then naturally would involve being based on "out of support" versions of the framework.
TL;DR: Unless there is a technical reason (e.g., a crucial new API not being available) we should not drop "out of support" .NET versions. At the time of writing (December 2025) the minimum supported .NET version remains at `.net6`.
## Timeline
**All of this information is related to ElectronNET.Core pre-v1!**
We pretty much release whenever we have something new (i.e., do fixes such as a 0.1.1, or add new features, such as a 0.2.0) quite quickly.
We will go for a 1.0.0 release of this as early as ~mid of January 2026 (unless we find some critical things or want to extend the beta phase for ElectronNET.Core). This should be sufficient time to get some user input and have enough experience to call it stable.
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: [GregorBiswanger, FlorianRappl]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: https://donorbox.org/electron-net
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
* **Version**:
* **Target**:
Steps to Reproduce:
1.
2.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: Feature
assignees: ''
---
================================================
FILE: .github/ISSUE_TEMPLATE/question.md
================================================
---
name: Question
about: The issue tracker is not for questions. Please ask questions on https://stackoverflow.com/questions/tagged/electron.net
or via chat in https://gitter.im/ElectronNET/community.
title: ''
labels: question
assignees: ''
---
🚨 The issue tracker is not for questions 🚨
The issue tracker is not for questions. Please ask questions on https://stackoverflow.com/questions/tagged/electron.net or via chat in https://gitter.im/ElectronNET/community.
================================================
FILE: .github/workflows/Build and Publish.yml
================================================
name: Build and Publish
on: [push]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
concurrency:
group: build-publish-${{ github.ref }}
cancel-in-progress: true
jobs:
Integration-Tests:
uses: ./.github/workflows/integration-tests.yml
name: '1'
Publish:
needs: [Integration-Tests]
runs-on: windows-latest
timeout-minutes: 10
name: '2 / Publish'
steps:
- uses: actions/checkout@v4
- name: Setup dotnet
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
6.0.x
8.0.x
10.0.x
- name: Build
run: |
if ($env:GITHUB_REF -eq "refs/heads/main") {
.\build.ps1 -Target Publish
} elseif ($env:GITHUB_REF -eq "refs/heads/develop") {
.\build.ps1 -Target PrePublish
} else {
.\build.ps1
}
================================================
FILE: .github/workflows/PR Validation.yml
================================================
name: PR Validation
on: [pull_request]
concurrency:
group: pr-validation-${{ github.ref }}
cancel-in-progress: true
jobs:
Whitespace-Check:
uses: ./.github/workflows/trailing-whitespace-check.yml
secrets: inherit
name: '1'
Tests:
needs: Whitespace-Check
uses: ./.github/workflows/integration-tests.yml
secrets: inherit
name: '2'
build:
needs: [Whitespace-Check, Tests]
runs-on: windows-latest
timeout-minutes: 10
name: '3 / Build'
steps:
- uses: actions/checkout@v4
- name: Setup dotnet
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
6.0.x
8.0.x
10.0.x
- name: Build
run: .\build.ps1
================================================
FILE: .github/workflows/integration-tests.yml
================================================
name: Tests
on:
workflow_call:
concurrency:
group: integration-tests-${{ github.ref }}
cancel-in-progress: true
jobs:
tests:
name: ${{ matrix.os }} API-${{ matrix.electronVersion }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, ubuntu-24.04, windows-2022, windows-2025, macos-14, macos-15-intel, macos-26]
electronVersion: ['30.4.0', '38.2.2']
include:
- os: ubuntu-22.04
rid: linux-x64
- os: ubuntu-24.04
rid: linux-x64
- os: windows-2022
rid: win-x64
- os: windows-2025
rid: win-x64
- os: macos-14
rid: osx-arm64
- os: macos-15-intel
rid: osx-x64
- os: macos-26
rid: osx-arm64
env:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
DOTNET_NOLOGO: 1
CI: true
ELECTRON_ENABLE_LOGGING: 1
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Random delay (0-20 seconds)
shell: bash
run: |
DELAY=$((RANDOM % 21))
echo "Waiting for $DELAY seconds..."
sleep $DELAY
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Restore
run: dotnet restore -r ${{ matrix.rid }} -p:RuntimeIdentifier=${{ matrix.rid }} -p:ElectronVersion=${{ matrix.electronVersion }} src/ElectronNET.IntegrationTests/ElectronNET.IntegrationTests.csproj
- name: Build
run: dotnet build --no-restore -c Release -r ${{ matrix.rid }} -p:RuntimeIdentifier=${{ matrix.rid }} -p:ElectronVersion=${{ matrix.electronVersion }} src/ElectronNET.IntegrationTests/ElectronNET.IntegrationTests.csproj
- name: Install Linux GUI dependencies
if: runner.os == 'Linux'
run: |
set -e
sudo apt-get update
. /etc/os-release
if [ "$VERSION_ID" = "24.04" ]; then ALSA_PKG=libasound2t64; else ALSA_PKG=libasound2; fi
echo "Using ALSA package: $ALSA_PKG"
sudo apt-get install -y xvfb \
libgtk-3-0 libnss3 libgdk-pixbuf-2.0-0 libdrm2 libgbm1 libxss1 libxtst6 libatk-bridge2.0-0 libatk1.0-0 libatspi2.0-0 libx11-xcb1 "$ALSA_PKG"
- name: Run tests (Linux)
if: runner.os == 'Linux'
continue-on-error: true
run: |
mkdir -p test-results
xvfb-run -a dotnet test src/ElectronNET.IntegrationTests/ElectronNET.IntegrationTests.csproj \
-c Release --no-build -r ${{ matrix.rid }} -p:RuntimeIdentifier=${{ matrix.rid }} -p:ElectronVersion=${{ matrix.electronVersion }} \
--logger "trx;LogFileName=${{ matrix.os }}-electron-${{ matrix.electronVersion }}.trx" \
--logger "console;verbosity=detailed" \
--results-directory test-results
- name: Run tests (Windows)
if: runner.os == 'Windows'
continue-on-error: true
run: |
New-Item -ItemType Directory -Force -Path test-results | Out-Null
dotnet test src/ElectronNET.IntegrationTests/ElectronNET.IntegrationTests.csproj -c Release --no-build -r ${{ matrix.rid }} -p:RuntimeIdentifier=${{ matrix.rid }} -p:ElectronVersion=${{ matrix.electronVersion }} --logger "trx;LogFileName=${{ matrix.os }}-electron-${{ matrix.electronVersion }}.trx" --logger "console;verbosity=detailed" --results-directory test-results
- name: Run tests (macOS)
if: runner.os == 'macOS'
continue-on-error: true
run: |
mkdir -p test-results
dotnet test src/ElectronNET.IntegrationTests/ElectronNET.IntegrationTests.csproj -c Release --no-build -r ${{ matrix.rid }} -p:RuntimeIdentifier=${{ matrix.rid }} -p:ElectronVersion=${{ matrix.electronVersion }} --logger "trx;LogFileName=${{ matrix.os }}-electron-${{ matrix.electronVersion }}.trx" --logger "console;verbosity=detailed" --results-directory test-results
- name: Upload raw test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-${{ matrix.os }}-electron-${{ matrix.electronVersion }}
path: test-results/*.trx
retention-days: 7
summary:
name: Test Results
runs-on: ubuntu-24.04
if: always()
needs: [tests]
permissions:
actions: read
contents: read
checks: write
pull-requests: write
steps:
- name: Download all test results
uses: actions/download-artifact@v4
with:
path: test-results
- name: Setup .NET (for CTRF conversion)
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Install CTRF TRX→CTRF converter (dotnet tool)
run: |
dotnet new tool-manifest
dotnet tool install DotnetCtrfJsonReporter --local
- name: Convert TRX → CTRF and clean names (filePath=OS|Electron X.Y.Z)
shell: bash
run: |
set -euo pipefail
mkdir -p ctrf
shopt -s globstar nullglob
conv=0
for trx in test-results/**/*.trx; do
base="$(basename "$trx" .trx)" # e.g. ubuntu-22.04-electron-30.4.0
os="${base%%-electron-*}"
electron="${base#*-electron-}"
label="$os|Electron $electron"
outdir="ctrf/${label}"
mkdir -p "$outdir"
out="${outdir}/ctrf-report.json"
dotnet tool run DotnetCtrfJsonReporter -p "$trx" -d "$outdir" -f "ctrf-report.json"
jq --arg fp "$label" '.results.tests |= map(.filePath = $fp)' "$out" > "${out}.tmp" && mv "${out}.tmp" "$out"
echo "Converted & normalized $trx -> $out"
conv=$((conv+1))
done
echo "Processed $conv TRX file(s)"
- name: Publish Test Report
if: always()
uses: ctrf-io/github-test-reporter@v1
with:
report-path: 'ctrf/**/*.json'
summary: true
pull-request: false
status-check: false
status-check-name: 'Integration Tests'
use-suite-name: true
update-comment: true
always-group-by: true
overwrite-comment: true
exit-on-fail: true
group-by: 'suite'
upload-artifact: true
fetch-previous-results: true
summary-report: false
summary-delta-report: true
github-report: true
test-report: false
test-list-report: false
failed-report: true
failed-folded-report: false
skipped-report: true
suite-folded-report: true
suite-list-report: false
file-report: true
previous-results-report: true
insights-report: true
flaky-report: true
flaky-rate-report: true
fail-rate-report: false
slowest-report: false
report-order: 'summary-delta-report,failed-report,skipped-report,suite-folded-report,file-report,previous-results-report,github-report'
env:
GITHUB_TOKEN: ${{ github.token }}
- name: Save PR Number
if: github.event_name == 'pull_request'
run: echo "PR_NUMBER=${{ github.event.pull_request.number }}" >> $GITHUB_ENV
- name: Write PR Number to File
if: github.event_name == 'pull_request'
run: echo "$PR_NUMBER" > pr_number.txt
shell: bash
- name: Upload PR Number Artifact
if: github.event_name == 'pull_request'
uses: actions/upload-artifact@v4
with:
name: pr_number
path: pr_number.txt
- name: Summary
run: echo "All matrix test jobs completed."
================================================
FILE: .github/workflows/pr-comment.yml
================================================
name: Create PR Comments
on:
workflow_run:
workflows: [ "PR Validation" ]
types: [completed]
permissions:
contents: read
actions: read
pull-requests: write
jobs:
pr-comment:
name: Post Test Result as PR comment
runs-on: ubuntu-24.04
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion != 'cancelled'
steps:
- name: Download CTRF artifact
uses: dawidd6/action-download-artifact@v8
with:
github_token: ${{ github.token }}
run_id: ${{ github.event.workflow_run.id }}
name: ctrf-report
path: ctrf
- name: Download PR Number Artifact
uses: dawidd6/action-download-artifact@v8
with:
github_token: ${{ github.token }}
run_id: ${{ github.event.workflow_run.id }}
name: pr_number
path: pr_number
- name: Read PR Number
run: |
set -Eeuo pipefail
FILE='pr_number/pr_number.txt'
# Ensure file exists
if [ ! -f "$FILE" ] || [ -L "$FILE" ]; then
echo "Error: $FILE is missing or is not a regular file." >&2
exit 1
fi
# Chec file size
if [ "$(wc -c < "$FILE" | tr -d ' ')" -gt 200 ]; then
echo "Error: $FILE is too large." >&2
exit 1
fi
# Read first line
PR_NUMBER=""
IFS= read -r PR_NUMBER < "$FILE" || true
# Validate whether it's a number
if ! [[ "$PR_NUMBER" =~ ^[0-9]{1,10}$ ]]; then
echo "Error: PR_NUMBER is not a valid integer on the first line." >&2
exit 1
fi
printf 'PR_NUMBER=%s\n' "$PR_NUMBER" >> "$GITHUB_ENV"
- name: Post PR Comment
uses: ctrf-io/github-test-reporter@v1
with:
report-path: 'ctrf/**/*.json'
issue: ${{ env.PR_NUMBER }}
summary: true
pull-request: true
use-suite-name: true
update-comment: true
always-group-by: true
overwrite-comment: true
upload-artifact: false
pull-request-report: true
env:
GITHUB_TOKEN: ${{ github.token }}
================================================
FILE: .github/workflows/publish-wiki.yml
================================================
name: Publish wiki
on:
push:
branches: [electronnet_core, main]
workflow_dispatch:
concurrency:
group: publish-wiki
cancel-in-progress: true
permissions:
contents: write
jobs:
publish-wiki:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- name: Remove level 1 headings from Markdown files
shell: bash
run: |
find docs/ -name '*.md' -exec sed -i '1d' {} \;
- name: Move all files to root folder
shell: bash
run: |
mv docs/*/* docs/
- name: Delete unwanted files
shell: bash
run: |
# rm docs/*.xlsm
# rm docs/*.pptx
rm docs/*.shproj
- name: Stripping file extensions....
uses: softworkz/strip-markdown-extensions-from-links-action@main
with:
path: ./docs/
- name: Copy Changelog
shell: bash
run: |
cp Changelog.md docs/RelInfo/ 2>/dev/null || true
- name: Copy images to wiki/wiki folder
shell: bash
run: |
mkdir docs/wiki
cp docs/*.svg docs/wiki/ 2>/dev/null || true
cp docs/*.png docs/wiki/ 2>/dev/null || true
cp docs/*.jpg docs/wiki/ 2>/dev/null || true
cp docs/*.gif docs/wiki/ 2>/dev/null || true
cp docs/*.mp4 docs/wiki/ 2>/dev/null || true
- name: Commit and push changes
run: |
git config --global user.name "GitHub Action"
git config --global user.email "action@github.com"
git add -A
git commit -m "Automatically update Markdown files" || echo "No changes to commit"
- uses: Andrew-Chen-Wang/github-wiki-action@v4.4.0
with:
path: docs/
ignore: |
'**/*.xlsm'
'**/*.pptx'
'**/*.shproj'
================================================
FILE: .github/workflows/retry-test-jobs.yml
================================================
name: Tests auto-rerun
on:
workflow_run:
workflows: [ "PR Validation", "Build and Publish" ]
types: [ completed ]
jobs:
rerun-failed-matrix-jobs-once:
if: >
${{
github.event.workflow_run.conclusion == 'failure' &&
github.event.workflow_run.run_attempt == 1
}}
runs-on: ubuntu-24.04
permissions:
actions: write
contents: read
steps:
- name: Decide whether to rerun (only if matrix jobs failed)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
RUN_ID: ${{ github.event.workflow_run.id }}
run: |
echo "Inspecting jobs of workflow run $RUN_ID in $REPO"
jobs_json="$(gh api -R $REPO repos/$REPO/actions/runs/$RUN_ID/jobs)"
echo "Jobs and conclusions:"
echo "$jobs_json" | jq '.jobs[] | {name: .name, conclusion: .conclusion}'
failed_matrix_jobs=$(echo "$jobs_json" | jq -r '
[ .jobs[]
| select(.conclusion == "failure"
and (.name | contains(" API-")))
]
| length // 0
')
failed_matrix_jobs=${failed_matrix_jobs:-0}
if [ "${failed_matrix_jobs}" -gt 0 ]; then
echo "Detected failing Integration Tests jobs – re-running failed jobs for this run."
gh run rerun -R "$REPO" "$RUN_ID" --failed
else
echo "Only non-matrix jobs (like Test Results) failed – not auto-rerunning."
fi
================================================
FILE: .github/workflows/trailing-whitespace-check.yml
================================================
name: Whitespace Check
on:
workflow_call:
jobs:
check-whitespace:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for trailing whitespace
run: |
echo "Checking for trailing whitespace in changed files..."
# Get the base branch
BASE_SHA="${{ github.event.pull_request.base.sha }}"
HEAD_SHA="${{ github.event.pull_request.head.sha }}"
# Get list of changed files (excluding deleted files)
CHANGED_FILES=$(git diff --name-only --diff-filter=d "$BASE_SHA" "$HEAD_SHA")
if [ -z "$CHANGED_FILES" ]; then
echo "No files to check."
exit 0
fi
# File patterns to check (text files)
PATTERNS="\.cs$|\.csproj$|\.sln$|\.ts$|\.html$|\.css$|\.scss$"
# Directories and file patterns to exclude
EXCLUDE_PATTERNS="(^|\/)(\.|node_modules|bin|obj|artifacts|packages|\.vs|\.nuke\/temp)($|\/)"
ERRORS_FOUND=0
TEMP_FILE=$(mktemp)
while IFS= read -r file; do
# Skip if file doesn't exist (shouldn't happen with --diff-filter=d, but just in case)
if [ ! -f "$file" ]; then
continue
fi
# Check if file matches patterns to check
if ! echo "$file" | grep -qE "$PATTERNS"; then
continue
fi
# Check if file should be excluded
if echo "$file" | grep -qE "$EXCLUDE_PATTERNS"; then
continue
fi
# Find trailing whitespace lines, excluding XML doc placeholder lines that are exactly "/// " (one space)
MATCHES=$(grep -n '[[:space:]]$' "$file" | grep -vE '^[0-9]+:[[:space:]]*/// $' || true)
if [ -n "$MATCHES" ]; then
echo "❌ Trailing whitespace found in: $file"
echo "$MATCHES" | head -10
TOTAL=$(echo "$MATCHES" | wc -l)
if [ "$TOTAL" -gt 10 ]; then
echo " ... and $(($TOTAL - 10)) more lines"
fi
echo "1" >> "$TEMP_FILE"
fi
done <<< "$CHANGED_FILES"
ERRORS_FOUND=$(wc -l < "$TEMP_FILE" 2>/dev/null || echo "0")
rm -f "$TEMP_FILE"
if [ "$ERRORS_FOUND" -gt 0 ]; then
echo ""
echo "❌ Found trailing whitespace in $ERRORS_FOUND file(s)."
echo "Please remove trailing whitespace from the files listed above."
exit 1
else
echo "✅ No trailing whitespace found in changed files."
exit 0
fi
================================================
FILE: .gitignore
================================================
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
project.fragment.lock.json
artifacts/
!/artifacts/.gitkeep
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
#*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Mac Only settings file
.DS_Store
# Nuke build tool
.nuke/temp
/publish.cmd
================================================
FILE: .nuke/build.schema.json
================================================
{
"$schema": "http://json-schema.org/draft-04/schema#",
"definitions": {
"Host": {
"type": "string",
"enum": [
"AppVeyor",
"AzurePipelines",
"Bamboo",
"Bitbucket",
"Bitrise",
"GitHubActions",
"GitLab",
"Jenkins",
"Rider",
"SpaceAutomation",
"TeamCity",
"Terminal",
"TravisCI",
"VisualStudio",
"VSCode"
]
},
"ExecutableTarget": {
"type": "string",
"enum": [
"Clean",
"Compile",
"Default",
"Package",
"PrePublish",
"Publish",
"PublishPackages",
"PublishPreRelease",
"PublishRelease",
"Restore",
"RunUnitTests"
]
},
"Verbosity": {
"type": "string",
"description": "",
"enum": [
"Verbose",
"Normal",
"Minimal",
"Quiet"
]
},
"NukeBuild": {
"properties": {
"Continue": {
"type": "boolean",
"description": "Indicates to continue a previously failed build attempt"
},
"Help": {
"type": "boolean",
"description": "Shows the help text for this build assembly"
},
"Host": {
"description": "Host for execution. Default is 'automatic'",
"$ref": "#/definitions/Host"
},
"NoLogo": {
"type": "boolean",
"description": "Disables displaying the NUKE logo"
},
"Partition": {
"type": "string",
"description": "Partition to use on CI"
},
"Plan": {
"type": "boolean",
"description": "Shows the execution plan (HTML)"
},
"Profile": {
"type": "array",
"description": "Defines the profiles to load",
"items": {
"type": "string"
}
},
"Root": {
"type": "string",
"description": "Root directory during build execution"
},
"Skip": {
"type": "array",
"description": "List of targets to be skipped. Empty list skips all dependencies",
"items": {
"$ref": "#/definitions/ExecutableTarget"
}
},
"Target": {
"type": "array",
"description": "List of targets to be invoked. Default is '{default_target}'",
"items": {
"$ref": "#/definitions/ExecutableTarget"
}
},
"Verbosity": {
"description": "Logging verbosity during build execution. Default is 'Normal'",
"$ref": "#/definitions/Verbosity"
}
}
}
},
"allOf": [
{
"properties": {
"CommonPropsFilePath": {
"type": "string",
"description": "common.props file path - to determine the configured version"
},
"Configuration": {
"type": "string",
"description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
"enum": [
"Debug",
"Release"
]
},
"ReleaseNotesFilePath": {
"type": "string",
"description": "ReleaseNotesFilePath - To determine the lates changelog version"
},
"Solution": {
"type": "string",
"description": "Path to a solution file that is automatically loaded"
}
}
},
{
"$ref": "#/definitions/NukeBuild"
}
]
}
================================================
FILE: .nuke/parameters.json
================================================
{
"$schema": "./build.schema.json",
"Solution": "src/ElectronNET.Lean.sln"
}
================================================
FILE: .vscode/launch.json
================================================
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceRoot}/src/ElectronNET.CLI/bin/Debug/net8.0/dotnet-electronize.dll",
"args": [],
"cwd": "${workspaceRoot}/src/ElectronNET.CLI",
// For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
"console": "internalConsole",
"stopAtEntry": false,
"internalConsoleOptions": "openOnSessionStart"
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickProcess}"
}
]
}
================================================
FILE: .vscode/tasks.json
================================================
{
"version": "2.0.0",
"command": "dotnet",
"args": [],
"tasks": [
{
"label": "build",
"type": "shell",
"command": "dotnet",
"args": ["build", "${workspaceRoot}/src/ElectronNET.CLI/ElectronNET.CLI.csproj"],
"problemMatcher": "$msCompile",
"group": {
"_id": "build",
"isDefault": false
}
}
]
}
================================================
FILE: Changelog.md
================================================
# 0.4.1
## ElectronNET.Core
- Updated documentation for preload scripts (#1031) @AeonSake
- Updated timeout for electron-builder (#1013) @softworkz
- Updated disposal avoiding exceptions on teardown (#1026) @softworkz
- Updated migration guide (#1015) @hilin
- Fixed handling of `Center` property for windows (#1001)
- Fixed false alarm for `ELECTRON001`, `ELECTRON008`, and `ELECTRON009` (#1012) @softworkz
- Added missing methods on `Cookies` (#1000)
- Added overload for `GetAllDisplaysAsync` with timeout (#1033) @softworkz
- Added `OnBoundsChanged` event (#1014) @softworkz
- Added new events for `ipcMain` (#1019) @DYH1319
# 0.4.0
## ElectronNET.Core
- Fixed ElectronSingleInstance handling (#996) @softworkz
- Fixed `PackageId` handling (#993) @softworkz
- Added cross-platform npm restore and check mismatch on publish (#988) @softworkz
# 0.3.1
## ElectronNET.Core
- Fixed issue transforming the project ID (#989, #990) @softworkz
# 0.3.0
## ElectronNET.Core
- Updated infrastructure (#937, #939) @softworkz
- Updated all model classes to Electron API 39.2 (#949) @softworkz
- Fixed output path for `electron-builder` (#942) @softworkz
- Fixed floating point display resolution (#944) @softworkz
- Fixed error in case of missing electron-host-hook (#978)
- Fixed previous API break using exposed `JsonElement` objects (#938) @softworkz
- Fixed and improved several test cases (#962) @softworkz
- Fixed startup of Electron.NET from VS Code Debug Adapter (#952)
- Fixed the `BrowserWindowOptions` (#945) @softworkz
- Fixed example for `AutoMenuHide` to reflect platform capabilities (#982) @markatosi
- Added several migration checks for publishing (#966) @softworkz
- Added more test runners for E2E tests (#950, #951) @agracio
- Added dynamic updates for tray menu (#973) @davidroth
- Added matrix tests with 6 runners and 2 electron version (#948) @softworkz
- Added additional APIs for WebContents (#958) @agracio
- Added documentation for MacOS package publish (#983) @markatosi
- Added sample application for `ElectronHostHook` (#967) @adityashirsatrao007
# 0.2.0
## ElectronNET.Core
- Updated dependencies (#930) @softworkz
- Updated integration tests (#931) @softworkz
- Updated `ElectronNET.Host` (#935) @softworkz
- Removed transition period specific build configuration (#928) @softworkz
- Added `IsRunningBlazor` option to `BrowserWindowOptions` (#926)
- Added platform support attributes (#929) @softworkz
# 0.1.0
## ElectronNET.Core
- Updated `PrintToPDFOptions` to also allow specifying the `PageSize` with an object (#769)
- Updated splashscreen image to have 0 margin (#622)
- Updated the IPC API w.r.t. naming and consistency (#905) @agracio
- Updated the IPC bridge w.r.t. synchronization and thread-safety (#918) @agracio
- Updated serialization to use `System.Text.Json` replacing `Newtonsoft.Json` (#917) @Denny09310
- Fixed parameter handling for the `sendToIpcRenderer` function (#922) @softworkz
- Fixed synchronization on removing event handlers (#921) @softworkz
- Fixed creation of windows with `contextIsolation` enabled (#906) @NimbusFox
- Fixed single instance behavior using the `ElectronSingleInstance` property (#901)
- Fixed potential race conditions (#908) @softworkz
- Added option to use `ElectronSplashScreen` with an HTML file (#799)
- Added option to provide floating point value as aspect ratios with `SetAspectRatio` (#793)
- Added option to provide `TitleBarOverlay` as an object (#911) @Denny09310
- Added `TitleBarOverlay` property to `BrowserWindowOptions` (#909)
- Added `RoundedCorners` property to `BrowserWindowOptions`
- Added integration tests and robustness checks (#913) @softworkz
- Added .NET 10 as an explicit target
# 0.0.18
## ElectronNET.Core
### Highlights
- **Complete MSBuild Integration**: Eliminated CLI tool dependency, moved all build processes to MSBuild with deep Visual Studio integration
- **Modernized Architecture**: Restructured process lifecycle with .NET launching first and running Electron as child process for better control and reliability
- **Cross-Platform Development**: Build and debug Linux applications directly from Windows Visual Studio via WSL integration
- **Flexible Electron Versioning**: Removed version lock-in, users can now select any Electron version with build-time validation
- **Enhanced Debugging Experience**: ASP.NET-first debugging with Hot Reload support and improved process termination handling
- **Console App Support**: No longer requires ASP.NET - now works with simple console applications for file system or remote server HTML/JS
### Build System & Project Structure
- Eliminated electron.manifest.json configuration file, replaced with MSBuild project properties
- Introduced new package structure: ElectronNET.Core (main package), ElectronNET.Core.Api (API definitions), ElectronNET.Core.AspNet (ASP.NET integration)
- Added Runtime Identifier (RID) selection as part of project configuration
- Restructured build folder layout to use standard .NET format (bin\net8.0\win-x64) instead of bin\Desktop
- Implemented incremental build support for Electron assets to improve build performance
- Added custom MSBuild tasks for Electron-specific build operations
### Development Experience
- Implemented unpackaged run-mode for development using regular .NET builds with unpackaged Electron configuration
- Added support for building Linux packages on Windows via WSL integration
- Enabled running and debugging Linux application outputs on Windows through WSL
- Integrated TypeScript compilation with ASP.NET tooling for consistent builds
- Added process orchestration supporting 8 different launch scenarios (packaged/unpackaged × console/ASP.NET × dotnet-first/electron-first)
### Debugging & Runtime
- Dramatically improved debugging experience with ASP.NET-first launch mode
- Added Hot Reload support for ASP.NET code during development
- Implemented proper process termination handling for all exit scenarios
- Minimized startup times through optimized build and launch procedures
### Technical Improvements
- Enhanced splash screen handling with automatic path resolution
- Improved ElectronHostHook integration as proper npm package dependency
- Updated to latest TypeScript version with ESLint configuration
- Added support for custom main.js files in projects
- Implemented version management through common.props file
- Added build-time Electron version compatibility validation
### Package & Distribution
- Integrated MSBuild publishing mechanisms for creating Electron packages
- Added folder publishing support with improved parameter handling
- Implemented automated package.json generation from MSBuild properties
- Added GitHub release automation with proper versioning
- Reduced package sizes by eliminating unnecessary TypeScript dependencies
### Migration & Compatibility
- Maintained backward compatibility for existing ElectronHostHook implementations
- Removed ASP.NET requirement: Now works with simple console applications for file system or remote server HTML/JS scenarios
- Added support for both console and ASP.NET Core application types
- Preserved all existing Electron API functionality while modernizing architecture
- Added migration path for existing projects through updated package structure
This represents a comprehensive modernization of Electron.NET, addressing the major pain points around debugging, build complexity, and platform limitations while maintaining full API compatibility.
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2017-2025 Gregor Biswanger, Robert Mühsig, Florian Rappl
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: NuGet.config
================================================
================================================
FILE: README.md
================================================
[](https://github.com/ElectronNET/Electron.NET)
[](https://donorbox.org/electron-net) [](https://gitter.im/ElectronNET/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](https://github.com/ElectronNET/Electron.NET/actions/workflows/ci.yml)
# Electron.NET Core is here!
## A Complete Transformation
`ElectronNET.Core` represents a fundamental modernization of Electron.NET, addressing years of accumulated pain points while preserving full API compatibility. This isn't just an update — it's a complete rethinking of how .NET developers build and debug cross-platform desktop applications with Electron.
Read more: [**What's New in `ElectronNET.Core`**](https://github.com/ElectronNET/Electron.NET/wiki/What's-New)
Build cross platform desktop applications with .NET 6/8/10 - from console apps to ASP.NET Core (Razor Pages, MVC) to Blazor.
## Wait - how does that work exactly?
Well... there are lots of different approaches how to get a X-plat desktop app running. Electron.NET provides a range of ways to build .NET-based solutions using Electron at the side of presentation.
While the classic Electron.NET setup (using an ASP.NET host run by the Electron side) is still the primary way, there's more flexibility now. Both .NET and Electron are now able to launch the other for better lifetime management, and when you don't need a local web server (like when running content from files or remote servers), you can drop the ASP.NET stack altogether and go with a lightweight console app instead.
## 📦 NuGet
* ElectronNET.Core: [](https://www.nuget.org/packages/ElectronNET.Core.API/)
* ElectronNET.Core.API: [](https://www.nuget.org/packages/ElectronNET.Core.API/)
* ElectronNET.Core.AspNet: [](https://www.nuget.org/packages/ElectronNET.Core.AspNet/)
## 🛠 Requirements to Run
You should have installed:
* .NET 6/8 or later.
* The minimum base OS is the same as [.NET 6](https://github.com/dotnet/core/blob/main/release-notes/6.0/supported-os.md) / [.NET 8](https://github.com/dotnet/core/blob/main/release-notes/8.0/supported-os.md).
* Node.JS using at least [Version 22.x](https://nodejs.org).
## 👩🏫 Usage with ASP.NET
- Create a new ASP.NET Core project
- Install the following two NuGet packages:
```ps1
dotnet add package ElectronNET.Core
dotnet add package ElectronNET.Core.AspNet
```
### Classic ASP.NET Core
#### Enable Electron.NET on Startup
To do so, use the `UseElectron` extension method on a `WebApplicationBuilder`, an `IWebHostBuilder` or any descendants.
> [!NOTE]
> New in Electron.NET Core is that you provide a callback method as an argument to `UseElectron()`, which ensures that you get to know the right moment to set up your application UI.
#### Program.cs
```csharp
using ElectronNET.API;
using ElectronNET.API.Entities;
public static void Main(string[] args)
{
WebHost.CreateDefaultBuilder(args)
.UseElectron(args, ElectronAppReady)
.UseStartup()
.Build()
.Run();
}
public static async Task ElectronAppReady()
{
var browserWindow = await Electron.WindowManager.CreateWindowAsync(
new BrowserWindowOptions { Show = false });
browserWindow.OnReadyToShow += () => browserWindow.Show();
}
```
### Minimal API Example
For a minimal API you can use:
```csharp
using ElectronNET;
using ElectronNET.API;
using ElectronNET.API.Entities;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddElectron(); // <- might be useful to set up DI
builder.UseElectron(args, async () =>
{
var browserWindow = await Electron.WindowManager.CreateWindowAsync(
new BrowserWindowOptions { Show = false, AutoHideMenuBar = true });
browserWindow.OnReadyToShow += () => browserWindow.Show();
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
```
### Blazor
For a project with Blazor you can use:
```cs
using ElectronNET.API;
using ElectronNET.API.Entities;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddRazorComponents()
.AddInteractiveWebAssemblyComponents();
builder.Services.AddElectron(); // <-- might be useful to set up DI
builder.UseElectron(args, async () =>
{
var options = new BrowserWindowOptions {
Show = false,
IsRunningBlazor = true, // <-- crucial
};
if (OperatingSystem.IsWindows() || OperatingSystem.IsLinux())
options.AutoHideMenuBar = true;
var browserWindow = await Electron.WindowManager.CreateWindowAsync(options);
browserWindow.OnReadyToShow += () => browserWindow.Show();
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
}
app.UseStaticFiles();
app.UseAntiforgery();
app.MapRazorComponents()
.AddInteractiveWebAssemblyRenderMode();
app.Run();
```
The `IsRunningBlazor` option makes sure to set up the renderer in a way that Blazor can just run without any interference. This includes things such as HMR for development.
## 🚀 Starting and Debugging the Application
Just press `F5` in Visual Studio or use dotnet for debugging.
## 📔 Usage of the Electron API
Complete documentation is available on the Wiki.
In this YouTube video, we show you how you can create a new project, use the Electron.NET API, debug a application and build an executable desktop app for Windows: [Electron.NET - Getting Started](https://www.youtube.com/watch?v=nuM6AojRFHk)
> [!NOTE]
> The video hasn't been updated for the changes in ElectronNET.Core, so it is partially outdated.
## 👨💻 Authors
* **[Gregor Biswanger](https://github.com/GregorBiswanger)** - (Microsoft MVP, Intel Black Belt and Intel Software Innovator) is a freelance lecturer, consultant, trainer, author and speaker. He is a consultant for large and medium-sized companies, organizations and agencies for software architecture, web- and cross-platform development. You can find Gregor often on the road attending or speaking at international conferences. - [Cross-Platform-Blog](http://www.cross-platform-blog.com) - Twitter [@BFreakout](https://www.twitter.com/BFreakout)
* **[Dr. Florian Rappl](https://github.com/FlorianRappl)** - Software Developer - from Munich, Germany. Microsoft MVP & Web Geek. - [The Art of Micro Frontends](https://microfrontends.art) - [Homepage](https://florian-rappl.de) - Twitter [@florianrappl](https://twitter.com/florianrappl)
* **[softworkz](https://github.com/softworkz)** - Full Range Developer - likes to start where others gave up. MS MVP alumni and Munich citizen as well.
* **[Robert Muehsig](https://github.com/robertmuehsig)** - Software Developer - from Dresden, Germany, now living & working in Switzerland. Microsoft MVP & Web Geek. - [codeinside Blog](https://blog.codeinside.eu) - Twitter [@robert0muehsig](https://twitter.com/robert0muehsig)
See also the list of [contributors](https://github.com/ElectronNET/Electron.NET/graphs/contributors) who participated in this project.
## 🙋♀️🙋♂ Contributing
Feel free to submit a pull request if you find any bugs (to see a list of active issues, visit the [Issues section](https://github.com/ElectronNET/Electron.NET/issues).
Please make sure all commits are properly documented.
## 🙏 Donate
We do this open source work in our free time. If you'd like us to invest more time on it, please [donate](https://donorbox.org/electron-net). Donation can be used to increase some issue priority. Thank you!
[](https://donorbox.org/electron-net)
Alternatively, consider using a GitHub sponsorship for the core maintainers:
- [Gregor Biswanger](https://github.com/sponsors/GregorBiswanger)
- [Florian Rappl](https://github.com/sponsors/FlorianRappl)
Any support appreciated! 🍻
## 🎉 License
MIT-licensed. See [LICENSE](https://github.com/ElectronNET/Electron.NET/blob/main/LICENSE) for details.
**Enjoy!**
================================================
FILE: artifacts/.gitkeep
================================================
================================================
FILE: build.cmd
================================================
:; set -eo pipefail
:; SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
:; ${SCRIPT_DIR}/build.sh "$@"
:; exit $?
@ECHO OFF
powershell -ExecutionPolicy ByPass -NoProfile -File "%~dp0build.ps1" %*
================================================
FILE: build.ps1
================================================
[CmdletBinding()]
Param(
[Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
[string[]]$BuildArguments
)
Write-Output "PowerShell $($PSVersionTable.PSEdition) version $($PSVersionTable.PSVersion)"
Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { Write-Error $_ -ErrorAction Continue; exit 1 }
$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
###########################################################################
# CONFIGURATION
###########################################################################
$BuildProjectFile = "$PSScriptRoot\nuke\_build.csproj"
$TempDirectory = "$PSScriptRoot\\.nuke\temp"
$DotNetGlobalFile = "$PSScriptRoot\\global.json"
$DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1"
$DotNetChannel = "Current"
$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1
$env:DOTNET_CLI_TELEMETRY_OPTOUT = 1
$env:DOTNET_MULTILEVEL_LOOKUP = 0
###########################################################################
# EXECUTION
###########################################################################
function ExecSafe([scriptblock] $cmd) {
& $cmd
if ($LASTEXITCODE) { exit $LASTEXITCODE }
}
# If dotnet CLI is installed globally and it matches requested version, use for execution
if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and `
$(dotnet --version) -and $LASTEXITCODE -eq 0) {
$env:DOTNET_EXE = (Get-Command "dotnet").Path
}
else {
# Download install script
$DotNetInstallFile = "$TempDirectory\dotnet-install.ps1"
New-Item -ItemType Directory -Path $TempDirectory -Force | Out-Null
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
(New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile)
# If global.json exists, load expected version
if (Test-Path $DotNetGlobalFile) {
$DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json)
if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) {
$DotNetVersion = $DotNetGlobal.sdk.version
}
}
# Install by channel or version
$DotNetDirectory = "$TempDirectory\dotnet-win"
if (!(Test-Path variable:DotNetVersion)) {
ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath }
} else {
ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath }
}
$env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe"
}
Write-Output "Microsoft (R) .NET SDK version $(& $env:DOTNET_EXE --version)"
ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet }
ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments }
================================================
FILE: build.sh
================================================
#!/usr/bin/env bash
bash --version 2>&1 | head -n 1
set -eo pipefail
SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
###########################################################################
# CONFIGURATION
###########################################################################
BUILD_PROJECT_FILE="$SCRIPT_DIR/nuke/_build.csproj"
TEMP_DIRECTORY="$SCRIPT_DIR//.nuke/temp"
DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json"
DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh"
DOTNET_CHANNEL="Current"
export DOTNET_CLI_TELEMETRY_OPTOUT=1
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
export DOTNET_MULTILEVEL_LOOKUP=0
###########################################################################
# EXECUTION
###########################################################################
function FirstJsonValue {
perl -nle 'print $1 if m{"'"$1"'": "([^"]+)",?}' <<< "${@:2}"
}
# If dotnet CLI is installed globally and it matches requested version, use for execution
if [ -x "$(command -v dotnet)" ] && dotnet --version &>/dev/null; then
export DOTNET_EXE="$(command -v dotnet)"
else
# Download install script
DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh"
mkdir -p "$TEMP_DIRECTORY"
curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL"
chmod +x "$DOTNET_INSTALL_FILE"
# If global.json exists, load expected version
if [[ -f "$DOTNET_GLOBAL_FILE" ]]; then
DOTNET_VERSION=$(FirstJsonValue "version" "$(cat "$DOTNET_GLOBAL_FILE")")
if [[ "$DOTNET_VERSION" == "" ]]; then
unset DOTNET_VERSION
fi
fi
# Install by channel or version
DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix"
if [[ -z ${DOTNET_VERSION+x} ]]; then
"$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path
else
"$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path
fi
export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet"
fi
echo "Microsoft (R) .NET SDK version $("$DOTNET_EXE" --version)"
"$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet
"$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@"
================================================
FILE: docs/.docproj/DocProj.props
================================================
8.1
true
true
**/*.md;**/*.markdown
**/*.png;**/*.bmp;**/*.jpg;**/*.gif;**/*.mp4
**/*.css;**/*.js;**/*.json
================================================
FILE: docs/.docproj/DocProj.targets
================================================
================================================
FILE: docs/API/App.md
================================================
# Electron.App
Control your application's event lifecycle.
## Overview
The `Electron.App` API provides comprehensive control over your application's lifecycle, including startup, shutdown, window management, and system integration. It handles application-level events and provides methods for managing the overall application state.
## Properties
#### 📋 `CommandLine`
A `CommandLine` object that allows you to read and manipulate the command line arguments that Chromium uses.
#### 📋 `IsReady`
Application host fully started.
#### 📋 `Name`
A string property that indicates the current application's name, which is the name in the application's package.json file.
Usually the name field of package.json is a short lowercase name, according to the npm modules spec. You should usually also specify a productName field, which is your application's full capitalized name, and which will be preferred over name by Electron.
#### 📋 `NameAsync`
A `Task` property that indicates the current application's name, which is the name in the application's package.json file.
Usually the name field of package.json is a short lowercase name, according to the npm modules spec. You should usually also specify a productName field, which is your application's full capitalized name, and which will be preferred over name by Electron.
#### 📋 `UserAgentFallback`
A string which is the user agent string Electron will use as a global fallback.
This is the user agent that will be used when no user agent is set at the webContents or session level. It is useful for ensuring that your entire app has the same user agent. Set to a custom value as early as possible in your app's initialization to ensure that your overridden value is used.
#### 📋 `UserAgentFallbackAsync`
A `Task` which is the user agent string Electron will use as a global fallback.
This is the user agent that will be used when no user agent is set at the webContents or session level. It is useful for ensuring that your entire app has the same user agent. Set to a custom value as early as possible in your app's initialization to ensure that your overridden value is used.
## Methods
#### 🧊 `void AddRecentDocument(string path)`
Adds path to the recent documents list. This list is managed by the OS. On Windows you can visit the list from the task bar, and on macOS you can visit it from dock menu.
#### 🧊 `void ClearRecentDocuments()`
Clears the recent documents list.
#### 🧊 `void Exit(int exitCode = 0)`
All windows will be closed immediately without asking user and the BeforeQuit and WillQuit events will not be emitted.
**Parameters:**
- `exitCode` - Exits immediately with exitCode. exitCode defaults to 0.
#### 🧊 `void Focus()`
On Linux, focuses on the first visible window. On macOS, makes the application the active app. On Windows, focuses on the application's first window.
#### 🧊 `void Focus(FocusOptions focusOptions)`
On Linux, focuses on the first visible window. On macOS, makes the application the active app. On Windows, focuses on the application's first window.
You should seek to use the `FocusOptions.Steal` option as sparingly as possible.
**Parameters:**
- `focusOptions` - Focus options
#### 🧊 `Task GetAppMetricsAsync(CancellationToken cancellationToken = default)`
Memory and cpu usage statistics of all the processes associated with the app.
**Returns:**
Array of ProcessMetric objects that correspond to memory and cpu usage statistics of all the processes associated with the app.
#### 🧊 `Task GetAppPathAsync(CancellationToken cancellationToken = default)`
The current application directory.
**Returns:**
The current application directory.
#### 🧊 `Task GetBadgeCountAsync(CancellationToken cancellationToken = default)`
The current value displayed in the counter badge.
**Returns:**
The current value displayed in the counter badge.
#### 🧊 `Task GetCurrentActivityTypeAsync(CancellationToken cancellationToken = default)`
The type of the currently running activity.
**Returns:**
The type of the currently running activity.
#### 🧊 `Task GetGpuFeatureStatusAsync(CancellationToken cancellationToken = default)`
The Graphics Feature Status from chrome://gpu/.
Note: This information is only usable after the gpu-info-update event is emitted.
**Returns:**
The Graphics Feature Status from chrome://gpu/.
#### 🧊 `Task GetJumpListSettingsAsync(CancellationToken cancellationToken = default)`
Jump List settings for the application.
**Returns:**
Jump List settings.
#### 🧊 `Task GetLocaleAsync(CancellationToken cancellationToken = default)`
The current application locale. Possible return values are documented [here](https://www.electronjs.org/docs/api/locales).
Note: When distributing your packaged app, you have to also ship the locales folder.
Note: On Windows, you have to call it after the Ready events gets emitted.
**Returns:**
The current application locale.
#### 🧊 `Task GetLoginItemSettingsAsync(CancellationToken cancellationToken = default)`
If you provided path and args options to SetLoginItemSettings then you need to pass the same arguments here for LoginItemSettings.OpenAtLogin to be set correctly.
**Returns:**
Login item settings.
#### 🧊 `Task GetPathAsync(PathName pathName, CancellationToken cancellationToken = default)`
The path to a special directory. If GetPathAsync is called without called SetAppLogsPath being called first, a default directory will be created equivalent to calling SetAppLogsPath without a path parameter.
**Parameters:**
- `pathName` - Special directory.
**Returns:**
A path to a special directory or file associated with name.
#### 🧊 `Task GetVersionAsync(CancellationToken cancellationToken = default)`
The version of the loaded application. If no version is found in the application's package.json file, the version of the current bundle or executable is returned.
**Returns:**
The version of the loaded application.
#### 🧊 `Task HasSingleInstanceLockAsync(CancellationToken cancellationToken = default)`
This method returns whether or not this instance of your app is currently holding the single instance lock. You can request the lock with RequestSingleInstanceLockAsync and release with ReleaseSingleInstanceLock.
**Returns:**
Whether this instance of your app is currently holding the single instance lock.
#### 🧊 `void Hide()`
Hides all application windows without minimizing them.
#### 🧊 `Task ImportCertificateAsync(ImportCertificateOptions options, CancellationToken cancellationToken = default)`
Imports the certificate in pkcs12 format into the platform certificate store. callback is called with the result of import operation, a value of 0 indicates success while any other value indicates failure according to chromium net_error_list.
**Parameters:**
- `options` - Import certificate options
- `cancellationToken` - The cancellation token
**Returns:**
Result of import. Value of 0 indicates success.
#### 🧊 `void InvalidateCurrentActivity()`
Invalidates the current Handoff user activity.
#### 🧊 `Task IsAccessibilitySupportEnabledAsync(CancellationToken cancellationToken = default)`
true if Chrome's accessibility support is enabled, false otherwise. This API will return true if the use of assistive technologies, such as screen readers, has been detected. See Chromium's accessibility docs for more details.
**Returns:**
true if Chrome's accessibility support is enabled, false otherwise.
#### 🧊 `Task IsDefaultProtocolClientAsync(string protocol, CancellationToken cancellationToken = default)`
This method checks if the current executable is the default handler for a protocol (aka URI scheme).
Note: On macOS, you can use this method to check if the app has been registered as the default protocol handler for a protocol. You can also verify this by checking ~/Library/Preferences/com.apple.LaunchServices.plist on the macOS machine. Please refer to Apple's documentation for details.
The API uses the Windows Registry and LSCopyDefaultHandlerForURLScheme internally.
**Parameters:**
- `protocol` - The name of your protocol, without ://
- `cancellationToken` - The cancellation token
**Returns:**
Whether the current executable is the default handler for a protocol (aka URI scheme).
#### 🧊 `Task IsUnityRunningAsync(CancellationToken cancellationToken = default)`
Whether the current desktop environment is Unity launcher.
**Returns:**
Whether the current desktop environment is Unity launcher.
#### 🧊 `void Quit()`
Try to close all windows. The BeforeQuit event will be emitted first. If all windows are successfully closed, the WillQuit event will be emitted and by default the application will terminate. This method guarantees that all beforeunload and unload event handlers are correctly executed. It is possible that a window cancels the quitting by returning false in the beforeunload event handler.
#### 🧊 `void ReleaseSingleInstanceLock()`
Releases all locks that were created by makeSingleInstance. This will allow multiple instances of the application to once again run side by side.
#### 🧊 `void Relaunch()`
Relaunches the app when current instance exits. By default the new instance will use the same working directory and command line arguments with current instance.
Note that this method does not quit the app when executed, you have to call Quit or Exit after calling Relaunch() to make the app restart.
When Relaunch() is called for multiple times, multiple instances will be started after current instance exited.
#### 🧊 `void Relaunch(RelaunchOptions relaunchOptions)`
Relaunches the app when current instance exits. By default the new instance will use the same working directory and command line arguments with current instance. When RelaunchOptions.Args is specified, the RelaunchOptions.Args will be passed as command line arguments instead. When RelaunchOptions.ExecPath is specified, the RelaunchOptions.ExecPath will be executed for relaunch instead of current app.
Note that this method does not quit the app when executed, you have to call Quit or Exit after calling Relaunch() to make the app restart.
When Relaunch() is called for multiple times, multiple instances will be started after current instance exited.
**Parameters:**
- `relaunchOptions` - Options for the relaunch
#### 🧊 `Task RemoveAsDefaultProtocolClientAsync(string protocol, CancellationToken cancellationToken = default)`
This method checks if the current executable as the default handler for a protocol (aka URI scheme). If so, it will remove the app as the default handler.
**Parameters:**
- `protocol` - The name of your protocol, without ://
- `cancellationToken` - The cancellation token
**Returns:**
Whether the call succeeded.
#### 🧊 `Task RequestSingleInstanceLockAsync(Action newInstanceOpened, CancellationToken cancellationToken = default)`
The return value of this method indicates whether or not this instance of your application successfully obtained the lock. If it failed to obtain the lock, you can assume that another instance of your application is already running with the lock and exit immediately.
I.e.This method returns true if your process is the primary instance of your application and your app should continue loading. It returns false if your process should immediately quit as it has sent its parameters to another instance that has already acquired the lock.
On macOS, the system enforces single instance automatically when users try to open a second instance of your app in Finder, and the open-file and open-url events will be emitted for that.However when users start your app in command line, the system's single instance mechanism will be bypassed, and you have to use this method to ensure single instance.
**Parameters:**
- `newInstanceOpened` - Lambda with an array of the second instance's command line arguments. The second parameter is the working directory path.
- `cancellationToken` - The cancellation token
**Returns:**
This method returns false if your process is the primary instance of the application and your app should continue loading. And returns true if your process has sent its parameters to another instance, and you should immediately quit.
#### 🧊 `void ResignCurrentActivity()`
Marks the current Handoff user activity as inactive without invalidating it.
#### 🧊 `void SetAccessibilitySupportEnabled(bool enabled)`
Manually enables Chrome's accessibility support, allowing to expose accessibility switch to users in application settings. See Chromium's accessibility docs for more details. Disabled (false) by default.
This API must be called after the Ready event is emitted.
Note: Rendering accessibility tree can significantly affect the performance of your app. It should not be enabled by default.
**Parameters:**
- `enabled` - Enable or disable accessibility tree rendering
#### 🧊 `void SetAppLogsPath(string path)`
Sets or creates a directory your app's logs which can then be manipulated with GetPathAsync or SetPath.
Calling SetAppLogsPath without a path parameter will result in this directory being set to ~/Library/Logs/YourAppName on macOS, and inside the userData directory on Linux and Windows.
**Parameters:**
- `path` - A custom path for your logs. Must be absolute
#### 🧊 `void SetAppUserModelId(string id)`
Changes the Application User Model ID to id.
**Parameters:**
- `id` - Model Id
#### 🧊 `Task SetAsDefaultProtocolClientAsync(string protocol, CancellationToken cancellationToken = default)`
Sets the current executable as the default handler for a protocol (aka URI scheme). It allows you to integrate your app deeper into the operating system. Once registered, all links with your-protocol:// will be opened with the current executable. The whole link, including protocol, will be passed to your application as a parameter.
Note: On macOS, you can only register protocols that have been added to your app's info.plist, which cannot be modified at runtime. However, you can change the file during build time via Electron Forge, Electron Packager, or by editing info.plist with a text editor. Please refer to Apple's documentation for details.
Note: In a Windows Store environment (when packaged as an appx) this API will return true for all calls but the registry key it sets won't be accessible by other applications. In order to register your Windows Store application as a default protocol handler you must declare the protocol in your manifest.
The API uses the Windows Registry and LSSetDefaultHandlerForURLScheme internally.
**Parameters:**
- `protocol` - The name of your protocol, without ://. For example, if you want your app to handle electron:// links, call this method with electron as the parameter.
- `cancellationToken` - The cancellation token
**Returns:**
Whether the call succeeded.
#### 🧊 `Task SetBadgeCountAsync(int count, CancellationToken cancellationToken = default)`
Sets the counter badge for current app. Setting the count to 0 will hide the badge. On macOS it shows on the dock icon. On Linux it only works for Unity launcher.
Note: Unity launcher requires the existence of a .desktop file to work, for more information please read Desktop Environment Integration.
**Parameters:**
- `count` - Counter badge
- `cancellationToken` - The cancellation token
**Returns:**
Whether the call succeeded.
#### 🧊 `void SetJumpList(JumpListCategory[] categories)`
Sets or removes a custom Jump List for the application. If categories is null the previously set custom Jump List (if any) will be replaced by the standard Jump List for the app (managed by Windows).
Note: If a JumpListCategory object has neither the Type nor the Name property set then its Type is assumed to be tasks. If the Name property is set but the Type property is omitted then the Type is assumed to be custom.
Note: Users can remove items from custom categories, and Windows will not allow a removed item to be added back into a custom category until after the next successful call to SetJumpList. Any attempt to re-add a removed item to a custom category earlier than that will result in the entire custom category being omitted from the Jump List. The list of removed items can be obtained using GetJumpListSettingsAsync.
**Parameters:**
- `categories` - Array of JumpListCategory objects
#### 🧊 `void SetLoginItemSettings(LoginSettings loginSettings)`
Set the app's login item settings.
To work with Electron's autoUpdater on Windows, which uses Squirrel, you'll want to set the launch path to Update.exe, and pass arguments that specify your application name.
**Parameters:**
- `loginSettings` - Login settings
#### 🧊 `void SetPath(PathName name, string path)`
Overrides the path to a special directory or file associated with name. If the path specifies a directory that does not exist, an Error is thrown. In that case, the directory should be created with fs.mkdirSync or similar.
You can only override paths of a name defined in GetPathAsync.
By default, web pages' cookies and caches will be stored under the PathName.UserData directory. If you want to change this location, you have to override the PathName.UserData path before the Ready event of the App module is emitted.
**Parameters:**
- `name` - Special directory name
- `path` - New path to a special directory
#### 🧊 `void SetUserActivity(string type, object userInfo)`
Creates an NSUserActivity and sets it as the current activity. The activity is eligible for Handoff to another device afterward.
**Parameters:**
- `type` - Uniquely identifies the activity. Maps to NSUserActivity.activityType.
- `userInfo` - App-specific state to store for use by another device
#### 🧊 `Task SetUserTasksAsync(UserTask[] userTasks, CancellationToken cancellationToken = default)`
Adds tasks to the UserTask category of the JumpList on Windows.
Note: If you'd like to customize the Jump List even more use SetJumpList instead.
**Parameters:**
- `userTasks` - Array of UserTask objects
- `cancellationToken` - The cancellation token
**Returns:**
Whether the call succeeded.
#### 🧊 `void Show()`
Shows application windows after they were hidden. Does not automatically focus them.
#### 🧊 `void ShowAboutPanel()`
Show the app's about panel options. These options can be overridden with SetAboutPanelOptions.
## Events
#### ⚡ `AccessibilitySupportChanged`
Emitted when Chrome's accessibility support changes. This event fires when assistive technologies, such as screen readers, are enabled or disabled. See https://www.chromium.org/developers/design-documents/accessibility for more details.
#### ⚡ `BrowserWindowBlur`
Emitted when a BrowserWindow blurred.
#### ⚡ `BrowserWindowCreated`
Emitted when a new BrowserWindow is created.
#### ⚡ `BrowserWindowFocus`
Emitted when a BrowserWindow gets focused.
#### ⚡ `OpenFile`
Emitted when a macOS user wants to open a file with the application. The open-file event is usually emitted when the application is already open and the OS wants to reuse the application to open the file. open-file is also emitted when a file is dropped onto the dock and the application is not yet running.
On Windows, you have to parse the arguments using App.CommandLine to get the filepath.
#### ⚡ `OpenUrl`
Emitted when a macOS user wants to open a URL with the application. Your application's Info.plist file must define the URL scheme within the CFBundleURLTypes key, and set NSPrincipalClass to AtomApplication.
#### ⚡ `Quitting`
Emitted when the application is quitting.
Note: On Windows, this event will not be emitted if the app is closed due to a shutdown/restart of the system or a user logout.
#### ⚡ `Ready`
Emitted when the application has finished basic startup.
#### ⚡ `WebContentsCreated`
Emitted when a new WebContents is created.
#### ⚡ `WillQuit`
Emitted when all windows have been closed and the application will quit.
See the description of the WindowAllClosed event for the differences between the WillQuit and WindowAllClosed events.
Note: On Windows, this event will not be emitted if the app is closed due to a shutdown/restart of the system or a user logout.
#### ⚡ `WindowAllClosed`
Emitted when all windows have been closed.
If you do not subscribe to this event and all windows are closed, the default behavior is to quit the app; however, if you subscribe, you control whether the app quits or not.If the user pressed Cmd + Q, or the developer called Quit, Electron will first try to close all the windows and then emit the WillQuit event, and in this case the WindowAllClosed event would not be emitted.
## Usage Examples
### Application Lifecycle
```csharp
// Handle app startup
Electron.App.Ready += () =>
{
Console.WriteLine("App is ready!");
};
// Handle window management
Electron.App.WindowAllClosed += () =>
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Electron.App.Quit();
}
};
// Prevent quit
Electron.App.BeforeQuit += async (args) =>
{
var result = await Electron.Dialog.ShowMessageBoxAsync("Do you want to quit?");
if (result.Response == 1) // Cancel
{
args.PreventDefault = true;
}
};
```
### Protocol Handling
```csharp
// Register custom protocol
var success = await Electron.App.SetAsDefaultProtocolClientAsync("myapp");
// Check if registered
var isDefault = await Electron.App.IsDefaultProtocolClientAsync("myapp");
```
### Jump List (Windows)
```csharp
// Set user tasks
await Electron.App.SetUserTasksAsync(new[]
{
new UserTask
{
Program = "myapp.exe",
Arguments = "--new-document",
Title = "New Document",
Description = "Create a new document"
}
});
```
### Application Information
```csharp
// Get app information
var appPath = await Electron.App.GetAppPathAsync();
var version = await Electron.App.GetVersionAsync();
var locale = await Electron.App.GetLocaleAsync();
// Set app name
await Electron.App.NameAsync; // Get current name
Electron.App.Name = "My Custom App Name";
```
### Badge Count (macOS/Linux)
```csharp
// Set badge count
await Electron.App.SetBadgeCountAsync(5);
// Get current badge count
var count = await Electron.App.GetBadgeCountAsync();
```
## Related APIs
- [Electron.WindowManager](WindowManager.md) - Window creation and management
- [Electron.Dialog](Dialog.md) - User interaction dialogs
- [Electron.Menu](Menu.md) - Application menus
## Additional Resources
- [Electron App Documentation](https://electronjs.org/docs/api/app) - Official Electron app API
- [Startup Methods](../Using/Startup-Methods.md) - Different application startup modes
================================================
FILE: docs/API/AutoUpdater.md
================================================
# Electron.AutoUpdater
Handle application updates and installation processes.
## Overview
The `Electron.AutoUpdater` API provides comprehensive functionality for handling application updates, including checking for updates, downloading, and installation. It supports multiple update providers and platforms with automatic update capabilities.
## Properties
#### 📋 `bool AllowDowngrade`
Whether to allow version downgrade. Default is false.
#### 📋 `bool AllowPrerelease`
GitHub provider only. Whether to allow update to pre-release versions. Defaults to true if application version contains prerelease components.
#### 📋 `bool AutoDownload`
Whether to automatically download an update when it is found. Default is true.
#### 📋 `bool AutoInstallOnAppQuit`
Whether to automatically install a downloaded update on app quit. Applicable only on Windows and Linux.
#### 📋 `string Channel`
Get the update channel. Not applicable for GitHub. Doesn't return channel from the update configuration, only if was previously set.
#### 📋 `Task ChannelAsync`
Get the update channel. Not applicable for GitHub. Doesn't return channel from the update configuration, only if was previously set.
#### 📋 `Task CurrentVersionAsync`
Get the current application version.
#### 📋 `bool FullChangelog`
GitHub provider only. Get all release notes (from current version to latest), not just the latest. Default is false.
#### 📋 `Dictionary RequestHeaders`
The request headers.
#### 📋 `Task> RequestHeadersAsync`
Get the current request headers.
#### 📋 `string UpdateConfigPath`
For test only. Configuration path for updates.
## Methods
#### 🧊 `Task CheckForUpdatesAndNotifyAsync()`
Asks the server whether there is an update and notifies the user if an update is available.
#### 🧊 `Task CheckForUpdatesAsync()`
Asks the server whether there is an update.
#### 🧊 `Task DownloadUpdateAsync()`
Start downloading update manually. Use this method if AutoDownload option is set to false.
**Returns:**
Path to downloaded file.
#### 🧊 `Task GetFeedURLAsync()`
Get the current feed URL.
**Returns:**
Feed URL.
#### 🧊 `void QuitAndInstall(bool isSilent = false, bool isForceRunAfter = false)`
Restarts the app and installs the update after it has been downloaded. Should only be called after update-downloaded has been emitted.
Note: QuitAndInstall() will close all application windows first and only emit before-quit event on app after that. This is different from the normal quit event sequence.
**Parameters:**
- `isSilent` - Windows-only: Runs the installer in silent mode. Defaults to false
- `isForceRunAfter` - Run the app after finish even on silent install. Not applicable for macOS
## Events
#### ⚡ `OnCheckingForUpdate`
Emitted when checking if an update has started.
#### ⚡ `OnDownloadProgress`
Emitted on download progress.
#### ⚡ `OnError`
Emitted when there is an error while updating.
#### ⚡ `OnUpdateAvailable`
Emitted when there is an available update. The update is downloaded automatically if AutoDownload is true.
#### ⚡ `OnUpdateDownloaded`
Emitted on download complete.
#### ⚡ `OnUpdateNotAvailable`
Emitted when there is no available update.
## Usage Examples
### Basic Auto-Update Setup
```csharp
// Configure auto-updater
Electron.AutoUpdater.AutoDownload = true;
Electron.AutoUpdater.AutoInstallOnAppQuit = true;
// Check for updates
var updateCheck = await Electron.AutoUpdater.CheckForUpdatesAsync();
if (updateCheck.UpdateInfo != null)
{
Console.WriteLine($"Update available: {updateCheck.UpdateInfo.Version}");
}
```
### Manual Update Management
```csharp
// Disable auto-download for manual control
Electron.AutoUpdater.AutoDownload = false;
// Check for updates
var result = await Electron.AutoUpdater.CheckForUpdatesAsync();
if (result.UpdateInfo != null)
{
Console.WriteLine($"Update found: {result.UpdateInfo.Version}");
// Ask user confirmation
var confirmResult = await Electron.Dialog.ShowMessageBoxAsync(
"Update Available",
$"Version {result.UpdateInfo.Version} is available. Download now?");
if (confirmResult.Response == 1) // Yes
{
// Download update manually
var downloadPath = await Electron.AutoUpdater.DownloadUpdateAsync();
Console.WriteLine($"Downloaded to: {downloadPath}");
// Install update
Electron.AutoUpdater.QuitAndInstall();
}
}
```
### Update Event Handling
```csharp
// Handle update events
Electron.AutoUpdater.OnCheckingForUpdate += () =>
{
Console.WriteLine("Checking for updates...");
};
Electron.AutoUpdater.OnUpdateAvailable += (updateInfo) =>
{
Console.WriteLine($"Update available: {updateInfo.Version}");
};
Electron.AutoUpdater.OnUpdateNotAvailable += (updateInfo) =>
{
Console.WriteLine("No updates available");
};
Electron.AutoUpdater.OnDownloadProgress += (progressInfo) =>
{
Console.WriteLine($"Download progress: {progressInfo.Percent}%");
};
Electron.AutoUpdater.OnUpdateDownloaded += (updateInfo) =>
{
Console.WriteLine($"Update downloaded: {updateInfo.Version}");
// Show notification to user
Electron.Notification.Show(new NotificationOptions
{
Title = "Update Downloaded",
Body = $"Version {updateInfo.Version} is ready to install.",
Actions = new[]
{
new NotificationAction { Text = "Install Now", Type = NotificationActionType.Button },
new NotificationAction { Text = "Later", Type = NotificationActionType.Button }
}
});
};
Electron.AutoUpdater.OnError += (error) =>
{
Console.WriteLine($"Update error: {error}");
Electron.Dialog.ShowErrorBox("Update Error", $"Failed to update: {error}");
};
```
### GitHub Provider Configuration
```csharp
// Configure for GitHub releases
Electron.AutoUpdater.AllowPrerelease = true; // Allow pre-release versions
Electron.AutoUpdater.FullChangelog = true; // Get full changelog
Electron.AutoUpdater.AllowDowngrade = false; // Prevent downgrades
// Set request headers if needed
Electron.AutoUpdater.RequestHeaders = new Dictionary
{
["Authorization"] = "token your-github-token",
["User-Agent"] = "MyApp-Updater"
};
```
### Update Installation
```csharp
// Install update immediately
if (updateDownloaded)
{
Electron.AutoUpdater.QuitAndInstall();
}
// Silent install (Windows only)
Electron.AutoUpdater.QuitAndInstall(isSilent: true, isForceRunAfter: true);
```
### Version Management
```csharp
// Get current version
var currentVersion = await Electron.AutoUpdater.CurrentVersionAsync;
Console.WriteLine($"Current version: {currentVersion}");
// Get update channel
var channel = await Electron.AutoUpdater.ChannelAsync;
Console.WriteLine($"Update channel: {channel}");
// Set custom feed URL
// Note: This would typically be configured in electron-builder.json
var feedUrl = await Electron.AutoUpdater.GetFeedURLAsync();
Console.WriteLine($"Feed URL: {feedUrl}");
```
## Related APIs
- [Electron.App](App.md) - Application lifecycle events during updates
- [Electron.Notification](Notification.md) - Notify users about update status
- [Electron.Dialog](Dialog.md) - Show update confirmation dialogs
## Additional Resources
- [Electron AutoUpdater Documentation](https://electronjs.org/docs/api/auto-updater) - Official Electron auto-updater API
================================================
FILE: docs/API/Clipboard.md
================================================
# Electron.Clipboard
Perform copy and paste operations on the system clipboard.
## Overview
The `Electron.Clipboard` API provides comprehensive access to the system clipboard, supporting multiple data formats including text, HTML, RTF, images, and custom data. It enables reading from and writing to the clipboard with platform-specific behavior.
## Methods
#### 🧊 `Task AvailableFormatsAsync(string type = "")`
Get an array of supported formats for the clipboard type.
**Parameters:**
- `type` - Clipboard type
**Returns:**
An array of supported formats for the clipboard type.
#### 🧊 `void Clear(string type = "")`
Clears the clipboard content.
**Parameters:**
- `type` - Clipboard type
#### 🧊 `Task ReadBookmarkAsync()`
Returns an Object containing title and url keys representing the bookmark in the clipboard. The title and url values will be empty strings when the bookmark is unavailable.
**Returns:**
Object containing title and url keys representing the bookmark in the clipboard.
#### 🧊 `Task ReadFindTextAsync()`
macOS: The text on the find pasteboard. This method uses synchronous IPC when called from the renderer process. The cached value is reread from the find pasteboard whenever the application is activated.
**Returns:**
The text on the find pasteboard.
#### 🧊 `Task ReadHTMLAsync(string type = "")`
Read the content in the clipboard as HTML markup.
**Parameters:**
- `type` - Clipboard type
**Returns:**
The content in the clipboard as markup.
#### 🧊 `Task ReadImageAsync(string type = "")`
Read an image from the clipboard.
**Parameters:**
- `type` - Clipboard type
**Returns:**
An image from the clipboard.
#### 🧊 `Task ReadRTFAsync(string type = "")`
Read the content in the clipboard as RTF.
**Parameters:**
- `type` - Clipboard type
**Returns:**
The content in the clipboard as RTF.
#### 🧊 `Task ReadTextAsync(string type = "")`
Read the content in the clipboard as plain text.
**Parameters:**
- `type` - Clipboard type
**Returns:**
The content in the clipboard as plain text.
#### 🧊 `void Write(Data data, string type = "")`
Writes data to the clipboard.
**Parameters:**
- `data` - Data object to write
- `type` - Clipboard type
#### 🧊 `void WriteBookmark(string title, string url, string type = "")`
Writes the title and url into the clipboard as a bookmark.
Note: Most apps on Windows don't support pasting bookmarks into them so you can use clipboard.write to write both a bookmark and fallback text to the clipboard.
**Parameters:**
- `title` - Bookmark title
- `url` - Bookmark URL
- `type` - Clipboard type
#### 🧊 `void WriteFindText(string text)`
macOS: Writes the text into the find pasteboard as plain text. This method uses synchronous IPC when called from the renderer process.
**Parameters:**
- `text` - Text to write to find pasteboard
#### 🧊 `void WriteHTML(string markup, string type = "")`
Writes markup to the clipboard.
**Parameters:**
- `markup` - HTML markup to write
- `type` - Clipboard type
#### 🧊 `void WriteImage(NativeImage image, string type = "")`
Writes an image to the clipboard.
**Parameters:**
- `image` - Image to write to clipboard
- `type` - Clipboard type
#### 🧊 `void WriteRTF(string text, string type = "")`
Writes the text into the clipboard in RTF.
**Parameters:**
- `text` - RTF content to write
- `type` - Clipboard type
#### 🧊 `void WriteText(string text, string type = "")`
Writes the text into the clipboard as plain text.
**Parameters:**
- `text` - Text content to write
- `type` - Clipboard type
## Usage Examples
### Basic Text Operations
```csharp
// Read text from clipboard
var text = await Electron.Clipboard.ReadTextAsync();
Console.WriteLine($"Clipboard text: {text}");
// Write text to clipboard
Electron.Clipboard.WriteText("Hello, Electron.NET!");
// Read with specific type
var html = await Electron.Clipboard.ReadHTMLAsync("public.main");
```
### Rich Content Handling
```csharp
// Copy formatted text
var htmlContent = "Title Some bold text
";
Electron.Clipboard.WriteHTML(htmlContent);
// Read RTF content
var rtf = await Electron.Clipboard.ReadRTFAsync();
Console.WriteLine($"RTF content: {rtf}");
```
### Image Operations
```csharp
// Read image from clipboard
var image = await Electron.Clipboard.ReadImageAsync();
if (image != null)
{
Console.WriteLine($"Image size: {image.Size.Width}x{image.Size.Height}");
}
// Write image to clipboard
var nativeImage = NativeImage.CreateFromPath("screenshot.png");
Electron.Clipboard.WriteImage(nativeImage);
```
### Bookmark Management
```csharp
// Read bookmark from clipboard
var bookmark = await Electron.Clipboard.ReadBookmarkAsync();
if (!string.IsNullOrEmpty(bookmark.Title))
{
Console.WriteLine($"Bookmark: {bookmark.Title} -> {bookmark.Url}");
}
// Write bookmark to clipboard
Electron.Clipboard.WriteBookmark("Electron.NET", "https://github.com/ElectronNET/Electron.NET");
```
### Advanced Clipboard Operations
```csharp
// Check available formats
var formats = await Electron.Clipboard.AvailableFormatsAsync();
Console.WriteLine($"Available formats: {string.Join(", ", formats)}");
// Clear clipboard
Electron.Clipboard.Clear();
// Write custom data
var data = new Data
{
Text = "Custom data",
Html = "Custom HTML
",
Image = nativeImage
};
Electron.Clipboard.Write(data);
```
### macOS Find Pasteboard
```csharp
// macOS specific find pasteboard operations
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
// Read find text
var findText = await Electron.Clipboard.ReadFindTextAsync();
Console.WriteLine($"Find text: {findText}");
// Write find text
Electron.Clipboard.WriteFindText("search term");
}
```
## Related APIs
- [Electron.Shell](Shell.md) - Work with file paths from clipboard
- [Electron.Notification](Notification.md) - Show clipboard operation results
## Additional Resources
- [Electron Clipboard Documentation](https://electronjs.org/docs/api/clipboard) - Official Electron clipboard API
================================================
FILE: docs/API/Dialog.md
================================================
# Electron.Dialog
Display native system dialogs for opening and saving files, alerting, etc.
## Overview
The `Electron.Dialog` API provides access to native system dialogs for file operations, message boxes, and certificate trust dialogs. These dialogs are modal and provide a consistent user experience across different platforms.
## Methods
#### 🧊 `Task ShowMessageBoxAsync(BrowserWindow browserWindow, MessageBoxOptions messageBoxOptions)`
Shows a message box, it will block the process until the message box is closed. It returns the index of the clicked button. If a callback is passed, the dialog will not block the process.
**Parameters:**
- `browserWindow` - The browserWindow argument allows the dialog to attach itself to a parent window, making it modal.
- `messageBoxOptions` - Message content and configuration
**Returns:**
The API call will be asynchronous and the result will be passed via MessageBoxResult.
#### 🧊 `Task ShowMessageBoxAsync(BrowserWindow browserWindow, string message)`
Shows a message box, it will block the process until the message box is closed. It returns the index of the clicked button. If a callback is passed, the dialog will not block the process.
**Parameters:**
- `browserWindow` - The browserWindow argument allows the dialog to attach itself to a parent window, making it modal.
- `message` - Message content
**Returns:**
The API call will be asynchronous and the result will be passed via MessageBoxResult.
#### 🧊 `Task ShowMessageBoxAsync(MessageBoxOptions messageBoxOptions)`
Shows a message box, it will block the process until the message box is closed. It returns the index of the clicked button. If a callback is passed, the dialog will not block the process.
**Parameters:**
- `messageBoxOptions` - Message content and configuration
**Returns:**
The API call will be asynchronous and the result will be passed via MessageBoxResult.
#### 🧊 `Task ShowMessageBoxAsync(string message)`
Shows a message box, it will block the process until the message box is closed. It returns the index of the clicked button. If a callback is passed, the dialog will not block the process.
**Parameters:**
- `message` - Message content
**Returns:**
The API call will be asynchronous and the result will be passed via MessageBoxResult.
#### 🧊 `Task ShowOpenDialogAsync(BrowserWindow browserWindow, OpenDialogOptions options)`
Note: On Windows and Linux an open dialog can not be both a file selector and a directory selector, so if you set properties to ['openFile', 'openDirectory'] on these platforms, a directory selector will be shown.
**Parameters:**
- `browserWindow` - The browserWindow argument allows the dialog to attach itself to a parent window, making it modal.
- `options` - Dialog configuration options
**Returns:**
An array of file paths chosen by the user
#### 🧊 `Task ShowSaveDialogAsync(BrowserWindow browserWindow, SaveDialogOptions options)`
Dialog for save files.
**Parameters:**
- `browserWindow` - The browserWindow argument allows the dialog to attach itself to a parent window, making it modal.
- `options` - Dialog configuration options
**Returns:**
Returns String, the path of the file chosen by the user, if a callback is provided it returns an empty string.
#### 🧊 `void ShowErrorBox(string title, string content)`
Displays a modal dialog that shows an error message.
This API can be called safely before the ready event the app module emits, it is usually used to report errors in early stage of startup.If called before the app readyevent on Linux, the message will be emitted to stderr, and no GUI dialog will appear.
**Parameters:**
- `title` - The title to display in the error box.
- `content` - The text content to display in the error box.
#### 🧊 `Task ShowCertificateTrustDialogAsync(BrowserWindow browserWindow, CertificateTrustDialogOptions options)`
On macOS, this displays a modal dialog that shows a message and certificate information, and gives the user the option of trusting/importing the certificate. If you provide a browserWindow argument the dialog will be attached to the parent window, making it modal.
**Parameters:**
- `browserWindow` - Parent window for modal behavior
- `options` - Certificate trust dialog options
#### 🧊 `Task ShowCertificateTrustDialogAsync(CertificateTrustDialogOptions options)`
On macOS, this displays a modal dialog that shows a message and certificate information, and gives the user the option of trusting/importing the certificate. If you provide a browserWindow argument the dialog will be attached to the parent window, making it modal.
**Parameters:**
- `options` - Certificate trust dialog options
## Usage Examples
### File Operations
```csharp
// Open multiple files
var files = await Electron.Dialog.ShowOpenDialogAsync(window, new OpenDialogOptions
{
Properties = new[] { OpenDialogProperty.OpenFile, OpenDialogProperty.MultiSelections }
});
// Save with custom extension
var path = await Electron.Dialog.ShowSaveDialogAsync(window, new SaveDialogOptions
{
DefaultPath = "backup.json",
Filters = new[] { new FileFilter { Name = "JSON", Extensions = new[] { "json" } } }
});
```
### User Confirmation
```csharp
// Confirmation dialog
var result = await Electron.Dialog.ShowMessageBoxAsync(window, new MessageBoxOptions
{
Type = MessageBoxType.Question,
Title = "Confirm Delete",
Message = $"Delete {filename}?",
Buttons = new[] { "Cancel", "Delete" },
DefaultId = 0,
CancelId = 0
});
if (result.Response == 1)
{
// Delete file
}
```
### Error Handling
```csharp
// Error dialog
Electron.Dialog.ShowErrorBox("Save Failed", "Could not save file. Please check permissions and try again.");
// Warning dialog
await Electron.Dialog.ShowMessageBoxAsync(new MessageBoxOptions
{
Type = MessageBoxType.Warning,
Title = "Warning",
Message = "This operation may take several minutes.",
Buttons = new[] { "Continue", "Cancel" }
});
```
## Related APIs
- [Electron.WindowManager](WindowManager.md) - Parent windows for modal dialogs
- [Electron.App](App.md) - Application lifecycle events
- [Electron.Shell](Shell.md) - File operations with selected paths
## Additional Resources
- [Electron Dialog Documentation](https://electronjs.org/docs/api/dialog) - Official Electron dialog API
================================================
FILE: docs/API/Dock.md
================================================
# Electron.Dock
Control your app in the macOS dock.
## Overview
The `Electron.Dock` API provides control over your application's appearance and behavior in the macOS dock. This includes bouncing the dock icon, setting badges, managing menus, and controlling visibility.
## Properties
#### 📋 `IReadOnlyCollection MenuItems`
Gets a read-only collection of all current dock menu items.
## Methods
#### 🧊 `Task BounceAsync(DockBounceType type, CancellationToken cancellationToken = default)`
When `DockBounceType.Critical` is passed, the dock icon will bounce until either the application becomes active or the request is canceled. When `DockBounceType.Informational` is passed, the dock icon will bounce for one second. However, the request remains active until either the application becomes active or the request is canceled.
Note: This method can only be used while the app is not focused; when the app is focused it will return -1.
**Parameters:**
- `type` - Can be critical or informational. The default is informational.
- `cancellationToken` - The cancellation token
**Returns:**
An ID representing the request.
#### 🧊 `void CancelBounce(int id)`
Cancel the bounce of id.
**Parameters:**
- `id` - Id of the request
#### 🧊 `void DownloadFinished(string filePath)`
Bounces the Downloads stack if the filePath is inside the Downloads folder.
**Parameters:**
- `filePath` - Path to the downloaded file
#### 🧊 `Task GetBadgeAsync(CancellationToken cancellationToken = default)`
Gets the string to be displayed in the dock's badging area.
**Returns:**
The badge string of the dock.
#### 🧊 `Task GetMenu(CancellationToken cancellationToken = default)`
Gets the application's dock menu.
**Returns:**
The application's dock menu.
#### 🧊 `void Hide()`
Hides the dock icon.
#### 🧊 `Task IsVisibleAsync(CancellationToken cancellationToken = default)`
Whether the dock icon is visible. The app.dock.show() call is asynchronous so this method might not return true immediately after that call.
**Returns:**
Whether the dock icon is visible.
#### 🧊 `void SetBadge(string text)`
Sets the string to be displayed in the dock's badging area.
**Parameters:**
- `text` - Badge text to display
#### 🧊 `void SetIcon(string image)`
Sets the image associated with this dock icon.
**Parameters:**
- `image` - Icon image path
#### 🧊 `void SetMenu(MenuItem[] menuItems)`
Sets the application's dock menu.
**Parameters:**
- `menuItems` - Array of menu items for the dock menu
#### 🧊 `void Show()`
Shows the dock icon.
## Usage Examples
### Basic Dock Operations
```csharp
// Hide/Show dock icon
Electron.Dock.Hide();
await Task.Delay(2000);
Electron.Dock.Show();
// Check visibility
var isVisible = await Electron.Dock.IsVisibleAsync();
Console.WriteLine($"Dock visible: {isVisible}");
```
### Badge Notifications
```csharp
// Set badge count
Electron.Dock.SetBadge("5");
// Get current badge
var badge = await Electron.Dock.GetBadgeAsync();
Console.WriteLine($"Current badge: {badge}");
// Clear badge
Electron.Dock.SetBadge("");
```
### Dock Icon Animation
```csharp
// Bounce for attention
var bounceId = await Electron.Dock.BounceAsync(DockBounceType.Critical);
Console.WriteLine($"Bounce ID: {bounceId}");
// Cancel bounce after 3 seconds
await Task.Delay(3000);
Electron.Dock.CancelBounce(bounceId);
// Informational bounce
await Electron.Dock.BounceAsync(DockBounceType.Informational);
```
### Dock Menu
```csharp
// Create dock menu
var dockMenuItems = new[]
{
new MenuItem { Label = "Show Window", Click = () => ShowMainWindow() },
new MenuItem { Label = "Settings", Click = () => OpenSettings() },
new MenuItem { Type = MenuType.Separator },
new MenuItem { Label = "Exit", Click = () => Electron.App.Quit() }
};
// Set dock menu
Electron.Dock.SetMenu(dockMenuItems);
// Get current menu
var currentMenu = await Electron.Dock.GetMenu();
Console.WriteLine($"Menu items: {Electron.Dock.MenuItems.Count}");
```
### Download Notifications
```csharp
// Notify about completed download
var downloadPath = "/Users/username/Downloads/document.pdf";
Electron.Dock.DownloadFinished(downloadPath);
```
### Custom Dock Icon
```csharp
// Set custom dock icon
Electron.Dock.SetIcon("assets/custom-dock-icon.png");
// Set icon based on status
if (isConnected)
{
Electron.Dock.SetIcon("assets/connected-icon.png");
}
else
{
Electron.Dock.SetIcon("assets/disconnected-icon.png");
}
```
### Application Integration
```csharp
// Update dock badge based on unread count
UpdateDockBadge(unreadMessageCount);
void UpdateDockBadge(int count)
{
if (count > 0)
{
Electron.Dock.SetBadge(count.ToString());
}
else
{
Electron.Dock.SetBadge("");
}
}
// Animate dock when receiving message
private async void OnMessageReceived()
{
await Electron.Dock.BounceAsync(DockBounceType.Informational);
Electron.Dock.SetBadge((unreadCount + 1).ToString());
}
```
## Related APIs
- [Electron.App](App.md) - Application lifecycle events
- [Electron.Notification](Notification.md) - Desktop notifications
- [Electron.Menu](Menu.md) - Menu items for dock menu
## Additional Resources
- [Electron Dock Documentation](https://electronjs.org/docs/api/dock) - Official Electron dock API
================================================
FILE: docs/API/GlobalShortcut.md
================================================
# Electron.GlobalShortcut
Register global keyboard shortcuts that work even when the application is not focused.
## Overview
The `Electron.GlobalShortcut` API provides the ability to register global keyboard shortcuts that can be triggered even when the application does not have keyboard focus. This is useful for creating system-wide hotkeys and shortcuts.
## Methods
#### 🧊 `Task IsRegisteredAsync(string accelerator)`
Check if the accelerator is registered.
**Parameters:**
- `accelerator` - Keyboard shortcut to check
**Returns:**
Whether this application has registered the accelerator.
#### 🧊 `void Register(string accelerator, Action function)`
Registers a global shortcut of accelerator. The callback is called when the registered shortcut is pressed by the user.
**Parameters:**
- `accelerator` - Keyboard shortcut combination
- `function` - Callback function to execute when shortcut is pressed
#### 🧊 `void Unregister(string accelerator)`
Unregisters the global shortcut of accelerator.
**Parameters:**
- `accelerator` - Keyboard shortcut to unregister
#### 🧊 `void UnregisterAll()`
Unregisters all of the global shortcuts.
## Usage Examples
### Basic Global Shortcuts
```csharp
// Register global shortcuts
Electron.GlobalShortcut.Register("CommandOrControl+N", () =>
{
CreateNewDocument();
});
Electron.GlobalShortcut.Register("CommandOrControl+O", () =>
{
OpenDocument();
});
Electron.GlobalShortcut.Register("CommandOrControl+S", () =>
{
SaveDocument();
});
```
### Media Control Shortcuts
```csharp
// Media playback shortcuts
Electron.GlobalShortcut.Register("MediaPlayPause", () =>
{
TogglePlayback();
});
Electron.GlobalShortcut.Register("MediaNextTrack", () =>
{
NextTrack();
});
Electron.GlobalShortcut.Register("MediaPreviousTrack", () =>
{
PreviousTrack();
});
```
### Application Control Shortcuts
```csharp
// Application control shortcuts
Electron.GlobalShortcut.Register("CommandOrControl+Shift+Q", async () =>
{
var result = await Electron.Dialog.ShowMessageBoxAsync("Quit Application?", "Are you sure you want to quit?");
if (result.Response == 1) // Yes
{
Electron.App.Quit();
}
});
Electron.GlobalShortcut.Register("CommandOrControl+Shift+H", () =>
{
ToggleMainWindow();
});
```
### Dynamic Shortcut Management
```csharp
// Register shortcuts based on user preferences
public void RegisterUserShortcuts(Dictionary shortcuts)
{
foreach (var shortcut in shortcuts)
{
Electron.GlobalShortcut.Register(shortcut.Key, shortcut.Value);
}
}
// Check if shortcut is available
public async Task IsShortcutAvailable(string accelerator)
{
return await Electron.GlobalShortcut.IsRegisteredAsync(accelerator);
}
// Unregister specific shortcut
public void UnregisterShortcut(string accelerator)
{
Electron.GlobalShortcut.Unregister(accelerator);
}
```
### Platform-Specific Shortcuts
```csharp
// macOS specific shortcuts
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Electron.GlobalShortcut.Register("Command+Comma", () =>
{
OpenPreferences();
});
Electron.GlobalShortcut.Register("Command+H", () =>
{
Electron.App.Hide();
});
}
// Windows/Linux shortcuts
else
{
Electron.GlobalShortcut.Register("Ctrl+Shift+P", () =>
{
OpenPreferences();
});
Electron.GlobalShortcut.Register("Alt+F4", () =>
{
Electron.App.Quit();
});
}
```
### Shortcut Validation
```csharp
// Validate shortcuts before registration
public async Task TryRegisterShortcut(string accelerator, Action callback)
{
if (await Electron.GlobalShortcut.IsRegisteredAsync(accelerator))
{
Console.WriteLine($"Shortcut {accelerator} is already registered");
return false;
}
try
{
Electron.GlobalShortcut.Register(accelerator, callback);
Console.WriteLine($"Successfully registered shortcut: {accelerator}");
return true;
}
catch (Exception ex)
{
Console.WriteLine($"Failed to register shortcut {accelerator}: {ex.Message}");
return false;
}
}
```
## Related APIs
- [Electron.App](App.md) - Application lifecycle events
- [Electron.Menu](Menu.md) - Menu-based shortcuts
- [Electron.WindowManager](WindowManager.md) - Window focus management
## Additional Resources
- [Electron GlobalShortcut Documentation](https://electronjs.org/docs/api/global-shortcut) - Official Electron global shortcut API
================================================
FILE: docs/API/HostHook.md
================================================
# Electron.HostHook
Execute native JavaScript/TypeScript code from the host process.
## Overview
The `Electron.HostHook` API allows you to execute native JavaScript/TypeScript code from the host process. This enables advanced integration scenarios where you need to run custom JavaScript code or access Node.js APIs directly.
## Methods
#### 🧊 `void Call(string socketEventName, params dynamic[] arguments)`
Execute native JavaScript/TypeScript code synchronously.
**Parameters:**
- `socketEventName` - Socket name registered on the host
- `arguments` - Optional parameters
#### 🧊 `Task CallAsync(string socketEventName, params dynamic[] arguments)`
Execute native JavaScript/TypeScript code asynchronously with type-safe return values.
**Parameters:**
- `T` - Expected return type
- `socketEventName` - Socket name registered on the host
- `arguments` - Optional parameters
**Returns:**
Task with the result from the executed host code.
## Usage Examples
### Basic Host Hook Execution
```csharp
// Execute simple JavaScript function
Electron.HostHook.Call("myFunction", "parameter1", 42);
// Execute with callback-style result
var result = await Electron.HostHook.CallAsync("getUserName", userId);
Console.WriteLine($"User name: {result}");
```
### Advanced Integration
```csharp
// Call custom Electron API
var fileContent = await Electron.HostHook.CallAsync("readFile", "config.json");
Console.WriteLine($"Config: {fileContent}");
// Execute with multiple parameters
var processedData = await Electron.HostHook.CallAsync("processData", rawData, options);
// Call with complex objects
var settings = new { theme = "dark", language = "en" };
var updatedSettings = await Electron.HostHook.CallAsync("updateSettings", settings);
```
### Error Handling
```csharp
try
{
// Execute host function with error handling
var result = await Electron.HostHook.CallAsync("riskyOperation", inputData);
Console.WriteLine($"Success: {result}");
}
catch (Exception ex)
{
// Handle execution errors
Console.WriteLine($"Host hook error: {ex.Message}");
Electron.Dialog.ShowErrorBox("Operation Failed", "Could not execute host function.");
}
```
### Type-Safe Operations
```csharp
// Strongly typed return values
var userInfo = await Electron.HostHook.CallAsync("getUserInfo", userId);
Console.WriteLine($"User: {userInfo.Name}, Email: {userInfo.Email}");
// Array results
var fileList = await Electron.HostHook.CallAsync("listFiles", directoryPath);
foreach (var file in fileList)
{
Console.WriteLine($"File: {file}");
}
// Complex object results
var systemStats = await Electron.HostHook.CallAsync("getSystemStats");
Console.WriteLine($"CPU: {systemStats.CpuUsage}%, Memory: {systemStats.MemoryUsage}%");
```
### Custom ElectronHostHook Setup
```csharp
// In your ElectronHostHook/index.ts
import { app } from 'electron';
export function getAppVersion(): string {
return app.getVersion();
}
export async function readConfigFile(): Promise {
const fs = require('fs').promises;
return await fs.readFile('config.json', 'utf8');
}
export function customNotification(message: string): void {
// Custom notification logic
console.log(`Custom notification: ${message}`);
}
```
### Integration with .NET Code
```csharp
// Use host hook in your application logic
public async Task GetApplicationVersion()
{
return await Electron.HostHook.CallAsync("getAppVersion");
}
public async Task LoadConfiguration()
{
try
{
var config = await Electron.HostHook.CallAsync("readConfigFile");
ApplyConfiguration(config);
}
catch (Exception ex)
{
Console.WriteLine($"Failed to load config: {ex.Message}");
UseDefaultConfiguration();
}
}
public void ShowCustomNotification(string message)
{
Electron.HostHook.Call("customNotification", message);
}
```
## Related APIs
- [Electron.IpcMain](IpcMain.md) - Inter-process communication
- [Electron.App](App.md) - Application lifecycle events
- [Electron.WebContents](WebContents.md) - Web content integration
## Additional Resources
- [Host Hook Documentation](../Core/Advanced-Migration-Topics.md) - Setting up custom host hooks
================================================
FILE: docs/API/IpcMain.md
================================================
# Electron.IpcMain
Communicate asynchronously from the main process to renderer processes.
## Overview
The `Electron.IpcMain` API provides inter-process communication between the main process and renderer processes. It allows you to send messages, listen for events, and handle communication between different parts of your Electron application.
## Methods
#### 🧊 `Task On(string channel, Action listener)`
Listens to channel, when a new message arrives listener would be called with listener(event, args...).
**Parameters:**
- `channel` - Channel name to listen on
- `listener` - Callback method to handle incoming messages
#### 🧊 `void OnSync(string channel, Func listener)`
Send a message to the renderer process synchronously via channel. Note: Sending a synchronous message will block the whole renderer process.
**Parameters:**
- `channel` - Channel name to listen on
- `listener` - Synchronous callback method
#### 🧊 `void Once(string channel, Action listener)`
Adds a one time listener method for the event. This listener is invoked only the next time a message is sent to channel, after which it is removed.
**Parameters:**
- `channel` - Channel name to listen on
- `listener` - Callback method to handle the message once
#### 🧊 `void RemoveAllListeners(string channel)`
Removes all listeners of the specified channel.
**Parameters:**
- `channel` - Channel name to remove listeners from
#### 🧊 `void Send(BrowserView browserView, string channel, params object[] data)`
Send a message to the BrowserView renderer process asynchronously via channel.
**Parameters:**
- `browserView` - Target browser view
- `channel` - Channel name to send on
- `data` - Arguments to send
#### 🧊 `void Send(BrowserWindow browserWindow, string channel, params object[] data)`
Send a message to the renderer process asynchronously via channel.
**Parameters:**
- `browserWindow` - Target browser window
- `channel` - Channel name to send on
- `data` - Arguments to send
## Usage Examples
### Basic Message Handling
```csharp
// Listen for messages from renderer
await Electron.IpcMain.On("request-data", (args) =>
{
Console.WriteLine($"Received request: {args}");
// Process the request and send response
});
// Send response back to renderer
Electron.IpcMain.Send(mainWindow, "data-response", processedData);
```
### Synchronous Communication
```csharp
// Handle synchronous requests
Electron.IpcMain.OnSync("get-user-info", (request) =>
{
var userId = request.ToString();
var userInfo = GetUserInfo(userId);
return userInfo;
});
```
### One-time Event Handling
```csharp
// Handle initialization request once
Electron.IpcMain.Once("app-initialized", (args) =>
{
Console.WriteLine("App initialized, setting up...");
InitializeApplication();
});
```
### Complex Data Exchange
```csharp
// Send complex data to renderer
var appData = new
{
Version = "1.0.0",
Features = new[] { "feature1", "feature2" },
Settings = new { Theme = "dark", Language = "en" }
};
Electron.IpcMain.Send(mainWindow, "app-config", appData);
// Listen for settings updates
await Electron.IpcMain.On("update-settings", (settings) =>
{
var config = JsonConvert.DeserializeObject(settings.ToString());
ApplySettings(config);
});
```
### Multi-Window Communication
```csharp
// Send message to specific window
var settingsWindow = await Electron.WindowManager.CreateWindowAsync();
Electron.IpcMain.Send(settingsWindow, "show-settings", currentSettings);
// Broadcast to all windows
foreach (var window in Electron.WindowManager.BrowserWindows)
{
Electron.IpcMain.Send(window, "notification", message);
}
```
### Error Handling
```csharp
// Handle IPC errors gracefully
await Electron.IpcMain.On("risky-operation", async (args) =>
{
try
{
var result = await PerformRiskyOperation(args);
Electron.IpcMain.Send(mainWindow, "operation-success", result);
}
catch (Exception ex)
{
Electron.IpcMain.Send(mainWindow, "operation-error", ex.Message);
}
});
```
### Integration with Host Hooks
```csharp
// Use with custom host functionality
await Electron.IpcMain.On("execute-host-function", async (args) =>
{
var functionName = args.ToString();
var result = await Electron.HostHook.CallAsync(functionName);
Electron.IpcMain.Send(mainWindow, "function-result", result);
});
```
## Related APIs
- [Electron.HostHook](HostHook.md) - Execute custom JavaScript functions
- [Electron.WindowManager](WindowManager.md) - Target specific windows for communication
- [Electron.WebContents](WebContents.md) - Send messages to web content
## Additional Resources
- [Electron IPC Documentation](https://electronjs.org/docs/api/ipc-main) - Official Electron IPC API
================================================
FILE: docs/API/Menu.md
================================================
# Electron.Menu
Create application menus, context menus, and menu items with full keyboard shortcut support.
## Overview
The `Electron.Menu` API provides comprehensive control over application menus and context menus. It supports native platform menus with custom menu items, submenus, keyboard shortcuts, and role-based menu items.
## Properties
#### 📋 `IReadOnlyDictionary> ContextMenuItems`
Gets a read-only dictionary of all current context menu items, keyed by browser window ID.
#### 📋 `IReadOnlyCollection MenuItems`
Gets a read-only collection of all current application menu items.
## Methods
#### 🧊 `void ContextMenuPopup(BrowserWindow browserWindow)`
Shows the context menu for the specified browser window.
**Parameters:**
- `browserWindow` - The browser window to show the context menu for
#### 🧊 `void SetApplicationMenu(MenuItem[] menuItems)`
Sets the application menu for the entire application.
**Parameters:**
- `menuItems` - Array of MenuItem objects defining the application menu
#### 🧊 `void SetContextMenu(BrowserWindow browserWindow, MenuItem[] menuItems)`
Sets a context menu for a specific browser window.
**Parameters:**
- `browserWindow` - The browser window to attach the context menu to
- `menuItems` - Array of MenuItem objects defining the context menu
## Usage Examples
### Application Menu
```csharp
// Create application menu
var menuItems = new[]
{
new MenuItem
{
Label = "File",
Submenu = new[]
{
new MenuItem { Label = "New", Click = () => CreateNewDocument() },
new MenuItem { Label = "Open", Click = () => OpenDocument() },
new MenuItem { Type = MenuType.Separator },
new MenuItem { Label = "Exit", Click = () => Electron.App.Quit() }
}
},
new MenuItem
{
Label = "Edit",
Submenu = new[]
{
new MenuItem { Role = MenuRole.Undo },
new MenuItem { Role = MenuRole.Redo },
new MenuItem { Type = MenuType.Separator },
new MenuItem { Role = MenuRole.Cut },
new MenuItem { Role = MenuRole.Copy },
new MenuItem { Role = MenuRole.Paste }
}
},
new MenuItem
{
Label = "View",
Submenu = new[]
{
new MenuItem { Role = MenuRole.Reload },
new MenuItem { Role = MenuRole.ForceReload },
new MenuItem { Role = MenuRole.ToggleDevTools },
new MenuItem { Type = MenuType.Separator },
new MenuItem { Role = MenuRole.ResetZoom },
new MenuItem { Role = MenuRole.ZoomIn },
new MenuItem { Role = MenuRole.ZoomOut }
}
},
new MenuItem
{
Label = "Window",
Submenu = new[]
{
new MenuItem { Role = MenuRole.Minimize },
new MenuItem { Role = MenuRole.Close }
}
}
};
// Set application menu
Electron.Menu.SetApplicationMenu(menuItems);
```
### Context Menu
```csharp
// Create context menu for specific window
var contextMenuItems = new[]
{
new MenuItem { Label = "Copy", Click = () => CopySelectedText() },
new MenuItem { Label = "Paste", Click = () => PasteText() },
new MenuItem { Type = MenuType.Separator },
new MenuItem { Label = "Inspect Element", Click = () => InspectElement() }
};
// Set context menu for window
Electron.Menu.SetContextMenu(mainWindow, contextMenuItems);
// Show context menu programmatically
Electron.Menu.ContextMenuPopup(mainWindow);
```
### Menu with Keyboard Shortcuts
```csharp
// Create menu with keyboard shortcuts
var menuItems = new[]
{
new MenuItem
{
Label = "File",
Submenu = new[]
{
new MenuItem
{
Label = "New",
Accelerator = "CmdOrCtrl+N",
Click = () => CreateNewDocument()
},
new MenuItem
{
Label = "Open",
Accelerator = "CmdOrCtrl+O",
Click = () => OpenDocument()
},
new MenuItem
{
Label = "Save",
Accelerator = "CmdOrCtrl+S",
Click = () => SaveDocument()
}
}
}
};
Electron.Menu.SetApplicationMenu(menuItems);
```
### Dynamic Menu Updates
```csharp
// Update menu items dynamically
var fileMenu = Electron.Menu.MenuItems.FirstOrDefault(m => m.Label == "File");
if (fileMenu?.Submenu != null)
{
var saveItem = fileMenu.Submenu.FirstOrDefault(m => m.Label == "Save");
if (saveItem != null)
{
saveItem.Enabled = HasUnsavedChanges;
}
}
```
### Platform-Specific Menus
```csharp
// macOS specific menu items
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
var macMenuItems = new[]
{
new MenuItem
{
Label = "MyApp",
Submenu = new[]
{
new MenuItem { Role = MenuRole.About },
new MenuItem { Type = MenuType.Separator },
new MenuItem { Role = MenuRole.Services },
new MenuItem { Type = MenuType.Separator },
new MenuItem { Role = MenuRole.Hide },
new MenuItem { Role = MenuRole.HideOthers },
new MenuItem { Role = MenuRole.Unhide },
new MenuItem { Type = MenuType.Separator },
new MenuItem { Role = MenuRole.Quit }
}
}
};
// Insert before File menu
var allMenus = new List(macMenuItems);
allMenus.AddRange(menuItems);
Electron.Menu.SetApplicationMenu(allMenus.ToArray());
}
```
## Related APIs
- [Electron.WindowManager](WindowManager.md) - Windows for context menus
- [Electron.App](App.md) - Application lifecycle events
- [Electron.GlobalShortcut](GlobalShortcut.md) - Global keyboard shortcuts
## Additional Resources
- [Electron Menu Documentation](https://electronjs.org/docs/api/menu) - Official Electron menu API
================================================
FILE: docs/API/NativeTheme.md
================================================
# Electron.NativeTheme
Detect and respond to changes in Chromium's native color theme.
## Overview
The `Electron.NativeTheme` API provides access to Chromium's native color theme information and allows you to detect and respond to changes in the system's dark/light mode settings. This enables your application to automatically adapt to the user's theme preferences.
## Methods
#### 🧊 `Task GetThemeSourceAsync()`
Get the current theme source setting.
**Returns:**
A `ThemeSourceMode` property that can be `ThemeSourceMode.System`, `ThemeSourceMode.Light` or `ThemeSourceMode.Dark`.
#### 🧊 `void SetThemeSource(ThemeSourceMode themeSourceMode)`
Setting this property to `ThemeSourceMode.System` will remove the override and everything will be reset to the OS default. By default 'ThemeSource' is `ThemeSourceMode.System`.
**Parameters:**
- `themeSourceMode` - The new ThemeSource
#### 🧊 `Task ShouldUseDarkColorsAsync()`
Check if the system is currently using dark colors.
**Returns:**
A bool for if the OS / Chromium currently has a dark mode enabled or is being instructed to show a dark-style UI.
#### 🧊 `Task ShouldUseHighContrastColorsAsync()`
Check if the system is currently using high contrast colors.
**Returns:**
A bool for if the OS / Chromium currently has high-contrast mode enabled or is being instructed to show a high-contrast UI.
#### 🧊 `Task ShouldUseInvertedColorSchemeAsync()`
Check if the system is currently using an inverted color scheme.
**Returns:**
A bool for if the OS / Chromium currently has an inverted color scheme or is being instructed to use an inverted color scheme.
## Events
#### ⚡ `Updated`
Emitted when something in the underlying NativeTheme has changed. This normally means that either the value of ShouldUseDarkColorsAsync, ShouldUseHighContrastColorsAsync or ShouldUseInvertedColorSchemeAsync has changed.
## Usage Examples
### Basic Theme Detection
```csharp
// Check current theme
var isDarkMode = await Electron.NativeTheme.ShouldUseDarkColorsAsync();
Console.WriteLine($"Dark mode: {isDarkMode}");
// Get current theme source
var themeSource = await Electron.NativeTheme.GetThemeSourceAsync();
Console.WriteLine($"Theme source: {themeSource}");
```
### Theme Change Monitoring
```csharp
// Monitor theme changes
Electron.NativeTheme.Updated += () =>
{
Console.WriteLine("Theme updated");
UpdateApplicationTheme();
};
async void UpdateApplicationTheme()
{
var isDarkMode = await Electron.NativeTheme.ShouldUseDarkColorsAsync();
var isHighContrast = await Electron.NativeTheme.ShouldUseHighContrastColorsAsync();
// Update application appearance
ApplyTheme(isDarkMode, isHighContrast);
}
```
### Manual Theme Control
```csharp
// Force dark theme
Electron.NativeTheme.SetThemeSource(ThemeSourceMode.Dark);
// Force light theme
Electron.NativeTheme.SetThemeSource(ThemeSourceMode.Light);
// Follow system theme
Electron.NativeTheme.SetThemeSource(ThemeSourceMode.System);
```
### Application Theme Integration
```csharp
public async Task InitializeThemeSupport()
{
// Set initial theme based on system preference
var isDarkMode = await Electron.NativeTheme.ShouldUseDarkColorsAsync();
ApplyTheme(isDarkMode);
// Monitor theme changes
Electron.NativeTheme.Updated += async () =>
{
var darkMode = await Electron.NativeTheme.ShouldUseDarkColorsAsync();
ApplyTheme(darkMode);
};
}
private void ApplyTheme(bool isDarkMode)
{
if (isDarkMode)
{
// Apply dark theme
SetDarkThemeColors();
UpdateWindowTheme("dark");
}
else
{
// Apply light theme
SetLightThemeColors();
UpdateWindowTheme("light");
}
}
```
### Advanced Theme Management
```csharp
// Check all theme properties
var isDarkMode = await Electron.NativeTheme.ShouldUseDarkColorsAsync();
var isHighContrast = await Electron.NativeTheme.ShouldUseHighContrastColorsAsync();
var isInverted = await Electron.NativeTheme.ShouldUseInvertedColorSchemeAsync();
Console.WriteLine($"Dark mode: {isDarkMode}");
Console.WriteLine($"High contrast: {isHighContrast}");
Console.WriteLine($"Inverted: {isInverted}");
// Apply appropriate theme
if (isHighContrast)
{
ApplyHighContrastTheme();
}
else if (isDarkMode)
{
ApplyDarkTheme();
}
else
{
ApplyLightTheme();
}
```
### Theme-Aware Window Creation
```csharp
// Create window with theme-appropriate settings
var isDarkMode = await Electron.NativeTheme.ShouldUseDarkColorsAsync();
var windowOptions = new BrowserWindowOptions
{
Width = 1200,
Height = 800,
Title = "My Application",
BackgroundColor = isDarkMode ? "#1a1a1a" : "#ffffff",
WebPreferences = new WebPreferences
{
// Additional web preferences based on theme
}
};
var window = await Electron.WindowManager.CreateWindowAsync(windowOptions);
```
## Related APIs
- [Electron.WindowManager](WindowManager.md) - Apply theme to windows
- [Electron.Screen](Screen.md) - Screen-related theme considerations
- [Electron.App](App.md) - Application-level theme events
## Additional Resources
- [Electron NativeTheme Documentation](https://electronjs.org/docs/api/native-theme) - Official Electron native theme API
- [Theme Support](../Core/What's-New.md) - Understanding theme functionality
- [User Experience](../Using/Configuration.md) - Design theme-aware applications
================================================
FILE: docs/API/Notification.md
================================================
# Electron.Notification
Show native desktop notifications with custom content and actions.
## Overview
The `Electron.Notification` API provides the ability to show native desktop notifications with custom titles, bodies, icons, and actions. Notifications work across Windows, macOS, and Linux with platform-specific behavior.
## Methods
#### 🧊 `Task IsSupportedAsync()`
Check if desktop notifications are supported on the current platform.
**Returns:**
Whether or not desktop notifications are supported on the current system.
#### 🧊 `void Show(NotificationOptions notificationOptions)`
Create OS desktop notifications with the specified options.
**Parameters:**
- `notificationOptions` - Notification configuration options
## Usage Examples
### Basic Notification
```csharp
// Simple notification
Electron.Notification.Show(new NotificationOptions
{
Title = "My Application",
Body = "This is a notification message",
Icon = "assets/notification-icon.png"
});
```
### Notification with Actions
```csharp
// Notification with reply action
Electron.Notification.Show(new NotificationOptions
{
Title = "New Message",
Body = "You have a new message from John",
Icon = "assets/message-icon.png",
Actions = new[]
{
new NotificationAction { Text = "Reply", Type = NotificationActionType.Button },
new NotificationAction { Text = "View", Type = NotificationActionType.Button }
},
OnClick = () => OpenMessageWindow(),
OnAction = (action) =>
{
if (action == "Reply")
{
ShowReplyDialog();
}
else if (action == "View")
{
OpenMessageWindow();
}
}
});
```
### Rich Notifications
```csharp
// Rich notification with all options
Electron.Notification.Show(new NotificationOptions
{
Title = "Download Complete",
Subtitle = "Your file has finished downloading",
Body = "document.pdf has been downloaded to your Downloads folder.",
Icon = "assets/download-icon.png",
ImageUrl = "file://" + Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "assets/preview.png"),
Sound = NotificationSound.Default,
Urgency = NotificationUrgency.Normal,
Category = "transfer.complete",
Tag = "download-123",
Actions = new[]
{
new NotificationAction { Text = "Open", Type = NotificationActionType.Button },
new NotificationAction { Text = "Show in Folder", Type = NotificationActionType.Button }
},
OnShow = () => Console.WriteLine("Notification shown"),
OnClick = () => OpenDownloadedFile(),
OnClose = () => Console.WriteLine("Notification closed"),
OnAction = (action) =>
{
if (action == "Open")
{
OpenDownloadedFile();
}
else if (action == "Show in Folder")
{
ShowInFolder();
}
}
});
```
### Platform-Specific Notifications
```csharp
// Windows toast notification
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Electron.Notification.Show(new NotificationOptions
{
Title = "Background Task",
Body = "Your backup is complete",
Icon = "assets/app-icon.ico",
Tag = "backup-complete",
RequireInteraction = true
});
}
// macOS notification with sound
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Electron.Notification.Show(new NotificationOptions
{
Title = "Alert",
Body = "Something needs your attention",
Sound = NotificationSound.Default,
Actions = new[]
{
new NotificationAction { Text = "View", Type = NotificationActionType.Button }
}
});
}
```
### Notification Management
```csharp
// Check notification support
var isSupported = await Electron.Notification.IsSupportedAsync();
Console.WriteLine($"Notifications supported: {isSupported}");
// Create notification with events
var notification = new NotificationOptions
{
Title = "Task Complete",
Body = "Your long-running task has finished",
OnShow = () => Console.WriteLine("Notification displayed"),
OnClick = () => OpenTaskResults(),
OnClose = () => Console.WriteLine("Notification dismissed")
};
Electron.Notification.Show(notification);
```
## Related APIs
- [Electron.App](App.md) - Application lifecycle events
- [Electron.Tray](Tray.md) - System tray integration with notifications
- [Electron.Screen](Screen.md) - Position notifications based on screen layout
## Additional Resources
- [Electron Notification Documentation](https://electronjs.org/docs/api/notification) - Official Electron notification API
================================================
FILE: docs/API/Overview.md
================================================
# API Reference Overview
The ElectronNET.Core API provides comprehensive access to Electron's native desktop functionality through a .NET interface. This section documents all the available API classes and their methods, events, and usage patterns.
## API Classes
### Core Application Management
- **[Electron.App](App.md)** - Control your application's event lifecycle, manage app metadata, and handle system-level operations
- **[Electron.WindowManager](WindowManager.md)** - Create and manage browser windows, control window behavior and appearance
- **[Electron.Menu](Menu.md)** - Create application menus, context menus, and menu items with full keyboard shortcut support
### User Interface & Interaction
- **[Electron.Dialog](Dialog.md)** - Display native system dialogs for opening/saving files, showing messages and alerts
- **[Electron.Notification](Notification.md)** - Show native desktop notifications with custom content and actions
- **[Electron.Tray](Tray.md)** - Create system tray icons with context menus and tooltip support
- **[Electron.Dock](Dock.md)** - macOS dock integration for bounce effects and badge counts
### System Integration
- **[Electron.Shell](Shell.md)** - Desktop integration for opening files, URLs, and accessing system paths
- **[Electron.Clipboard](Clipboard.md)** - Read from and write to the system clipboard with multiple data formats
- **[Electron.Screen](Screen.md)** - Access display and screen information for responsive layouts
- **[Electron.NativeTheme](NativeTheme.md)** - Detect and respond to system theme changes (light/dark mode)
### Communication & Automation
- **[Electron.IpcMain](IpcMain.md)** - Inter-process communication between main process and renderer processes
- **[Electron.HostHook](HostHook.md)** - Custom host hook functionality for advanced integration scenarios
- **[Electron.GlobalShortcut](GlobalShortcut.md)** - Register global keyboard shortcuts that work even when app is not focused
- **[Electron.AutoUpdater](AutoUpdater.md)** - Handle application updates and installation processes
### System Monitoring
- **[Electron.PowerMonitor](PowerMonitor.md)** - Monitor system power events like sleep, wake, and battery status
## API Relationships
### Window and Dialog Integration
- Use `BrowserWindow` instances as parent windows for dialogs
- Dialogs automatically become modal when parent window is provided
- Window events coordinate with application lifecycle events
### IPC Communication
- `IpcMain` handles communication from renderer processes
- Use with `Electron.WindowManager` for window-specific messaging
- Coordinate with `Electron.App` events for application-wide communication
### System Integration
- `Shell` operations work with file paths from `Dialog` operations
- `Screen` information helps create properly sized windows
- `Notification` and `Tray` provide complementary user interaction methods
## 🚀 Next Steps
- **[Electron.App](App.md)** - Start with application lifecycle management
- **[Electron.WindowManager](WindowManager.md)** - Learn window creation and management
- **[Electron.Dialog](Dialog.md)** - Add file operations and user interactions
- **[Electron.Menu](Menu.md)** - Implement application menus and shortcuts
## 📚 Additional Resources
- **[Electron Documentation](https://electronjs.org/docs)** - Official Electron API reference
- **[Getting Started](../GettingStarted/ASP.Net.md)** - Development setup guides
- **[Migration Guide](../Core/Migration-Guide.md)** - Moving from previous versions
================================================
FILE: docs/API/PowerMonitor.md
================================================
# Electron.PowerMonitor
Monitor system power events like sleep, wake, and battery status.
## Overview
The `Electron.PowerMonitor` API provides access to system power events and state changes. This includes monitoring when the system is going to sleep, waking up, or changing power sources.
## Events
#### ⚡ `OnAC`
Emitted when the system changes to AC power.
#### ⚡ `OnBattery`
Emitted when system changes to battery power.
#### ⚡ `OnLockScreen`
Emitted when the system is about to lock the screen.
#### ⚡ `OnResume`
Emitted when system is resuming.
#### ⚡ `OnShutdown`
Emitted when the system is about to reboot or shut down.
#### ⚡ `OnSuspend`
Emitted when the system is suspending.
#### ⚡ `OnUnLockScreen`
Emitted when the system is about to unlock the screen.
## Usage Examples
### Basic Power Event Monitoring
```csharp
// Monitor system sleep/wake
Electron.PowerMonitor.OnSuspend += () =>
{
Console.WriteLine("System going to sleep");
// Save application state
SaveApplicationState();
};
Electron.PowerMonitor.OnResume += () =>
{
Console.WriteLine("System waking up");
// Restore application state
RestoreApplicationState();
};
```
### Screen Lock/Unlock Monitoring
```csharp
// Handle screen lock events
Electron.PowerMonitor.OnLockScreen += () =>
{
Console.WriteLine("Screen locking");
// Pause real-time operations
PauseRealTimeOperations();
};
Electron.PowerMonitor.OnUnLockScreen += () =>
{
Console.WriteLine("Screen unlocking");
// Resume real-time operations
ResumeRealTimeOperations();
};
```
### Power Source Changes
```csharp
// Monitor power source changes
Electron.PowerMonitor.OnAC += () =>
{
Console.WriteLine("Switched to AC power");
// Adjust power-intensive operations
EnablePowerIntensiveFeatures();
};
Electron.PowerMonitor.OnBattery += () =>
{
Console.WriteLine("Switched to battery power");
// Reduce power consumption
EnablePowerSavingMode();
};
```
### System Shutdown Handling
```csharp
// Handle system shutdown
Electron.PowerMonitor.OnShutdown += () =>
{
Console.WriteLine("System shutting down");
// Save critical data and exit gracefully
SaveAndExit();
};
```
### Application State Management
```csharp
private bool isSuspended = false;
public void InitializePowerMonitoring()
{
// Track suspension state
Electron.PowerMonitor.OnSuspend += () =>
{
isSuspended = true;
OnSystemSleep();
};
Electron.PowerMonitor.OnResume += () =>
{
isSuspended = false;
OnSystemWake();
};
// Handle screen lock for security
Electron.PowerMonitor.OnLockScreen += () =>
{
OnScreenLocked();
};
}
private void OnSystemSleep()
{
// Pause network operations
PauseNetworkOperations();
// Save unsaved work
AutoSaveDocuments();
// Reduce resource usage
MinimizeResourceUsage();
}
private void OnSystemWake()
{
// Resume network operations
ResumeNetworkOperations();
// Check for updates
CheckForUpdates();
// Restore full functionality
RestoreFullFunctionality();
}
private void OnScreenLocked()
{
// Hide sensitive information
HideSensitiveData();
// Pause real-time features
PauseRealTimeFeatures();
}
```
### Battery Status Monitoring
```csharp
// Monitor battery status changes
Electron.PowerMonitor.OnAC += () =>
{
Console.WriteLine("Plugged in - full performance mode");
EnableFullPerformanceMode();
};
Electron.PowerMonitor.OnBattery += () =>
{
Console.WriteLine("On battery - power saving mode");
EnablePowerSavingMode();
};
```
## Related APIs
- [Electron.App](App.md) - Application lifecycle events
- [Electron.Notification](Notification.md) - Notify users about power events
## Additional Resources
- [Electron PowerMonitor Documentation](https://electronjs.org/docs/api/power-monitor) - Official Electron power monitor API
================================================
FILE: docs/API/Screen.md
================================================
# Electron.Screen
Access display and screen information for responsive layouts.
## Overview
The `Electron.Screen` API provides access to screen and display information, including screen size, display metrics, cursor position, and multi-monitor configurations. This is essential for creating responsive applications that adapt to different screen configurations.
## Methods
#### 🧊 `Task GetAllDisplaysAsync()`
Gets information about all available displays.
**Returns:**
An array of displays that are currently available.
#### 🧊 `Task GetCursorScreenPointAsync()`
Gets the current position of the mouse cursor on screen.
**Returns:**
The current absolute position of the mouse pointer.
#### 🧊 `Task GetDisplayMatchingAsync(Rectangle rectangle)`
Gets the display that most closely intersects the provided bounds.
**Parameters:**
- `rectangle` - The rectangle to find the matching display for
**Returns:**
The display that most closely intersects the provided bounds.
#### 🧊 `Task GetDisplayNearestPointAsync(Point point)`
Gets the display that is closest to the specified point.
**Parameters:**
- `point` - The point to find the nearest display for
**Returns:**
The display nearest the specified point.
#### 🧊 `Task GetMenuBarHeightAsync()`
macOS: The height of the menu bar in pixels.
**Returns:**
The height of the menu bar in pixels.
#### 🧊 `Task GetPrimaryDisplayAsync()`
Gets information about the primary display (main screen).
**Returns:**
The primary display.
## Events
#### ⚡ `OnDisplayAdded`
Emitted when a new Display has been added.
#### ⚡ `OnDisplayMetricsChanged`
Emitted when one or more metrics change in a display. The changedMetrics is an array of strings that describe the changes. Possible changes are bounds, workArea, scaleFactor and rotation.
#### ⚡ `OnDisplayRemoved`
Emitted when oldDisplay has been removed.
## Usage Examples
### Display Information
```csharp
// Get primary display
var primaryDisplay = await Electron.Screen.GetPrimaryDisplayAsync();
Console.WriteLine($"Primary display: {primaryDisplay.Size.Width}x{primaryDisplay.Size.Height}");
// Get all displays
var displays = await Electron.Screen.GetAllDisplaysAsync();
Console.WriteLine($"Available displays: {displays.Length}");
// Get display near cursor
var cursorPoint = await Electron.Screen.GetCursorScreenPointAsync();
var nearestDisplay = await Electron.Screen.GetDisplayNearestPointAsync(cursorPoint);
Console.WriteLine($"Nearest display scale factor: {nearestDisplay.ScaleFactor}");
```
### Multi-Monitor Setup
```csharp
// Get all displays for multi-monitor setup
var displays = await Electron.Screen.GetAllDisplaysAsync();
foreach (var display in displays)
{
Console.WriteLine($"Display {display.Id}:");
Console.WriteLine($" Size: {display.Size.Width}x{display.Size.Height}");
Console.WriteLine($" Position: {display.Bounds.X},{display.Bounds.Y}");
Console.WriteLine($" Scale Factor: {display.ScaleFactor}");
Console.WriteLine($" Work Area: {display.WorkArea.Width}x{display.WorkArea.Height}");
}
```
### Responsive Window Placement
```csharp
// Create window on appropriate display
var displays = await Electron.Screen.GetAllDisplaysAsync();
var targetDisplay = displays.FirstOrDefault(d => d.Bounds.X > 0) ?? displays.First();
var windowOptions = new BrowserWindowOptions
{
Width = Math.Min(1200, targetDisplay.WorkArea.Width),
Height = Math.Min(800, targetDisplay.WorkArea.Height),
X = targetDisplay.WorkArea.X + (targetDisplay.WorkArea.Width - 1200) / 2,
Y = targetDisplay.WorkArea.Y + (targetDisplay.WorkArea.Height - 800) / 2
};
var window = await Electron.WindowManager.CreateWindowAsync(windowOptions);
```
### Display Change Monitoring
```csharp
// Monitor display changes
Electron.Screen.OnDisplayAdded += (display) =>
{
Console.WriteLine($"Display added: {display.Id}");
UpdateWindowPositions();
};
Electron.Screen.OnDisplayRemoved += (display) =>
{
Console.WriteLine($"Display removed: {display.Id}");
UpdateWindowPositions();
};
Electron.Screen.OnDisplayMetricsChanged += (display, metrics) =>
{
Console.WriteLine($"Display {display.Id} metrics changed: {string.Join(", ", metrics)}");
UpdateWindowPositions();
};
void UpdateWindowPositions()
{
// Recalculate window positions based on current displays
}
```
### macOS Menu Bar Height
```csharp
// Account for menu bar height on macOS
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
var menuBarHeight = await Electron.Screen.GetMenuBarHeightAsync();
var windowOptions = new BrowserWindowOptions
{
Y = menuBarHeight, // Position below menu bar
TitleBarStyle = TitleBarStyle.Hidden // Hide title bar for custom look
};
}
```
## Related APIs
- [Electron.WindowManager](WindowManager.md) - Position windows based on screen information
- [Electron.App](App.md) - Handle display-related application events
## Additional Resources
- [Electron Screen Documentation](https://electronjs.org/docs/api/screen) - Official Electron screen API
================================================
FILE: docs/API/Shell.md
================================================
# Electron.Shell
Desktop integration for opening files, URLs, and accessing system paths.
## Overview
The `Electron.Shell` API provides system integration functionality for opening files and URLs with their default applications, managing trash/recycle bin, and creating/reading shortcut links.
## Methods
#### 🧊 `void Beep()`
Play the beep sound.
#### 🧊 `Task OpenExternalAsync(string url)`
Open the given external protocol URL in the desktop's default manner (e.g., mailto: URLs in the user's default mail agent).
**Parameters:**
- `url` - Max 2081 characters on windows
**Returns:**
The error message corresponding to the failure if a failure occurred, otherwise empty string.
#### 🧊 `Task OpenExternalAsync(string url, OpenExternalOptions options)`
Open the given external protocol URL with additional options.
**Parameters:**
- `url` - Max 2081 characters on windows
- `options` - Controls the behavior of OpenExternal
**Returns:**
The error message corresponding to the failure if a failure occurred, otherwise empty string.
#### 🧊 `Task OpenPathAsync(string path)`
Open the given file in the desktop's default manner.
**Parameters:**
- `path` - The path to the directory or file
**Returns:**
The error message corresponding to the failure if a failure occurred, otherwise empty string.
#### 🧊 `Task ReadShortcutLinkAsync(string shortcutPath)`
Resolves the shortcut link at shortcutPath. An exception will be thrown when any error happens.
**Parameters:**
- `shortcutPath` - The path to the shortcut
**Returns:**
ShortcutDetails of the shortcut.
#### 🧊 `Task ShowItemInFolderAsync(string fullPath)`
Show the given file in a file manager. If possible, select the file.
**Parameters:**
- `fullPath` - The full path to the directory or file
#### 🧊 `Task TrashItemAsync(string fullPath)`
Move the given file to trash and returns a bool status for the operation.
**Parameters:**
- `fullPath` - The full path to the directory or file
**Returns:**
Whether the item was successfully moved to the trash.
#### 🧊 `Task WriteShortcutLinkAsync(string shortcutPath, ShortcutLinkOperation operation, ShortcutDetails options)`
Creates or updates a shortcut link at shortcutPath.
**Parameters:**
- `shortcutPath` - The path to the shortcut
- `operation` - Default is ShortcutLinkOperation.Create
- `options` - Structure of a shortcut
**Returns:**
Whether the shortcut was created successfully.
## Usage Examples
### File Operations
```csharp
// Open file with default application
var error = await Electron.Shell.OpenPathAsync(filePath);
if (string.IsNullOrEmpty(error))
{
Console.WriteLine("File opened successfully");
}
else
{
Console.WriteLine($"Failed to open file: {error}");
}
// Show file in file manager
await Electron.Shell.ShowItemInFolderAsync(filePath);
// Move file to trash
var trashed = await Electron.Shell.TrashItemAsync(filePath);
Console.WriteLine($"File trashed: {trashed}");
```
### URL Operations
```csharp
// Open URL in default browser
var error = await Electron.Shell.OpenExternalAsync("https://electron.net");
if (!string.IsNullOrEmpty(error))
{
Console.WriteLine($"Failed to open URL: {error}");
}
// Open email client
await Electron.Shell.OpenExternalAsync("mailto:user@example.com");
// Open with options
var error = await Electron.Shell.OpenExternalAsync("https://example.com", new OpenExternalOptions
{
Activate = true
});
```
### System Integration
```csharp
// Play system beep
Electron.Shell.Beep();
// Create desktop shortcut
var success = await Electron.Shell.WriteShortcutLinkAsync(
@"C:\Users\Public\Desktop\MyApp.lnk",
ShortcutLinkOperation.Create,
new ShortcutDetails
{
Target = "C:\\Program Files\\MyApp\\MyApp.exe",
Description = "My Application",
WorkingDirectory = "C:\\Program Files\\MyApp"
}
);
// Read shortcut information
var details = await Electron.Shell.ReadShortcutLinkAsync(@"C:\Users\Public\Desktop\MyApp.lnk");
Console.WriteLine($"Target: {details.Target}");
```
### Integration with Dialog API
```csharp
// Use with file dialog results
var files = await Electron.Dialog.ShowOpenDialogAsync(window, options);
if (files.Length > 0)
{
var selectedFile = files[0];
// Open the selected file
await Electron.Shell.OpenPathAsync(selectedFile);
// Show in file manager
await Electron.Shell.ShowItemInFolderAsync(selectedFile);
}
```
## Related APIs
- [Electron.Dialog](Dialog.md) - Select files to open with Shell
- [Electron.App](App.md) - Application lifecycle events
- [Electron.Clipboard](Clipboard.md) - Copy file paths for Shell operations
## Additional Resources
- [Electron Shell Documentation](https://electronjs.org/docs/api/shell) - Official Electron shell API
================================================
FILE: docs/API/Tray.md
================================================
# Electron.Tray
Add icons and context menus to the system's notification area.
## Overview
The `Electron.Tray` API provides the ability to add icons and context menus to the system's notification area (system tray). This allows applications to provide quick access to common functions and maintain a presence in the system even when windows are closed.
## Properties
#### 📋 `IReadOnlyCollection MenuItems`
Gets a read-only collection of all current tray menu items.
## Methods
#### 🧊 `void Destroy()`
Destroys the tray icon immediately.
#### 🧊 `void DisplayBalloon(DisplayBalloonOptions options)`
Windows: Displays a tray balloon notification.
**Parameters:**
- `options` - Balloon notification options
#### 🧊 `Task IsDestroyedAsync()`
Check if the tray icon has been destroyed.
**Returns:**
Whether the tray icon is destroyed.
#### 🧊 `void SetImage(string image)`
Sets the image associated with this tray icon.
**Parameters:**
- `image` - New image for the tray icon
#### 🧊 `void SetPressedImage(string image)`
Sets the image associated with this tray icon when pressed on macOS.
**Parameters:**
- `image` - Image for pressed state
#### 🧊 `void SetTitle(string title)`
macOS: Sets the title displayed aside of the tray icon in the status bar.
**Parameters:**
- `title` - Title text
#### 🧊 `void SetToolTip(string toolTip)`
Sets the hover text for this tray icon.
**Parameters:**
- `toolTip` - Tooltip text
#### 🧊 `void Show(string image)`
Shows the tray icon without a context menu.
**Parameters:**
- `image` - The image to use for the tray icon
#### 🧊 `void Show(string image, MenuItem menuItem)`
Shows the tray icon with a single menu item.
**Parameters:**
- `image` - The image to use for the tray icon
- `menuItem` - Single menu item for the tray context menu
#### 🧊 `void Show(string image, MenuItem[] menuItems)`
Shows the tray icon with multiple menu items.
**Parameters:**
- `image` - The image to use for the tray icon
- `menuItems` - Array of menu items for the tray context menu
## Events
#### ⚡ `OnBalloonClick`
Windows: Emitted when the tray balloon is clicked.
#### ⚡ `OnBalloonClosed`
Windows: Emitted when the tray balloon is closed because of timeout or user manually closes it.
#### ⚡ `OnBalloonShow`
Windows: Emitted when the tray balloon shows.
#### ⚡ `OnClick`
Emitted when the tray icon is clicked.
#### ⚡ `OnDoubleClick`
macOS, Windows: Emitted when the tray icon is double clicked.
#### ⚡ `OnRightClick`
macOS, Windows: Emitted when the tray icon is right clicked.
## Usage Examples
### Basic Tray Icon
```csharp
// Simple tray icon
await Electron.Tray.Show("assets/tray-icon.png");
// Tray icon with single menu item
await Electron.Tray.Show("assets/tray-icon.png", new MenuItem
{
Label = "Show Window",
Click = () => ShowMainWindow()
});
```
### Tray with Context Menu
```csharp
// Tray with multiple menu items
var trayMenuItems = new[]
{
new MenuItem { Label = "Show Window", Click = () => ShowMainWindow() },
new MenuItem { Label = "Settings", Click = () => OpenSettings() },
new MenuItem { Type = MenuType.Separator },
new MenuItem { Label = "Exit", Click = () => Electron.App.Quit() }
};
await Electron.Tray.Show("assets/tray-icon.png", trayMenuItems);
```
### Dynamic Tray Updates
```csharp
// Update tray tooltip based on status
await Electron.Tray.SetToolTip("MyApp - Connected");
// Change tray icon based on state
if (isConnected)
{
await Electron.Tray.SetImage("assets/connected.png");
}
else
{
await Electron.Tray.SetImage("assets/disconnected.png");
}
```
### Tray Event Handling
```csharp
// Handle tray clicks
Electron.Tray.OnClick += (clickArgs, bounds) =>
{
if (clickArgs.AltKey || clickArgs.ShiftKey)
{
// Alt+Click or Shift+Click - show context menu
Electron.Menu.ContextMenuPopup(Electron.WindowManager.BrowserWindows.First());
}
else
{
// Regular click - toggle main window
ToggleMainWindow();
}
};
Electron.Tray.OnRightClick += (clickArgs, bounds) =>
{
// Show context menu on right click
Electron.Menu.ContextMenuPopup(Electron.WindowManager.BrowserWindows.First());
};
```
### Windows Balloon Notifications
```csharp
// Show Windows balloon notification
await Electron.Tray.DisplayBalloon(new DisplayBalloonOptions
{
Title = "Background Task Complete",
Content = "Your file has been processed successfully.",
Icon = "assets/notification-icon.ico"
});
// Handle balloon events
Electron.Tray.OnBalloonClick += () =>
{
ShowMainWindow();
Electron.WindowManager.BrowserWindows.First().Focus();
};
```
### macOS Tray Features
```csharp
// macOS specific tray features
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
await Electron.Tray.SetTitle("MyApp");
// Use template image for dark mode support
await Electron.Tray.SetImage("assets/tray-template.png");
await Electron.Tray.SetPressedImage("assets/tray-pressed-template.png");
}
```
### Application Integration
```csharp
// Integrate with application lifecycle
Electron.App.WindowAllClosed += () =>
{
// Keep app running in tray when windows are closed
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Electron.App.Hide();
}
};
// Handle tray double-click
Electron.Tray.OnDoubleClick += (clickArgs, bounds) =>
{
ShowMainWindow();
Electron.WindowManager.BrowserWindows.First().Focus();
};
```
## Related APIs
- [Electron.Menu](Menu.md) - Context menus for tray icons
- [Electron.Notification](Notification.md) - Desktop notifications
- [Electron.App](App.md) - Application lifecycle events
- [Electron.WindowManager](WindowManager.md) - Windows to show/hide from tray
## Additional Resources
- [Electron Tray Documentation](https://electronjs.org/docs/api/tray) - Official Electron tray API
================================================
FILE: docs/API/WebContents.md
================================================
# Electron.WebContents
Render and control web pages.
## Overview
The `Electron.WebContents` API provides control over web page content within Electron windows. It handles page loading, navigation, JavaScript execution, and web page lifecycle events.
## Properties
#### 📋 `int Id`
Gets the unique identifier for this web contents.
#### 📋 `Session Session`
Manage browser sessions, cookies, cache, proxy settings, etc.
## Methods
#### 🧊 `void ExecuteJavaScriptAsync(string code, bool userGesture = false)`
Evaluates script code in page.
In the browser window some HTML APIs like `requestFullScreen` can only be invoked by a gesture from the user. Setting `userGesture` to `true` will remove this limitation.
Code execution will be suspended until web page stop loading.
**Parameters:**
- `code` - The code to execute
- `userGesture` - if set to `true` simulate a user gesture
**Returns:**
The result of the executed code.
#### 🧊 `Task GetPrintersAsync()`
Get system printers.
**Returns:**
Array of available printers.
#### 🧊 `Task GetUrl()`
Get the URL of the loaded page.
It's useful if a web-server redirects you and you need to know where it redirects. For instance, It's useful in case of Implicit Authorization.
**Returns:**
URL of the loaded page.
#### 🧊 `void InsertCSS(bool isBrowserWindow, string path)`
Inserts CSS into the web page.
See: https://www.electronjs.org/docs/api/web-contents#contentsinsertcsscss-options
Works for both BrowserWindows and BrowserViews.
**Parameters:**
- `isBrowserWindow` - Whether the webContents belong to a BrowserWindow or not (the other option is a BrowserView)
- `path` - Absolute path to the CSS file location
#### 🧊 `Task LoadURLAsync(string url)`
Loads the url in the window. The url must contain the protocol prefix.
The async method will resolve when the page has finished loading, and rejects if the page fails to load.
A noop rejection handler is already attached, which avoids unhandled rejection errors.
Loads the `url` in the window. The `url` must contain the protocol prefix, e.g. the `http://` or `file://`. If the load should bypass http cache then use the `pragma` header to achieve it.
**Parameters:**
- `url` - URL to load
#### 🧊 `Task LoadURLAsync(string url, LoadURLOptions options)`
Loads the url with additional options.
The async method will resolve when the page has finished loading, and rejects if the page fails to load.
A noop rejection handler is already attached, which avoids unhandled rejection errors.
Loads the `url` in the window. The `url` must contain the protocol prefix, e.g. the `http://` or `file://`. If the load should bypass http cache then use the `pragma` header to achieve it.
**Parameters:**
- `url` - URL to load
- `options` - Loading options
#### 🧊 `void OpenDevTools()`
Opens the devtools.
#### 🧊 `void OpenDevTools(OpenDevToolsOptions openDevToolsOptions)`
Opens the devtools with options.
**Parameters:**
- `openDevToolsOptions` - Developer tools options
#### 🧊 `Task PrintAsync(PrintOptions options = null)`
Prints window's web page.
**Parameters:**
- `options` - Print options
**Returns:**
Whether the print operation succeeded.
#### 🧊 `Task PrintToPDFAsync(string path, PrintToPDFOptions options = null)`
Prints window's web page as PDF with Chromium's preview printing custom settings.The landscape will be ignored if @page CSS at-rule is used in the web page. By default, an empty options will be regarded as: Use page-break-before: always; CSS style to force to print to a new page.
**Parameters:**
- `path` - Output file path
- `options` - PDF generation options
**Returns:**
Whether the PDF generation succeeded.
## Events
#### ⚡ `InputEvent`
Emitted when an input event is sent to the WebContents.
#### ⚡ `OnCrashed`
Emitted when the renderer process crashes or is killed.
#### ⚡ `OnDidFailLoad`
Emitted when the load failed.
#### ⚡ `OnDidFinishLoad`
Emitted when the navigation is done, i.e. the spinner of the tab has stopped spinning, and the onload event was dispatched.
#### ⚡ `OnDidNavigate`
Emitted when a main frame navigation is done.
#### ⚡ `OnDidRedirectNavigation`
Emitted after a server side redirect occurs during navigation.
#### ⚡ `OnDidStartNavigation`
Emitted when any frame (including main) starts navigating.
#### ⚡ `OnDomReady`
Emitted when the document in the top-level frame is loaded.
#### ⚡ `OnWillRedirect`
Emitted when a server side redirect occurs during navigation.
## Usage Examples
### Page Loading
```csharp
// Load URL with options
await webContents.LoadURLAsync("https://example.com", new LoadURLOptions
{
UserAgent = "MyApp/1.0",
ExtraHeaders = "Authorization: Bearer token123"
});
// Load local file
await webContents.LoadURLAsync("file://" + Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "app/index.html"));
// Get current URL
var currentUrl = await webContents.GetUrl();
Console.WriteLine($"Current URL: {currentUrl}");
```
### JavaScript Execution
```csharp
// Execute simple JavaScript
var result = await webContents.ExecuteJavaScriptAsync("document.title");
Console.WriteLine($"Page title: {result}");
// Execute with user gesture simulation
await webContents.ExecuteJavaScriptAsync("document.requestFullscreen()", true);
// Execute complex code
var userAgent = await webContents.ExecuteJavaScriptAsync("navigator.userAgent");
Console.WriteLine($"User agent: {userAgent}");
```
### Developer Tools
```csharp
// Open dev tools
webContents.OpenDevTools();
// Open with specific options
webContents.OpenDevTools(new OpenDevToolsOptions
{
Mode = DevToolsMode.Detached,
Activate = true
});
```
### CSS Injection
```csharp
// Inject CSS file
webContents.InsertCSS(true, "styles/custom-theme.css");
// Inject CSS for BrowserView
webContents.InsertCSS(false, "styles/browser-view.css");
```
### Printing Operations
```csharp
// Print web page
var printSuccess = await webContents.PrintAsync(new PrintOptions
{
Silent = false,
PrintBackground = true,
DeviceName = "My Printer"
});
if (printSuccess)
{
Console.WriteLine("Print job sent successfully");
}
```
### PDF Generation
```csharp
// Generate PDF
var pdfSuccess = await webContents.PrintToPDFAsync("document.pdf", new PrintToPDFOptions
{
MarginsType = PrintToPDFMarginsType.None,
PageSize = PrintToPDFPageSize.A4,
PrintBackground = true,
Landscape = false
});
if (pdfSuccess)
{
Console.WriteLine("PDF generated successfully");
}
```
### Navigation Monitoring
```csharp
// Monitor navigation events
webContents.OnDidStartNavigation += (url) =>
{
Console.WriteLine($"Starting navigation to: {url}");
};
webContents.OnDidNavigate += (navInfo) =>
{
Console.WriteLine($"Navigated to: {navInfo.Url}");
};
webContents.OnDidFinishLoad += () =>
{
Console.WriteLine("Page finished loading");
};
webContents.OnDidFailLoad += (failInfo) =>
{
Console.WriteLine($"Page failed to load: {failInfo.ErrorCode} - {failInfo.ErrorDescription}");
};
```
### Content Interaction
```csharp
// Wait for DOM ready
webContents.OnDomReady += () =>
{
Console.WriteLine("DOM is ready");
// Safe to execute DOM-related JavaScript now
};
// Handle page crashes
webContents.OnCrashed += (killed) =>
{
Console.WriteLine($"Renderer crashed, killed: {killed}");
// Optionally reload the page
};
```
## Related APIs
- [Electron.WindowManager](WindowManager.md) - Windows containing web contents
- [Electron.Session](Session.md) - Session management for web contents
- [Electron.IpcMain](IpcMain.md) - Communication with web contents
## Additional Resources
- [Electron WebContents Documentation](https://electronjs.org/docs/api/web-contents) - Official Electron web contents API
- [Web Content Management](../Core/What's-New.md) - Understanding web content handling
- [Security Considerations](../Using/Configuration.md) - Secure web content integration
================================================
FILE: docs/API/WindowManager.md
================================================
# Electron.WindowManager
Create and manage browser windows, control window behavior and appearance.
## Overview
The `Electron.WindowManager` API provides comprehensive control over browser windows in your Electron application. It handles window creation, management, and coordination with the application lifecycle.
## Properties
#### 📋 `IReadOnlyCollection BrowserViews`
Gets a read-only collection of all currently open browser views.
#### 📋 `IReadOnlyCollection BrowserWindows`
Gets a read-only collection of all currently open browser windows.
#### 📋 `bool IsQuitOnWindowAllClosed`
Controls whether the application quits when all windows are closed. Default is true.
## Methods
#### 🧊 `Task CreateBrowserViewAsync()`
Creates a new browser view with default options.
**Returns:**
The created BrowserView instance.
#### 🧊 `Task CreateBrowserViewAsync(BrowserViewConstructorOptions options)`
Creates a new browser view with custom options.
**Parameters:**
- `options` - Browser view configuration options
**Returns:**
The created BrowserView instance.
#### 🧊 `Task CreateWindowAsync(string loadUrl = "http://localhost")`
Creates a new browser window with default options.
**Parameters:**
- `loadUrl` - URL to load in the window. Defaults to "http://localhost"
**Returns:**
The created BrowserWindow instance.
#### 🧊 `Task CreateWindowAsync(BrowserWindowOptions options, string loadUrl = "http://localhost")`
Creates a new browser window with custom options.
**Parameters:**
- `options` - Window configuration options
- `loadUrl` - URL to load in the window. Defaults to "http://localhost"
**Returns:**
The created BrowserWindow instance.
## Usage Examples
### Basic Window Creation
```csharp
// Create window with default options
var mainWindow = await Electron.WindowManager.CreateWindowAsync();
// Create window with custom options
var settingsWindow = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions
{
Width = 800,
Height = 600,
Show = false,
Title = "Settings",
WebPreferences = new WebPreferences
{
NodeIntegration = false,
ContextIsolation = true
}
}, "https://localhost:5001/settings");
```
### Window Management
```csharp
// Get all windows
var windows = Electron.WindowManager.BrowserWindows;
Console.WriteLine($"Open windows: {windows.Count}");
// Configure quit behavior
Electron.WindowManager.IsQuitOnWindowAllClosed = false; // Keep app running when windows close
// Handle window lifecycle
Electron.App.WindowAllClosed += () =>
{
Console.WriteLine("All windows closed");
if (Electron.WindowManager.IsQuitOnWindowAllClosed)
{
Electron.App.Quit();
}
};
```
### Browser View Integration
```csharp
// Create browser view
var browserView = await Electron.WindowManager.CreateBrowserViewAsync(new BrowserViewConstructorOptions
{
WebPreferences = new WebPreferences
{
NodeIntegration = false,
ContextIsolation = true
}
});
// Add to window
await mainWindow.SetBrowserViewAsync(browserView);
await browserView.WebContents.LoadURLAsync("https://example.com");
// Set view bounds
await mainWindow.SetBoundsAsync(browserView, new Rectangle
{
X = 0,
Y = 100,
Width = 800,
Height = 400
});
```
### Window Options Configuration
```csharp
// Comprehensive window options
var options = new BrowserWindowOptions
{
Width = 1200,
Height = 800,
MinWidth = 600,
MinHeight = 400,
MaxWidth = 1920,
MaxHeight = 1080,
X = 100,
Y = 100,
Center = true,
Frame = true,
Title = "My Application",
Icon = "assets/app-icon.png",
Show = false,
AlwaysOnTop = false,
SkipTaskbar = false,
Kiosk = false,
TitleBarStyle = TitleBarStyle.Default,
BackgroundColor = "#FFFFFF",
DarkTheme = false,
Transparent = false,
WebPreferences = new WebPreferences
{
NodeIntegration = false,
ContextIsolation = true,
EnableWebSQL = false,
Partition = "persist:electron",
ZoomFactor = 1.0f,
DevTools = true
}
};
```
### Multi-Window Applications
```csharp
// Create main window
var mainWindow = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions
{
Width = 1200,
Height = 800,
Show = false
});
// Create secondary window
var secondaryWindow = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions
{
Width = 600,
Height = 400,
Parent = mainWindow,
Modal = true,
Show = false
});
// Load different content
await mainWindow.WebContents.LoadURLAsync("https://localhost:5001");
await secondaryWindow.WebContents.LoadURLAsync("https://localhost:5001/settings");
// Show windows when ready
mainWindow.OnReadyToShow += () => mainWindow.Show();
secondaryWindow.OnReadyToShow += () => secondaryWindow.Show();
```
## Related APIs
- [Electron.App](App.md) - Application lifecycle and window events
- [Electron.Dialog](Dialog.md) - Parent windows for modal dialogs
- [Electron.Menu](Menu.md) - Window-specific menus
- [Electron.WebContents](WebContents.md) - Window content management
## Additional Resources
- [Electron Window Management Documentation](https://electronjs.org/docs/api/browser-window) - Official Electron window API
================================================
FILE: docs/About.md
================================================
# About this Project
Electron.NET has been developed by a small number of people in the hope that it may be useful for others.
Support for this project in all forms is very welcome, no matter whether in form of code contributions or donations.
## 💬 Community
[](https://gitter.im/ElectronNET/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
Besides the chat on Gitter and the issues [discussed here](https://github.com/ElectronNET/Electron.NET/issues) you can also use [StackOverflow](https://stackoverflow.com/questions/tagged/electron.net) with the tag `electron.net`.
## 🙋♀️🙋♂ Contributing
Feel free to submit a pull request if you find any bugs (to see a list of active issues, visit the [Issues section](https://github.com/ElectronNET/Electron.NET/issues).
Please make sure all commits are properly documented.
## 🙏 Donate
We do this open source work in our free time. If you'd like us to invest more time on it, please [donate](https://donorbox.org/electron-net). Donation can be used to increase some issue priority. Thank you!
[](https://donorbox.org/electron-net)
Alternatively, consider using a GitHub sponsorship for the core maintainers:
- [Gregor Biswanger](https://github.com/sponsors/GregorBiswanger)
- [Florian Rappl](https://github.com/sponsors/FlorianRappl)
Any support appreciated! 🍻
## 👨💻 Authors
* **[Gregor Biswanger](https://github.com/GregorBiswanger)** - (Microsoft MVP, Intel Black Belt and Intel Software Innovator) is a freelance lecturer, consultant, trainer, author and speaker. He is a consultant for large and medium-sized companies, organizations and agencies for software architecture, web- and cross-platform development. You can find Gregor often on the road attending or speaking at international conferences. - [Cross-Platform-Blog](http://www.cross-platform-blog.com) - Twitter [@BFreakout](https://www.twitter.com/BFreakout)
* **[Dr. Florian Rappl](https://github.com/FlorianRappl)** - Software Developer - from Munich, Germany. Microsoft MVP & Web Geek. - [The Art of Micro Frontends](https://microfrontends.art) - [Homepage](https://florian-rappl.de) - Twitter [@florianrappl](https://twitter.com/florianrappl)
* [**softworkz**](https://github.com/softworkz) - full range developer - likes to start where others gave up - MS MVP alumni and Munich citizen as well. Has not been involved in the evolution of Electron.NET but rather dropped off the update and overhaul for ElectronNET.Core in a kind-of drive-by action.
* **[Robert Muehsig](https://github.com/robertmuehsig)** - Software Developer - from Dresden, Germany, now living & working in Switzerland. Microsoft MVP & Web Geek. - [codeinside Blog](https://blog.codeinside.eu) - Twitter [@robert0muehsig](https://twitter.com/robert0muehsig)
See also the list of [contributors](https://github.com/ElectronNET/Electron.NET/graphs/contributors) who participated in this project.
## 🎉 License
MIT-licensed. See [LICENSE](https://github.com/ElectronNET/Electron.NET?tab=MIT-1-ov-file#readme) for details.
================================================
FILE: docs/Core/Advanced-Migration-Topics.md
================================================
# Advanced Migration Topics
This guide covers advanced scenarios and edge cases that may require additional configuration when migrating to ElectronNET.Core.
## Custom ASP.NET Port Configuration
### Port Configuration Changes
**Previous Approach:**
Specifying the WebPort in `electron.manifest.json` is no longer supported because the ASP.NET-first launch mode makes this timing-dependent.
**New Approach:**
Configure custom ASP.NET ports through MSBuild metadata:
```xml
```
## Custom ElectronHostHook Configuration
> [!NOTE]
> These changes are only required in case you are using a custom ElectronHostHook implementation!
> If you have an ElectronHostHook folder in your project but you did not customize that code and aren't using its demo features (Excel and ZIP), you can also just remove that folder from your project.
### TypeScript and Node.js Updates
**Update package.json:**
This shows the relevant changes only: All shown versions are the new required minimum versions.
```json
{
"devDependencies": {
"@types/node": "^22.18",
"typescript": "^5.9.3"
},
"dependencies": {
"socket.io": "^4.8.1",
}
}
```
**Update Project File:**
The below modifications will allow you to use the latest TypeScript compiler in your ASP.Net project.
```xml
commonjs
true
ElectronHostHook/tsconfig.json
```
### Integration Benefits
- **Modern TypeScript** - Latest language features and better type checking
- **Updated Node.js Types** - Compatibility with Node.js 22.x APIs
- **ESLint Integration** - Better code quality and consistency
- **MSBuild Compilation** - Integrated with Visual Studio build process
## Troubleshooting Advanced Scenarios
### Multi-Project Solutions
When using ElectronNET.Core in multi-project solutions:
1. **Install ElectronNET.Core.Api** in class library projects
2. **Install ElectronNET.Core and ElectronNET.Core.AspNet** only in the startup project
3. **Share configuration** through project references or shared files
## Next Steps
- **[Migration Guide](Migration-Guide.md)** - Complete migration process
- **[What's New?](What's-New.md)** - Overview of all ElectronNET.Core features
- **[Getting Started](../GettingStarted/ASP.Net.md)** - Development workflows
================================================
FILE: docs/Core/Migration-Checks.md
================================================
# Migration Checks
Electron.NET includes automatic build-time validation checks that help users migrating from previous versions avoid common configuration issues. These checks run automatically during the build process and provide helpful guidance when problems are detected.
## Overview
When you build an Electron.NET project, the following validation checks are performed:
| Code | Check | Description |
|------|-------|-------------|
| [ELECTRON001](#1-packagejson-rules) | package.json location rules | Ensures `package.json`/`package-lock.json` aren’t present in unsupported locations (root `package.json` handled separately) |
| [ELECTRON008](#1-packagejson-rules) | root package.json contains electron | Warns when root `package.json` contains the word `electron` (case-insensitive) |
| [ELECTRON009](#1-packagejson-rules) | root package.json copied to output | Warns when root `package.json` is configured to be copied to output/publish |
| [ELECTRON002](#2-electron-manifestjson-not-allowed) | electron-manifest.json not allowed | Detects deprecated manifest files |
| [ELECTRON003](#3-electron-builderjson-location) | electron-builder.json location | Verifies electron-builder.json exists in Properties folder |
| [ELECTRON004](#3-electron-builderjson-location) | electron-builder.json wrong location | Warns if electron-builder.json is found in incorrect locations |
| [ELECTRON005](#4-parent-paths-not-allowed-in-electron-builderjson) | Parent paths not allowed | Checks for `..` references in config |
| [ELECTRON006](#5-publish-profile-validation) | ASP.NET publish profile mismatch | Warns when ASP.NET projects have console-style profiles |
| [ELECTRON007](#5-publish-profile-validation) | Console publish profile mismatch | Warns when console projects have ASP.NET-style profiles |
---
## 1. package.json rules
**Warning Codes:** `ELECTRON001`, `ELECTRON008`, `ELECTRON009`
### What is checked
The build system scans for `package.json` and `package-lock.json` files in your project directory.
Rules:
- **ELECTRON001**: `package.json` / `package-lock.json` must not exist in the project directory or subdirectories
- Exception: `ElectronHostHook` folder is allowed
- Note: a **root** `package.json` is **excluded** from `ELECTRON001` and validated by `ELECTRON008` / `ELECTRON009`
- **ELECTRON008**: If a root `package.json` exists, it must **not** contain electron-related dependencies or configuration.
- **ELECTRON009**: If a root `package.json` exists, it must **not** be configured to be copied to output/publish (for example via `CopyToOutputDirectory` / `CopyToPublishDirectory` metadata)
### Why this matters
Electron.NET generates its Electron-related `package.json` during publishing based on MSBuild properties. A user-maintained Electron-related `package.json` can conflict with that process.
Also, ensuring the root `package.json` is not copied prevents accidentally shipping it with the published app.
### Exception
A `package.json` / `package-lock.json` file **is allowed** in the `ElectronHostHook` folder if you're using custom host hooks.
### How to fix
If you have an Electron-related `package.json` from older Electron.NET versions:
1. **Open your project's `.csproj` file**
2. **Add the required properties** to a PropertyGroup with the label `ElectronNetCommon`:
```xml
my-electron-app
My Electron App
1.0.0
My awesome Electron.NET application
My Company
Copyright © 2025
30.0.9
```
3. **Delete** Electron-related `package.json` / `package-lock.json` files (except those under `ElectronHostHook` if applicable)
If you keep a root `package.json` for non-Electron reasons:
- Ensure it does **not** contain electron dependencies or configuration (fixes `ELECTRON008`)
- Ensure it is **not** copied to output/publish (fixes `ELECTRON009`)
> **See also:** [Migration Guide](Migration-Guide.md) for complete migration instructions.
---
## 2. electron-manifest.json not allowed
**Warning Code:** `ELECTRON002`
### What is checked
The build system checks for the presence of `electron.manifest.json` or `electron-manifest.json` files in your project.
### Why this matters
The `electron.manifest.json` file format is deprecated. All configuration should now be specified using:
- MSBuild properties in your `.csproj` file (for application metadata)
- The `electron-builder.json` file in the `Properties` folder (for build configuration)
### How to fix
1. **Migrate application properties** to your `.csproj` file (see [Migration Guide](Migration-Guide.md))
2. **Move the `build` section** from `electron.manifest.json` to `Properties/electron-builder.json`
3. **Delete the old `electron.manifest.json`** file
**Example electron-builder.json:**
```json
{
"compression": "maximum",
"win": {
"icon": "Assets/app.ico",
"target": ["nsis", "portable"]
},
"linux": {
"icon": "Assets/app.png",
"target": ["AppImage", "deb"]
},
"mac": {
"icon": "Assets/app.icns",
"target": ["dmg", "zip"]
}
}
```
---
## 3. electron-builder.json Location
**Warning Codes:** `ELECTRON003`, `ELECTRON004`
### What is checked
- `ELECTRON003`: Verifies that an `electron-builder.json` file exists in the `Properties` folder
- `ELECTRON004`: Warns if `electron-builder.json` is found in incorrect locations
### Why this matters
The `electron-builder.json` file must be located in the `Properties` folder so it can be properly copied to the output directory during publishing.
### How to fix
1. **Create the Properties folder** if it doesn't exist
2. **Move or create** `electron-builder.json` in `Properties/electron-builder.json`
3. **Remove** any `electron-builder.json` files from other locations
**Expected structure:**
```
MyProject/
├── Properties/
│ ├── electron-builder.json ✅ Correct location
│ ├── launchSettings.json
│ └── PublishProfiles/
├── MyProject.csproj
└── Program.cs
```
---
## 4. Parent paths not allowed in electron-builder.json
**Warning Code:** `ELECTRON005`
### What is checked
The build system scans the `electron-builder.json` file for parent-path references (`..`).
### Why this matters
During the publish process, the `electron-builder.json` file is copied to the build output directory. Any relative paths in this file are resolved from that location, not from your project directory. Parent-path references (`../`) will not work correctly because they would point outside the published application.
### How to fix
1. **Move resource files** (icons, installers, etc.) inside your project folder structure
2. **Configure the files** to be copied to the output directory in your `.csproj`:
```xml
PreserveNewest
```
3. **Update paths** in `electron-builder.json` to use relative paths without `..`:
**Before (incorrect):**
```json
{
"win": {
"icon": "../SharedAssets/app.ico"
}
}
```
**After (correct):**
```json
{
"win": {
"icon": "Assets/app.ico"
}
}
```
---
## 5. Publish Profile Validation
**Warning Codes:** `ELECTRON006`, `ELECTRON007`
### What is checked
The build system examines `.pubxml` files in the `Properties/PublishProfiles` folder and validates that they match the project type:
- **ELECTRON006**: For **ASP.NET projects** (using `Microsoft.NET.Sdk.Web`), checks that publish profiles include `WebPublishMethod`. This property is required for proper ASP.NET publishing.
- **ELECTRON007**: For **console/other projects** (not using the Web SDK), checks that publish profiles do NOT include the `WebPublishMethod` property. These ASP.NET-specific properties are incorrect for non-web applications.
### Why this matters
Electron.NET supports both ASP.NET and console application project types, each requiring different publish profile configurations:
| Project Type | SDK | Expected Properties |
|--------------|-----|---------------------|
| ASP.NET (Razor Pages, MVC, Blazor) | `Microsoft.NET.Sdk.Web` | `WebPublishMethod`, no `PublishProtocol` |
| Console Application | `Microsoft.NET.Sdk` | `PublishProtocol`, no `WebPublishMethod` |
Using the wrong publish profile type can lead to incomplete or broken builds.
### How to fix
1. **Delete existing publish profiles** from `Properties/PublishProfiles/`
2. **Create new publish profiles** using the Visual Studio Publishing Wizard:
- Right-click on the project in Solution Explorer
- Select **Publish...**
- Follow the wizard to create a **Folder** publish profile
For correct publish profile examples for both ASP.NET and Console applications, see **[Package Building](../Using/Package-Building.md#step-1-create-publish-profiles)**.
---
## Disabling Migration Checks
If you need to disable specific migration checks (not recommended), you can set the following properties in your `.csproj` file:
```xml
true
```
> ⚠️ **Warning:** Disabling migration checks may result in build or runtime errors. Only disable checks if you fully understand the implications.
---
## See Also
- [Migration Guide](Migration-Guide.md) - Complete step-by-step migration instructions
- [Advanced Migration Topics](Advanced-Migration-Topics.md) - Complex migration scenarios
- [Configuration](../Using/Configuration.md) - Project configuration options
- [Package Building](../Using/Package-Building.md) - Building distributable packages
================================================
FILE: docs/Core/Migration-Guide.md
================================================
# Migration Guide
Migrating from previous versions of Electron.NET to ElectronNET.Core is straightforward but requires several important changes. This guide walks you through the process step by step.
## 📋 Prerequisites
Before starting the migration:
- **Backup your project** - Ensure you have a working backup
- **Update development tools** - Install Node.js 22.x and .NET 8.0+
- **Review current setup** - Note your current Electron and ASP.NET versions
## 🚀 Migration Steps
### Step 1: Update NuGet Packages
**Uninstall old packages:**
```powershell
dotnet remove package ElectronNET.API
```
**Install new packages:**
```powershell
dotnet add package ElectronNET.Core
dotnet add package ElectronNET.Core.AspNet # For ASP.NET projects
```
> **Note**: The API package is automatically included as a dependency of `ElectronNET.Core`. See [Package Description](../RelInfo/Package-Description.md) for details about the package structure.
### Step 2: Configure Project Settings
**Auto-generated Configuration:**
ElectronNET.Core automatically creates `electron-builder.json` in the `Properties` folder of your project during the first build or NuGet restore. No manual configuration is needed for basic setups.
**Migrate Existing Configuration:**
If you have an existing `electron.manifest.json` file:
1. **Open the generated `electron-builder.json`** file in your project
2. **Locate the 'build' section** in your old `electron.manifest.json`
3. **Copy the contents** of the build section (not the "build" key itself) into the new `electron-builder.json`
4. **Use Visual Studio Project Designer** to configure Electron settings through the UI
5. **Delete the old `electron.manifest.json`** file
**Alternative: Manual Configuration**
You can also manually edit `electron-builder.json`:
```json
{
"linux": {
"target": [
"tar.xz"
]
},
"win": {
"target": [
{
"target": "portable",
"arch": "x64"
}
]
}
}
```
**Modify Launch Settings:**
ElectronNET.Core no longer needs a separate CLI tool (electronize.exe) for launching. You should update your launch settings to use either the ASP.NET-first or Electron-first approach. See [Debugging](../Using/Debugging.md) for details.
## 🎯 Testing Migration
After completing the migration steps:
1. **Build your project** to ensure no compilation errors
2. **Test debugging** using the new ASP.NET-first approach
3. **Verify packaging** works with the new configuration
4. **Check cross-platform builds** if targeting multiple platforms
## 🚨 Common Migration Issues
### Build Errors
- **Node.js version**: Verify Node.js 22.x is installed and in PATH
- **Package conflicts**: Clean NuGet cache if needed
### Runtime Errors
- **Missing electron-builder.json**: Trigger rebuild or manual NuGet restore
- **Process termination**: Use .NET-first startup mode for better cleanup
## 🚀 Next Steps
- **[What's New?](What's-New.md)** - Complete overview of ElectronNET.Core features
- **[Advanced Migration Topics](Advanced-Migration-Topics.md)** - Handle complex scenarios
- **[Getting Started](../GettingStarted/ASP.Net.md)** - Learn about new development workflows
## 💡 Migration Benefits
✅ **Simplified Configuration** - No more CLI tools or JSON files
✅ **Better Debugging** - Native Visual Studio experience with Hot Reload
✅ **Modern Architecture** - .NET-first process lifecycle
✅ **Cross-Platform Ready** - Build Linux apps from Windows
✅ **Future-Proof** - Flexible Electron version selection
### Step 3: Update Startup Code
**Update UseElectron() calls** to include the new callback parameter. This callback executes at the right moment to initialize your Electron UI.
#### Modern ASP.NET Core (WebApplication)
```csharp
using ElectronNET.API;
using ElectronNET.API.Entities;
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.UseElectron(args, ElectronAppReady);
var app = builder.Build();
app.Run();
}
public static async Task ElectronAppReady()
{
var browserWindow = await Electron.WindowManager.CreateWindowAsync(
new BrowserWindowOptions { Show = false });
browserWindow.OnReadyToShow += () => browserWindow.Show();
}
```
#### Traditional ASP.NET Core (IWebHostBuilder)
```csharp
using ElectronNET.API;
using ElectronNET.API.Entities;
public static void Main(string[] args)
{
WebHost.CreateDefaultBuilder(args)
.UseElectron(args, ElectronAppReady)
.UseStartup()
.Build()
.Run();
}
public static async Task ElectronAppReady()
{
var browserWindow = await Electron.WindowManager.CreateWindowAsync(
new BrowserWindowOptions { Show = false });
browserWindow.OnReadyToShow += () => browserWindow.Show();
}
```
### Step 4: Update Development Tools
See [System Requirements](../GettingStarted/System-Requirements.md).
### Step 5: Update Debugging Setup
**Watch Feature Removal:**
The old 'watch' feature is no longer supported. Instead, use the new ASP.NET-first debugging with Hot Reload:
- **Old approach**: Manual process attachment and slow refresh
- **New approach**: Native Visual Studio debugging with Hot Reload
- **Benefits**: Faster development cycle, better debugging experience
**Update Launch Settings:**
Replace old watch configurations with new debugging profiles. See [Debugging](../GettingStarted/Debugging.md) for detailed setup instructions.
================================================
FILE: docs/Core/What's-New.md
================================================
# What's New in ElectronNET.Core
## A Complete Transformation
ElectronNET.Core represents a fundamental modernization of Electron.NET, addressing years of accumulated pain points while preserving full API compatibility. This isn't just an update—it's a complete rethinking of how .NET developers build and debug cross-platform desktop applications with Electron.
## Complete Build System Overhaul
### From CLI Complexity to MSBuild Simplicity
The most visible change is the complete elimination of the CLI tool dependency. Where developers once needed to manage complex command-line operations and JSON configuration files, everything now flows through Visual Studio's native project system.
The old `electron.manifest.json` file is gone, replaced by clean MSBuild project properties that integrate seamlessly with Visual Studio's project designer. This provides not just a better development experience, but also eliminates entire categories of configuration errors that plagued earlier versions.
### Intelligent Package Structure
The new package architecture reflects a clearer separation of concerns:
- **ElectronNET.Core** - The main package containing build logic and project system integration
- **ElectronNET.Core.Api** - Pure API definitions for Electron integration
- **ElectronNET.Core.AspNet** - ASP.NET-specific runtime components
This modular approach allows projects to include only what they need while maintaining the flexibility to scale from simple console applications to complex web applications.
More about the available nuget packages: [Package Description](../RelInfo/Package-Description.md).
## Beyond ASP.NET: Console Application Support
### A Shift in Accessibility
A major new opportunity in ElectronNET.Core is the removal of the ASP.NET requirement. Developers can now build Electron solutions using simple DotNet console applications, expanding the use cases and removing a major barrier to adoption for a number of use cases.
### Flexible Content Sources
Console applications with ElectronNET.Core support multiple content scenarios:
- **File System HTML/JS**: Serve static web content directly from the file system
- **Remote Server Integration**: Connect to existing web servers or APIs
- **Lightweight Architecture**: Avoid the overhead of ASP.NET when it's not needed
- **Simplified Deployment**: Package and distribute with minimal dependencies
This capability transforms ElectronNET from a web-focused framework into a versatile platform that can integrate with any HTML/JS content source, making it accessible to a much broader range of development scenarios and team structures.
## Revolutionary Development Experience
### Debugging Reimagined
The debugging experience has been completely transformed. The new DotNet-first launch mode means developers can now debug their .NET code directly, with full access to familiar debugging tools and Hot Reload capabilities. No more attaching to processes or working around limited debugging scenarios — the development workflow now matches standard .NET development patterns.
### Cross-Platform Development Without Compromises
One of the most significant breakthroughs is the ability to build and debug Linux applications directly from Windows Visual Studio through WSL integration. Developers can now:
- Build Linux packages while working on Windows
- Debug Linux application behavior in real-time
- Test cross-platform functionality without context switching
- Deploy to Linux targets with confidence
This capability eliminates the traditional barriers between Windows development environments and Linux deployment targets.
### Flexible Runtime Identifier Support
Runtime Identifier (RID) selection is now a first-class part of the project configuration, allowing developers to explicitly target specific platforms and architectures. The build system automatically structures output folders using standard .NET conventions (`bin\net8.0\win-x64`) instead of the ambiguous `bin\Desktop` layout, making multi-target builds clean and predictable.
## Modernized Architecture
### Process Lifecycle Revolution
The underlying process architecture has been fundamentally redesigned. Instead of Electron launching first and managing the .NET process, ElectronNET.Core puts .NET in control. The .NET application launches first and runs Electron as a child process, providing:
- Better process lifecycle management
- More reliable application termination
- Enhanced error handling and recovery
- Cleaner separation between web and native concerns
This architecture supports eight different launch scenarios, covering every combination of packaged/unpackaged deployment, console/ASP.NET hosting, and dotnet-first/electron-first initialization. The Electron-first launch method is still available or course.
For more details, see: [Startup Methods](../GettingStarted/Startup-Methods.md).
### Unpackaged Development Mode
The new unpackaged run-mode transforms development workflows by using regular .NET builds with unpackaged Electron configurations. This approach leverages .NET's incremental build capabilities for both managed and native code, dramatically reducing rebuild times and improving the development feedback loop.
## Enhanced Technical Foundation
### TypeScript Integration
TypeScript compilation is now fully integrated with ASP.NET tooling, providing consistent builds across different development environments. The updated toolchain uses modern TypeScript versions with ESLint configuration, eliminating the compatibility issues that previously affected custom ElectronHostHook implementations.
### API Enhancements
The improved splash screen handling with automatic path resolution eliminates common configuration pitfalls, while maintaining full backward compatibility with existing ElectronHostHook code.
### Performance Optimizations
Package sizes have been reduced by eliminating unnecessary dependencies, while build performance has improved through intelligent incremental compilation. The new architecture also minimizes startup times through optimized build and launch procedures.
## Seamless Migration Path
### Backward Compatibility Focus
Despite the extensive changes, ElectronNET.Core maintains complete API compatibility with existing applications. The modular package structure allows for incremental adoption, and existing ElectronHostHook implementations continue to work without modification.
### Clear Upgrade Journey
The migration path is designed to be straightforward:
1. Update package references to the new structure
2. Remove the old manifest file
3. Configure project properties through Visual Studio
4. Adopt new debugging workflows at your own pace
Further reading: [Migration Guide](Migration-Guide.md).
## Future Horizons
### Unlocked Possibilities
This modernization removes the technical debt that was limiting Electron.NET's evolution. The flexible Electron versioning, integrated build system, and cross-platform capabilities create a foundation for:
- More frequent updates and feature additions
- Enhanced community contributions
- Better tooling and IDE integration
- Expanded platform support
### Version Independence
The removal of rigid Electron version coupling means developers can now choose the Electron version that best fits their needs, with build-time validation ensuring compatibility. This approach encourages community feedback and enables faster adoption of new Electron features.
## Conclusion
ElectronNET.Core represents more than just new features—it's a complete reimagining of what .NET + Electron development can be. By eliminating friction points, removing the ASP.NET requirement to support console applications, improving debugging experiences, and enabling true cross-platform development, it transforms Electron.NET from a challenging framework to work with into a modern, efficient platform for building cross-platform desktop applications.
The changes address the core issues that were driving developers away from Electron.NET while opening new possibilities for the future. This foundation will enable more rapid innovation and better support for the growing demands of cross-platform .NET development.
================================================
FILE: docs/Docs.shproj
================================================
06caadc7-de5b-47b4-ab2a-e9501459a2d1
================================================
FILE: docs/GettingStarted/ASP.Net.md
================================================
# ASP.NET Core Setup
ASP.NET Core remains the recommended approach for complex web applications with ElectronNET.Core, providing all the benefits of the ASP.NET ecosystem along with enhanced Electron integration.
## 🛠 System Requirements
See [System Requirements](../GettingStarted/System-Requirements.md).
## 🚀 Quick Start
### 1. Create ASP.NET Core Project
#### Visual Studio
Create a new ASP.NET Core Web App in Visual Studio by selecting **New Project** and choosing one of the ASP.NET Core project templates.
#### From the command line
```bash
dotnet new webapp -n MyElectronWebApp
cd MyElectronWebApp
```
### 2. Install NuGet Packages
#### Visual Studio
Right-click the solution and select **Manage Nuget Packages**.
Finr and install `ElectronNET.Core` as well as `ElectronNET.Core.AspNet`.
#### From the command line
```powershell
dotnet add package ElectronNET.Core
dotnet add package ElectronNET.Core.AspNet
```
> [!Note]
> The API package is automatically included as a dependency of `ElectronNET.Core`.
### 3. Configure Program.cs
Update your `Program.cs` to enable Electron.NET:
```csharp
using ElectronNET.API;
using ElectronNET.API.Entities;
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
// Add this line to enable Electron.NET:
builder.UseElectron(args, ElectronAppReady);
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
}
public static async Task ElectronAppReady()
{
var browserWindow = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions { Show = false });
browserWindow.OnReadyToShow += () => browserWindow.Show();
}
}
```
#### ASP.NET Port
If you want to launch a specific URL, you can retrieve the actual ASP.NET port from the new `ElectronNetRuntime` static class, for example:
```csharp
await browserWindow.WebContents
.LoadURLAsync($"http://localhost:{ElectronNetRuntime.AspNetWebPort}/mypage.html");
```
### 4. Alternative: IWebHostBuilder Setup
For projects using the traditional `Startup.cs` pattern, please see "Traditional ASP.NET Core" in the [Migration Guide](../Core/Migration-Guide.md).
### 5. Dependency Injection
ElectronNET.API can be added to your DI container within the `Startup` class. All of the modules available in Electron will be added as Singletons.
```csharp
using ElectronNET.API;
public void ConfigureServices(IServiceCollection services)
{
services.AddElectron();
}
```
## 🚀 Next Steps
- **[Debugging](../Using/Debugging.md)** - Learn about ASP.NET debugging features
- **[Package Building](../Using/Package-Building.md)** - Create distributable packages
- **[Startup Methods](../Using/Startup-Methods.md)** - Understanding launch scenarios
## 💡 Benefits of ASP.NET + Electron
✅ **Full Web Stack** - Use MVC, Razor Pages, Blazor, and all ASP.NET features
✅ **Hot Reload** - Edit ASP.NET code and see changes instantly
✅ **Rich Ecosystem** - Access to thousands of ASP.NET packages
✅ **Modern Development** - Latest C# features and ASP.NET patterns
✅ **Scalable Architecture** - Build complex, maintainable applications
================================================
FILE: docs/GettingStarted/Console-App.md
================================================
# Console Application Setup
A major benefit in ElectronNET.Core is the ability to build Electron applications using simple console applications instead of requiring ASP.NET Core. This removes a significant barrier and enables many more use cases.
## 🎯 What You Can Build
Console applications with ElectronNET.Core support multiple content scenarios:
- **File System HTML/JS** - Serve static web content directly from the file system
- **Remote Server Integration** - Connect to existing web servers or APIs
- **Lightweight Architecture** - Avoid ASP.NET overhead when not needed
- **Simplified Deployment** - Package and distribute with minimal dependencies
## 📋 Prerequisites
See [System Requirements](../GettingStarted/System-Requirements.md).
## 🚀 Quick Start
### 1. Create Console Application
#### Visual Studio
Create a new console application in Visual Studio by selecting **New Project** and choosing one of the project templates for console apps.
#### From the command line
```bash
dotnet new console -n MyElectronApp
cd MyElectronApp
```
### 2. Install NuGet Packages
```powershell
dotnet add package ElectronNET.Core
```
> [!Note]
> The API package is automatically included as a dependency of `ElectronNET.Core`.
### 3. Configure Project File
Add the Electron.NET configuration to your `.csproj` file:
```xml
Exe
net10.0
win-x64
```
> [!WARNING]
> Specifying `OutputType` property is crucial in order to get the ability of WSL debugging. Especially it is not included in ASP.NET projects.
> When you migrate from ASP.NET to a console application, be sure to add this to the project file.
### 4. Implement Basic Structure
Here's a complete console application example:
```csharp
using System;
using System.Threading.Tasks;
using ElectronNET.API.Entities;
namespace MyElectronApp
public class Program
{
public static async Task Main(string[] args)
{
var runtimeController = ElectronNetRuntime.RuntimeController;
try
{
// Start Electron runtime
await runtimeController.Start();
await runtimeController.WaitReadyTask;
// Initialize your Electron app
await InitializeApp();
// Wait for shutdown
await runtimeController.WaitStoppedTask.ConfigureAwait(false);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
await runtimeController.Stop().ConfigureAwait(false);
await runtimeController.WaitStoppedTask.WaitAsync(TimeSpan.FromSeconds(2)).ConfigureAwait(false);
}
}
private static async Task InitializeApp()
{
// Create main window
var browserWindow = await Electron.WindowManager.CreateWindowAsync(
new BrowserWindowOptions
{
Show = false,
WebPreferences = new WebPreferences
{
// Add these two when using file:// URLs
WebSecurity = false,
AllowRunningInsecureContent = true,
NodeIntegration = false,
ContextIsolation = true
}
});
// Load your content (file system, remote URL, etc.)
await browserWindow.WebContents.LoadURLAsync("https://example.com");
// Show window when ready
browserWindow.OnReadyToShow += () => browserWindow.Show();
}
}
```
## 📁 Content Sources
### File System Content
Serve HTML/JS files from your project:
```csharp
// In your project root, create wwwroot/index.html
var fileInfo = new FileInfo(Environment.ProcessPath);
var exeFolder = fileInfo.DirectoryName;
var htmlPath = Path.Combine(exeFolder, "wwwroot/index.html");
var url = new Uri(htmlPath, UriKind.Absolute);
await browserWindow.WebContents.LoadFileAsync(url.ToString());
```
### Remote Content
Load content from any web server:
```csharp
await browserWindow.WebContents.LoadURLAsync("https://your-server.com/app");
```
## 🚀 Next Steps
- **[Debugging](../Using/Debugging.md)** - Learn about debugging console applications
- **[Package Building](../Using/Package-Building.md)** - Create distributable packages
- **[Migration Guide](../Core/Migration-Guide.md)** - Moving from ASP.NET projects
## 💡 Benefits of Console Apps
✅ **Simpler Architecture** - No ASP.NET complexity when not needed
✅ **Flexible Content** - Use any HTML/JS source
✅ **Faster Development** - Less overhead for simple applications
✅ **Easy Deployment** - Minimal dependencies
✅ **Better Performance** - Lighter weight than full web applications
================================================
FILE: docs/GettingStarted/System-Requirements.md
================================================
## 🛠 System Requirements
### Required Software
- **.NET 8.0** or later
- **Node.js 22.x** or later (see below)
- **Visual Studio 2022** (recommended) or other .NET IDE
### Supported Operating Systems
- **Windows 10/11** (x64, ARM64)
- **macOS 11+** (Intel, Apple Silicon)
- **Linux** (most distributions with glibc 2.31+)
> [!Note]
> For Linux development on Windows, install [WSL2](https://docs.microsoft.com/windows/wsl/install) to build and debug Linux packages.
> Do not forget to install NodeJS 22.x (LTS) on WSL.
> Visual Studio will automatically install .NET when debugging on WSL. In all other cases you will need to install a matching .NET SDK on WSL as well.
### NodeJS Installation
ElectronNET.Core requires Node.js 22.x. Update your installation:
**Windows:**
1. Download from [nodejs.org](https://nodejs.org)
2. Run the installer
3. Verify: `node --version` should show v22.x.x
**Linux:**
```bash
# Using Node Version Manager (recommended)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
source ~/.bashrc
nvm install 22
nvm use 22
# Or using package manager
sudo apt update
sudo apt install nodejs=22.*
```
## 🚀 Next Steps
- **[Debugging](../Using/Debugging.md)** - Learn about ASP.NET debugging features
- **[Package Building](../Using/Package-Building.md)** - Create distributable packages
- **[Startup Methods](../Using/Startup-Methods.md)** - Understanding launch scenarios
================================================
FILE: docs/Home.md
================================================
# Electron.NET Wiki Home
Welcome to the **Electron.NET Core** documentation! This wiki covers everything you need to know about building cross-platform desktop applications with ASP.NET Core and Electron.NET.
## 🚀 What is Electron.NET Core?
Electron.NET Core is a complete modernization of Electron.NET that eliminates the CLI tool dependency and integrates deeply with Visual Studio's MSBuild system. It transforms the development experience by providing:
- **Native Visual Studio Integration** - No more CLI tools or JSON configuration files
- **Console Application Support** - Build Electron apps with simple console applications, not just ASP.NET
- **Cross-Platform Development** - Build and debug Linux applications from Windows via WSL
- **Enhanced Debugging** - ASP.NET-first debugging with Hot Reload support
- **Flexible Architecture** - Choose any Electron version and target multiple platforms
## 📚 Documentation Sections
### [Core Documentation](Core/What's-New.md)
- **[What's New?](Core/What's-New.md)** - Complete overview of ElectronNET.Core features and improvements
- **[Migration Guide](Core/Migration-Guide.md)** - Step-by-step migration from previous versions
- **[Advanced Migration Topics](Core/Advanced-Migration-Topics.md)** - Technical details for complex scenarios
### [Getting Started](GettingStarted/ASP.Net.md)
- **[ASP.NET Applications](GettingStarted/ASP.Net.md)** - Build Electron apps with ASP.NET Core
- **[Console Applications](GettingStarted/Console-App.md)** - Use console apps for file system or remote content
- **[Startup Methods](GettingStarted/Startup-Methods.md)** - Understanding different launch scenarios
- **[Debugging](GettingStarted/Debugging.md)** - Debug Electron apps effectively in Visual Studio
- **[Package Building](GettingStarted/Package-Building.md)** - Create distributable packages
### [Release Information](RelInfo/Package-Description.md)
- **[Package Description](RelInfo/Package-Description.md)** - Understanding the three NuGet packages
- **[Changelog](../Changelog.md)** - Complete list of changes and improvements
## 🛠 System Requirements
See [System Requirements](GettingStarted/System-Requirements.md).
## 💡 Key Benefits
✅ **No CLI Tools Required** - Everything works through Visual Studio
✅ **Console App Support** - Use any HTML/JS source, not just ASP.NET
✅ **True Cross-Platform** - Build Linux apps from Windows
✅ **Modern Debugging** - Hot Reload and ASP.NET-first debugging
✅ **Flexible Packaging** - Choose any Electron version
✅ **MSBuild Integration** - Leverages .NET's build system
## 🚦 Getting Started
New to ElectronNET.Core? Start here:
1. **[ASP.NET Setup](GettingStarted/ASP.Net.md)** - Traditional web application approach
2. **[Console App Setup](GettingStarted/Console-App.md)** - Lightweight console application approach
3. **[Migration Guide](Core/Migration-Guide.md)** - Moving from previous versions
## 🤝 Contributing
Found an issue or want to improve the documentation? Contributions are welcome!
The wiki is auto-generated from the `/docs` folder in the [GitHub repository](https://github.com/ElectronNET/Electron.NET).
================================================
FILE: docs/RelInfo/Package-Description.md
================================================
# Package Description
ElectronNET.Core consists of three specialized NuGet packages designed for different development scenarios and project architectures.
## 📦 Package Overview
### ElectronNET.Core (Main Package)
**Primary package for Electron.NET applications**
- **Build system integration** - MSBuild targets and tasks for Electron
- **Project system extensions** - Visual Studio designer integration
- **Runtime orchestration** - Process lifecycle management
- **Automatic configuration** - Generates electron-builder.json and package.json
- **Includes ElectronNET.Core.Api** as a dependency
**When to use:**
- Main application projects (startup projects)
- Projects that need full Electron.NET functionality
- Applications requiring build-time Electron configuration
### ElectronNET.Core.Api (API Package)
**Pure API definitions without build integration**
- **Electron API wrappers** - Complete Electron API surface
- **Type definitions** - Full TypeScript-style IntelliSense
- **No build dependencies** - Clean API-only package
- **Multi-platform support** - Windows, macOS, Linux
**When to use:**
- Class library projects that interact with Electron APIs
- Projects that only need API access without build integration
- Multi-project solutions where only the startup project needs build integration
### ElectronNET.Core.AspNet (ASP.NET Integration)
**ASP.NET-specific runtime components**
- **ASP.NET middleware** - UseElectron() extension methods
- **WebHost integration** - Seamless ASP.NET + Electron hosting
- **Hot Reload support** - Enhanced debugging experience
- **Web-specific optimizations** - ASP.NET tailored performance
**When to use:**
- ASP.NET Core projects building Electron applications
- Applications requiring web server functionality
- Projects using MVC, Razor Pages, or Blazor
## 🏗 Architecture Benefits
### Separation of Concerns
- **Build logic separate from API** - Clean dependency management
- **Optional ASP.NET integration** - Use only what you need
- **Multi-project friendly** - Share APIs across projects without build conflicts
### Project Scenarios
**Single Project (ASP.NET):**
```xml
```
**Single Project (Console):**
```xml
```
**Multi-Project Solution (ASP.NET):**
```xml
```
**Multi-Project Solution (Console):**
```xml
```
## 🔗 Dependency Chain
```
ElectronNET.Core.AspNet → ElectronNET.Core.Api
ElectronNET.Core → ElectronNET.Core.Api
```
- **ElectronNET.Core.AspNet** depends on ElectronNET.Core.Api
- **ElectronNET.Core** depends on ElectronNET.Core.Api
- **ElectronNET.Core.Api** has no dependencies
## 💡 Recommendations
✅ **Start with ElectronNET.Core** for new projects
✅ **Add ElectronNET.Core.AspNet** only for ASP.NET applications
✅ **Use ElectronNET.Core.Api** for class libraries and API-only scenarios
✅ **Multi-project solutions** benefit from the modular architecture
================================================
FILE: docs/Using/Configuration.md
================================================
# Project Configuration
## 🔧 Visual Studio App Designer
Electron.NET provides close integration via the Visual Studio Project System and MSBuild. After adding the ElectronNET.Core package, you will see this in the project configuration page after double-click on the 'Properties' folder or right-click on the project and choosing 'Properties':


## Project File Settings
The same settings can be configured manually by editing the MSBuild properties in your `.csproj` file.
These are the current default values when you don't make any changes:
```xml
30.4.0
26.0
win-x64
true
$(MSBuildProjectName.Replace(".", "-").ToLower())
electron-builder.json
$(MSBuildProjectName)
```
### Relation to package.json
ElectronNET.Core does not work with an `electron-manifest.json` file anymore.
Since electron builder still expects a `package.json` file to exist, ElectronNET.Core is creating this under the hood automatically during build. For reference, here's the package.json template file that is being used, so you can see how the MSBuild properties are being mapped to `package.json` data:
```json
{
"name": "$(ElectronPackageId)",
"productName": "$(ElectronTitle)",
"build": {
"appId": "$(ElectronPackageId)",
"linux": {
"desktop": {
"entry": { "Name": "$(Title)" }
},
"executableName": "$(ElectronPackageId)"
},
"deb": {
"desktop": {
"entry": { "Name": "$(Title)" }
}
}
},
"description": "$(Description)",
"version": "$(Version)",
"main": "main.js",
"author": {
"name": "$(Company)"
},
"license": "$(License)",
"executable": "$(TargetName)",
"singleInstance": "$(ElectronSingleInstance)",
"homepage": "$(ProjectUrl)",
"splashscreen": {
"imageFile": "$(ElectronSplashScreen)"
}
}
```
### Node.js Integration
Electron.NET requires Node.js integration to be enabled for IPC to function. If you are not using the IPC functionality you can disable Node.js integration like so:
```csharp
WebPreferences wp = new WebPreferences();
wp.NodeIntegration = false;
BrowserWindowOptions browserWindowOptions = new BrowserWindowOptions
{
WebPreferences = wp
};
```
### Preload Script
If you require the use of a [preload script](https://www.electronjs.org/docs/latest/tutorial/tutorial-preload), you can specify the file path of the script when creating a new window like so:
```csharp
WebPreferences wp = new WebPreferences();
wp.Preload = "path/to/preload.js";
BrowserWindowOptions browserWindowOptions = new BrowserWindowOptions
{
WebPreferences = wp
};
```
> [!IMPORTANT]
> When using a preload script _AND_ running a Blazor app, `IsRunningBlazor` must be set to `false` (or removed) and the following lines must be added to the preload script:
> ```js
> global.process = undefined;
> global.module = undefined;
> ```
## 🚀 Next Steps
- **[Startup Methods](Startup-Methods.md)** - Understanding launch scenarios
- **[Debugging](../Using/Debugging.md)** - Learn about ASP.NET debugging features
- **[Package Building](Package-Building.md)** - Create distributable packages
================================================
FILE: docs/Using/Custom_main.md
================================================
# Using custom_main.js
This guide explains how to include and use a `custom_main.js` file in your Electron.NET application for advanced Electron/Node.js customization.
## Why use custom_main.js?
- Register custom protocol handlers (e.g., `myapp://`) — protocols must be registered before the app is fully initialized
- Integrate Node.js modules (e.g., telemetry, OS APIs)
- Control startup logic (abort, environment checks)
- Set up IPC messaging or preload scripts
## Step-by-Step Process
### 1. Create the custom_main.js file
Place your custom logic in `electron/custom_main.js`:
```javascript
module.exports.onStartup = function(host) {
// Example: Register a global shortcut for opening dev tools
const { app, globalShortcut, BrowserWindow } = require('electron');
app.on('ready', () => {
const ret = globalShortcut.register('Control+Shift+I', () => {
BrowserWindow.getAllWindows().forEach(win => win.webContents.openDevTools());
console.log('Ctrl+Shift+I is pressed: DevTools opened!');
});
});
app.on('will-quit', () => {
globalShortcut.unregisterAll();
});
return true;
};
```
### 2. Configure your .csproj to copy custom_main.js to output
Add this to your `.csproj` file:
```xml
PreserveNewest
.electron\custom_main.js
```
### 3. Build and run your app
Use the standard build/run commands:
```powershell
dotnet build
dotnet run
```
Electron.NET will automatically load and execute your `custom_main.js` before initializing the .NET backend.
## Advanced Usage
Use environment variables to control features:
```javascript
const env = process.env.ASPNETCORE_ENVIRONMENT || 'Production';
if (env === 'Development') { /* enable dev features */ }
```
## Notes
- `custom_main.js` must use CommonJS syntax (`module.exports.onStartup = ...`).
- Place the file in your source directory and copy it to `.electron` using `.csproj`.
- Electron.NET will abort startup if `onStartup` returns `false`.
### Complete example is available here [ElectronNetSampleApp](https://github.com/niteshsinghal85/ElectronNetSampleApp)
================================================
FILE: docs/Using/Debugging.md
================================================
# Debugging
ElectronNET.Core transforms the debugging experience by providing native Visual Studio integration with multiple debugging modes. No more complex setup or manual process attachment—debugging now works as expected for .NET developers.
## 🎯 Debugging Modes
ElectronNET.Core supports three main debugging approaches, all configured through Visual Studio's launch profiles:
### 1. ASP.NET-First Debugging (Recommended)
Debug your .NET code directly with full Hot Reload support:
```json
{
"profiles": {
"ASP.Net (unpackaged)": {
"commandName": "Project",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:8001/"
}
}
}
```
**Benefits:**
- ✅ Full C# debugging with breakpoints
- ✅ Hot Reload for ASP.NET code
- ✅ Edit-and-continue functionality
- ✅ Native Visual Studio debugging experience
### 2. Electron-First Debugging
Debug the Electron process when you need to inspect native Electron APIs:
```json
{
"profiles": {
"Electron (unpackaged)": {
"commandName": "Executable",
"executablePath": "node",
"commandLineArgs": "node_modules/electron/cli.js main.js -unpackedelectron",
"workingDirectory": "$(TargetDir).electron",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
```
**Benefits:**
- ✅ Debug Electron main process
- ✅ Inspect native Electron APIs
- ✅ Node.js debugging capabilities
### 3. Cross-Platform WSL Debugging
Debug Linux builds directly from Windows Visual Studio:
```json
{
"profiles": {
"WSL": {
"commandName": "WSL2",
"launchUrl": "http://localhost:8001/",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_URLS": "http://localhost:8001/"
},
"distributionName": ""
}
}
}
```
**Benefits:**
- ✅ Debug Linux applications from Windows
- ✅ Test Linux-specific behavior
- ✅ Validate cross-platform compatibility
## 🔧 Setup Instructions
### 1. Configure Launch Settings
Add the debugging profiles to `Properties/launchSettings.json`:
```json
{
"profiles": {
"ASP.Net (unpackaged)": {
"commandName": "Project",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:8001/"
},
"Electron (unpackaged)": {
"commandName": "Executable",
"executablePath": "node",
"commandLineArgs": "node_modules/electron/cli.js main.js -unpackedelectron",
"workingDirectory": "$(TargetDir).electron",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"WSL": {
"commandName": "WSL2",
"launchUrl": "http://localhost:8001/",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_URLS": "http://localhost:8001/"
},
"distributionName": ""
}
}
}
```
### 2. Switch Runtime Identifiers
When switching between Windows and WSL debugging:
1. **Right-click your project** in Solution Explorer
2. Select **Properties**
3. Adjust the Runtime Identifier
Without Visual Studio:
1. **Edit** the .csproj file
2. **Update the RuntimeIdentifier**:
```xml
win-x64
linux-x64
```
### 3. Enable WSL Debugging
For WSL debugging, ensure:
- **WSL2 is installed** and configured
- **Linux distribution** is set in the launch profile
- **Project targets Linux RID** for WSL debugging
## 🚀 Debugging Workflow
### ASP.NET-First Debugging (Default)
1. **Select "ASP.Net (unpackaged)"** profile in Visual Studio
2. **Press F5** to start debugging
3. **Set breakpoints** in your C# code
4. **Use Hot Reload** to edit ASP.NET code during runtime
5. **Stop debugging** when finished
### Electron Process Debugging
1. **Select "Electron (unpackaged)"** profile
2. **Press F5** to start debugging
3. **Attach to Electron process** if needed
4. **Debug Node.js and Electron APIs**
### Cross-Platform Debugging
1. **Set RuntimeIdentifier** to `linux-x64`
2. **Select "WSL"** profile
3. **Press F5** to debug in WSL
4. **Test Linux-specific behavior**
## 🔍 Debugging Tips
### Hot Reload
- **Works with ASP.NET-first debugging**
- **Edit Razor views, controllers, and pages**
- **See changes instantly** without restart
- **Preserves application state**
### Breakpoint Debugging
```csharp
// Set breakpoints here
public async Task Index()
{
var data = await GetData(); // ← Breakpoint
return View(data);
}
```
### Process Management
- **ASP.NET-first mode** automatically manages Electron process lifecycle
- **Proper cleanup** on debugging session end
- **No manual process killing** required
## 🛠 Troubleshooting
### Common Issues
**"Electron process not found"**
- Ensure Node.js 22.x is installed
- Check that packages are restored (`dotnet restore`)
- Verify RuntimeIdentifier matches your target platform
**"WSL debugging fails"**
- Install and configure WSL2
- Ensure Linux distribution is properly set up
- Check that project targets correct RID
**"Hot Reload not working"**
- Use ASP.NET-first debugging profile
- Ensure ASPNETCORE_ENVIRONMENT=Development
- Check for compilation errors
## 🎨 Visual Debugging
*Placeholder for image showing Visual Studio debugging interface with Electron.NET*
The debugging interface provides familiar Visual Studio tools:
- **Locals and Watch windows** for variable inspection
- **Call Stack** for method call tracing
- **Immediate Window** for runtime evaluation
- **Hot Reload** indicator for edit-and-continue
## 🚀 Next Steps
- **[Startup Methods](Startup-Methods.md)** - Understanding different launch scenarios
- **[Package Building](Package-Building.md)** - Debug packaged applications
- **[Migration Guide](../Core/Migration-Guide.md)** - Moving from old debugging workflows
## 💡 Benefits
✅ **Native Visual Studio Experience** - No complex setup or manual attachment
✅ **Hot Reload Support** - Edit ASP.NET code during debugging
✅ **Cross-Platform Debugging** - Debug Linux apps from Windows
✅ **Multiple Debugging Modes** - Choose the right approach for your needs
✅ **Process Lifecycle Management** - Automatic cleanup and proper termination
================================================
FILE: docs/Using/Package-Building.md
================================================
# Package Building
ElectronNET.Core integrates with Visual Studio's publishing system to create distributable Electron packages using electron-builder. The process leverages .NET's build system while automatically generating the necessary Electron configuration files.
## 🎯 Publishing Overview
The publishing process differs slightly between ASP.NET and console applications:
- **ASP.NET Apps** - Use folder publishing with SelfContained=true
- **Console Apps** - Use folder publishing with SelfContained=false
## 🚀 Publishing Process
### Step 1: Create Publish Profiles
Add publish profiles to `Properties/PublishProfiles/`:
#### ASP.NET Application Profile (Windows)
**win-x64.pubxml:**
```xml
Release
Any CPU
true
FileSystem
publish\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\
FileSystem
<_TargetId>Folder
net10.0
win-x64
48eff821-2f4d-60cc-aa44-be0f1d6e5f35
true
```
#### ASP.NET Application Profile (Linux)
**linux-x64.pubxml:**
```xml
Release
Any CPU
true
FileSystem
publish\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\
FileSystem
<_TargetId>Folder
net10.0
linux-x64
48eff821-2f4d-60cc-aa44-be0f1d6e5f35
true
```
#### ASP.NET Application Profile (macOS Apple Silicon ARM64)
**osx-arm64.pubxml:**
```xml
Release
Any CPU
true
FileSystem
publish\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\
FileSystem
<_TargetId>Folder
net10.0
osx-arm64
48eff821-2f4d-60cc-aa44-be0f1d6e5f35
true
```
#### ASP.NET Application Profile (macOS Intel x64)
**osx-x64.pubxml:**
```xml
Release
Any CPU
true
FileSystem
publish\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\
FileSystem
<_TargetId>Folder
net10.0
osx-x64
48eff821-2f4d-60cc-aa44-be0f1d6e5f35
true
```
#### Console Application Profile (Windows)
**win-x64.pubxml:**
```xml
Release
Any CPU
publish\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\
FileSystem
net10.0
win-x64
false
false
false
```
#### Console Application Profile (Linux)
**linux-x64.pubxml:**
```xml
Release
Any CPU
publish\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\
FileSystem
net10.0
linux-x64
false
false
```
#### Console Application Profile (macOS Apple Silicon ARM64)
**osx-arm64.pubxml:**
```xml
Release
Any CPU
publish\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\
FileSystem
net10.0
osx-arm64
false
false
```
#### Console Application Profile (macOS Intel x64)
**osx-x64.pubxml:**
```xml
Release
Any CPU
publish\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\
FileSystem
net10.0
osx-x64
false
false
```
### Step 2: Configure Electron Builder
ElectronNET.Core automatically adds a default `electron-builder.json` file under `Properties\electron-builder.json`.
Please see here for details of the available configuration options: https://www.electron.build/.
### Step 3: Publish from Visual Studio
1. **Right-click your project** in Solution Explorer
2. **Select "Publish"**
4. **Select your publish profile** (Windows/Linux)
5. **Click "Publish"**
The publish process will:
- Build your .NET application
- Copy all files as needed
- Install npm dependencies
- Run electron-builder
> [!NOTE]
> When running publish for a Linux configuration on Windows, Electron.NET will automatically use WSL for the platform-specific steps.
**After publishing**, the final results will be in
`publish\Release\netx.0\xxx-xxx\`
## MacOS
> [!NOTE]
> macOS builds can't be created on Windows machines because they require symlinks that aren't supported on Windows (per [this Electron issue](https://github.com/electron-userland/electron-packager/issues/71)). macOS builds can be produced on either Linux or macOS machines.
## 🚀 Next Steps
- **[Startup Methods](Startup-Methods.md)** - Understanding different launch modes for packaged apps
- **[Debugging](Debugging.md)** - Debug packaged applications
- **[Migration Guide](../Core/Migration-Guide.md)** - Update existing projects for new publishing
## 💡 Benefits
✅ **Native VS Integration** - Use familiar publish workflows
✅ **Cross-Platform Building** - Build Linux packages from Windows
✅ **Automatic Configuration** - No manual electron-builder setup
✅ **Multiple Package Types** - NSIS, AppImage, DMG, etc.
✅ **CI/CD Ready** - Easy integration with build pipelines
================================================
FILE: docs/Using/Startup-Methods.md
================================================
# Startup Methods
ElectronNET.Core supports multiple startup methods to handle different development and deployment scenarios. The framework automatically detects the appropriate mode based on command-line flags and environment.
## 🎯 Startup Scenarios
The framework supports **8 different launch scenarios** covering every combination of:
- **Packaged vs Unpackaged** deployment
- **Console vs ASP.NET** application types
- **Dotnet-first vs Electron-first** initialization
## 🚀 Command-Line Flags
### Unpackaged Debugging Modes
#### **`-unpackedelectron`** - Electron-first debugging
```bash
# Launch Electron first, which then starts .NET
node node_modules/electron/cli.js main.js -unpackedelectron
```
#### **`-unpackeddotnet`** - .NET-first debugging
```bash
# Launch .NET first, which then starts Electron
dotnet run -unpackeddotnet
```
### Packaged Deployment Modes
#### **`-dotnetpacked`** - .NET-first packaged execution
```bash
# Run packaged app with .NET starting first
MyApp.exe -dotnetpacked
```
#### **No flags** - Electron-first packaged execution (default)
```bash
# Run packaged app with Electron starting first
MyApp.exe
```
## 📋 Startup Method Details
### 1. Unpackaged + Electron-First (Development)
- **Use Case**: Debug Electron main process and Node.js code
- **Command**: `-unpackedelectron` flag
- **Process Flow**:
1. Electron starts first
2. Electron launches .NET process
3. .NET connects back to Electron
4. Application runs with Electron in control
### 2. Unpackaged + .NET-First (Development)
- **Use Case**: Debug ASP.NET/C# code with Hot Reload
- **Command**: `-unpackeddotnet` flag
- **Process Flow**:
1. .NET application starts first
2. .NET launches Electron process
3. Electron connects back to .NET
4. Application runs with .NET in control
### 3. Packaged + .NET-First (Production)
- **Use Case**: Deployed application with .NET controlling lifecycle
- **Command**: `-dotnetpacked` flag
- **Process Flow**:
1. .NET executable starts first
2. .NET launches Electron from packaged files
3. Electron loads from app.asar or extracted files
4. .NET maintains process control
### 4. Packaged + Electron-First (Production)
- **Use Case**: Traditional Electron app behavior
- **Command**: No special flags
- **Process Flow**:
1. Electron executable starts first
2. Electron launches .NET from packaged files
3. .NET runs from Electron's process context
4. Electron maintains UI control
## 🔧 Configuration Examples
### ASP.NET Application Startup
```csharp
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Configure for different startup modes
builder.WebHost.UseElectron(args, async () =>
{
var browserWindow = await Electron.WindowManager.CreateWindowAsync(
new BrowserWindowOptions { Show = false });
await browserWindow.WebContents.LoadURLAsync("http://localhost:8001");
browserWindow.OnReadyToShow += () => browserWindow.Show();
});
var app = builder.Build();
app.Run();
```
### Console Application Startup
```csharp
// Program.cs
public static async Task Main(string[] args)
{
var runtimeController = ElectronNetRuntime.RuntimeController;
await runtimeController.Start();
await runtimeController.WaitReadyTask;
await InitializeApplication();
await runtimeController.WaitStoppedTask;
}
```
## 🎨 Visual Process Flow

The image above illustrates how each combination of deployment type, application type, and initialization order affects the process lifecycle.
## 🚀 Development Workflows
### Debugging Workflow
**ASP.NET-First Debugging** (Recommended)
```json
// launchSettings.json
{
"ASP.Net (unpackaged)": {
"commandName": "Project",
"commandLineArgs": "-unpackeddotnet"
}
}
```
**Electron-First Debugging**
```json
// launchSettings.json
{
"Electron (unpackaged)": {
"commandName": "Executable",
"executablePath": "node",
"commandLineArgs": "node_modules/electron/cli.js main.js -unpackedelectron"
}
}
```
### Production Deployment
**Dotnet-First Deployment**
```bash
# Build and package
dotnet publish -c Release -r win-x64
cd publish\Release\net8.0\win-x64
npm install
npx electron-builder
# Run with dotnet-first
MyApp.exe -dotnetpacked
```
**Electron-First Deployment** (Default)
```bash
# Run packaged application (no special flags needed)
MyApp.exe
```
## 🔍 Process Lifecycle Management
### Automatic Cleanup
ElectronNET.Core automatically manages process lifecycle:
- **Graceful shutdown** when main window is closed
- **Proper cleanup** of child processes
- **Error handling** for process failures
- **Cross-platform compatibility** for process management
### Manual Control
Access runtime controller for advanced scenarios:
```csharp
var runtime = ElectronNetRuntime.RuntimeController;
// Wait for Electron to be ready
await runtime.WaitReadyTask;
// Stop Electron runtime
await runtime.Stop();
await runtime.WaitStoppedTask;
```
## 🛠 Troubleshooting
### Common Startup Issues
**"Electron process not found"**
- Ensure Node.js 22.x is installed
- Check that .NET build succeeded
- Verify RuntimeIdentifier is set correctly
**"Port conflicts"**
- Use different ports for different startup modes
- Check that no other instances are running
- Verify firewall settings
**"Process won't terminate"**
- Use dotnet-first mode for better cleanup
- Check for unhandled exceptions
- Verify all windows are properly closed
## 💡 Best Practices
### Choose the Right Mode
- **Development**: Use .NET-first for C# debugging, Electron-first for Node.js debugging
- **Production**: Use .NET-first for better process control, Electron-first for traditional behavior
- **Cross-platform**: Use .NET-first for consistent behavior across platforms
### Environment Configuration
```xml
Production
```
## 🚀 Next Steps
- **[Debugging](Debugging.md)** - Debug different startup modes
- **[Package Building](Package-Building.md)** - Package for different deployment scenarios
- **[Migration Guide](../Core/Migration-Guide.md)** - Update existing apps for new startup methods
## 🎯 Summary
The flexible startup system ensures ElectronNET.Core works optimally in every scenario while providing the control and debugging experience .NET developers expect. Choose the appropriate mode based on your development workflow and deployment requirements.
================================================
FILE: docs/_Footer.md
================================================
Want to contribute to this documentation? Please fork and create a PR! The Wiki is autogenerated from the /docs content in the repository.
================================================
FILE: docs/_Sidebar.md
================================================
# Wiki
- [Home](Home.md)
- [About this Project](About.md)
# Electron.NET Core
- [What's new?](Core/What's-New.md)
- [Migration Guide](Core/Migration-Guide.md)
- [Migration Checks](Core/Migration-Checks.md)
- [Advanced Migration](Core/Advanced-Migration-Topics.md)
# Getting Started
- [System Requirements](GettingStarted/System-Requirements.md)
- [With ASP.Net](GettingStarted/ASP.Net.md)
- [With a Console App](GettingStarted/Console-App.md)
# Using Electron.NET
- [Configuration](Using/Configuration.md)
- [Startup-Methods](Using/Startup-Methods.md)
- [Debugging](Using/Debugging.md)
- [Package Building](Using/Package-Building.md)
- [Adding a `custom_main.js`](Using/Custom_main.md)
# API Reference
- [API Overview](API/Overview.md)
- [Electron.App](API/App.md)
- [Electron.Dialog](API/Dialog.md)
- [Electron.Menu](API/Menu.md)
- [Electron.WindowManager](API/WindowManager.md)
- [Electron.Shell](API/Shell.md)
- [Electron.Screen](API/Screen.md)
- [Electron.Notification](API/Notification.md)
- [Electron.Tray](API/Tray.md)
- [Electron.Clipboard](API/Clipboard.md)
- [Electron.Dock](API/Dock.md)
- [Electron.HostHook](API/HostHook.md)
- [Electron.IpcMain](API/IpcMain.md)
- [Electron.GlobalShortcut](API/GlobalShortcut.md)
- [Electron.AutoUpdater](API/AutoUpdater.md)
- [Electron.NativeTheme](API/NativeTheme.md)
- [Electron.PowerMonitor](API/PowerMonitor.md)
# Release Information
- [Package Description](RelInfo/Package-Description.md)
- [Changelog](RelInfo/Changelog.md)
================================================
FILE: docs/md-styles.css
================================================
/*!
* Bootstrap v3.4.1 (https://getbootstrap.com/)
* Copyright 2011-2019 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
html {
font-family: sans-serif;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%
}
article, aside, details, figcaption, figure, footer, header, hgroup, main, menu, nav, section, summary {
display: block
}
audio, canvas, progress, video {
display: inline-block;
vertical-align: baseline
}
audio:not([controls]) {
display: none;
height: 0
}
[hidden], template {
display: none
}
a:active, a:hover {
outline: 0
}
abbr[title] {
border-bottom: none;
text-decoration: underline;
-webkit-text-decoration: underline dotted;
-moz-text-decoration: underline dotted;
text-decoration: underline dotted
}
b, optgroup, strong {
font-weight: 700
}
dfn {
font-style: italic
}
h1 {
margin: .67em 0
}
mark {
background: #ff0;
color: #000
}
sub, sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline
}
sup {
top: -.5em
}
sub {
bottom: -.25em
}
img {
border: 0;
vertical-align: middle
}
svg:not(:root) {
overflow: hidden
}
hr {
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
height: 0
}
code, kbd, pre, samp {
font-size: 1em
}
button, input, optgroup, select, textarea {
color: inherit;
font: inherit;
margin: 0
}
button {
overflow: visible
}
button, select {
text-transform: none
}
button, html input[type=button], input[type=reset], input[type=submit] {
-webkit-appearance: button;
cursor: pointer
}
button[disabled], html input[disabled] {
cursor: default
}
button::-moz-focus-inner, input::-moz-focus-inner {
border: 0;
padding: 0
}
input {
line-height: normal
}
input[type=checkbox], input[type=radio] {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 0
}
input[type=number]::-webkit-inner-spin-button, input[type=number]::-webkit-outer-spin-button {
height: auto
}
input[type=search] {
-webkit-appearance: textfield;
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box
}
input[type=search]::-webkit-search-cancel-button, input[type=search]::-webkit-search-decoration {
-webkit-appearance: none
}
textarea {
overflow: auto
}
td, th {
padding: 0
}
/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */
@media print {
*, :after, :before {
color: #000 !important;
text-shadow: none !important;
background: 0 0 !important;
-webkit-box-shadow: none !important;
box-shadow: none !important
}
a, a:visited {
text-decoration: underline
}
a[href]:after {
content: " (" attr(href) ")"
}
abbr[title]:after {
content: " (" attr(title) ")"
}
a[href^="#"]:after, a[href^="javascript:"]:after {
content: ""
}
blockquote, pre {
border: 1px solid #999;
page-break-inside: avoid
}
thead {
display: table-header-group
}
img, tr {
page-break-inside: avoid
}
img {
max-width: 100% !important
}
h2, h3, p {
orphans: 3;
widows: 3
}
h2, h3 {
page-break-after: avoid
}
.navbar {
display: none
}
.btn > .caret, .dropup > .btn > .caret {
border-top-color: #000 !important
}
.label {
border: 1px solid #000
}
.table {
border-collapse: collapse !important
}
.table td, .table th {
background-color: #fff !important
}
.table-bordered td, .table-bordered th {
border: 1px solid #ddd !important
}
}
@font-face {
font-family: "Glyphicons Halflings";
src: url("../fonts/glyphicons-halflings-regular.eot");
src: url("../fonts/glyphicons-halflings-regular.eot?#iefix") format("embedded-opentype"),url("../fonts/glyphicons-halflings-regular.woff2") format("woff2"),url("../fonts/glyphicons-halflings-regular.woff") format("woff"),url("../fonts/glyphicons-halflings-regular.ttf") format("truetype"),url("../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular") format("svg")
}
.glyphicon {
position: relative;
top: 1px;
display: inline-block;
font-family: "Glyphicons Halflings";
font-style: normal;
font-weight: 400;
line-height: 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale
}
.glyphicon-asterisk:before {
content: "\002a"
}
.glyphicon-plus:before {
content: "\002b"
}
.glyphicon-eur:before, .glyphicon-euro:before {
content: "\20ac"
}
.glyphicon-minus:before {
content: "\2212"
}
.glyphicon-cloud:before {
content: "\2601"
}
.glyphicon-envelope:before {
content: "\2709"
}
.glyphicon-pencil:before {
content: "\270f"
}
.glyphicon-glass:before {
content: "\e001"
}
.glyphicon-music:before {
content: "\e002"
}
.glyphicon-search:before {
content: "\e003"
}
.glyphicon-heart:before {
content: "\e005"
}
.glyphicon-star:before {
content: "\e006"
}
.glyphicon-star-empty:before {
content: "\e007"
}
.glyphicon-user:before {
content: "\e008"
}
.glyphicon-film:before {
content: "\e009"
}
.glyphicon-th-large:before {
content: "\e010"
}
.glyphicon-th:before {
content: "\e011"
}
.glyphicon-th-list:before {
content: "\e012"
}
.glyphicon-ok:before {
content: "\e013"
}
.glyphicon-remove:before {
content: "\e014"
}
.glyphicon-zoom-in:before {
content: "\e015"
}
.glyphicon-zoom-out:before {
content: "\e016"
}
.glyphicon-off:before {
content: "\e017"
}
.glyphicon-signal:before {
content: "\e018"
}
.glyphicon-cog:before {
content: "\e019"
}
.glyphicon-trash:before {
content: "\e020"
}
.glyphicon-home:before {
content: "\e021"
}
.glyphicon-file:before {
content: "\e022"
}
.glyphicon-time:before {
content: "\e023"
}
.glyphicon-road:before {
content: "\e024"
}
.glyphicon-download-alt:before {
content: "\e025"
}
.glyphicon-download:before {
content: "\e026"
}
.glyphicon-upload:before {
content: "\e027"
}
.glyphicon-inbox:before {
content: "\e028"
}
.glyphicon-play-circle:before {
content: "\e029"
}
.glyphicon-repeat:before {
content: "\e030"
}
.glyphicon-refresh:before {
content: "\e031"
}
.glyphicon-list-alt:before {
content: "\e032"
}
.glyphicon-lock:before {
content: "\e033"
}
.glyphicon-flag:before {
content: "\e034"
}
.glyphicon-headphones:before {
content: "\e035"
}
.glyphicon-volume-off:before {
content: "\e036"
}
.glyphicon-volume-down:before {
content: "\e037"
}
.glyphicon-volume-up:before {
content: "\e038"
}
.glyphicon-qrcode:before {
content: "\e039"
}
.glyphicon-barcode:before {
content: "\e040"
}
.glyphicon-tag:before {
content: "\e041"
}
.glyphicon-tags:before {
content: "\e042"
}
.glyphicon-book:before {
content: "\e043"
}
.glyphicon-bookmark:before {
content: "\e044"
}
.glyphicon-print:before {
content: "\e045"
}
.glyphicon-camera:before {
content: "\e046"
}
.glyphicon-font:before {
content: "\e047"
}
.glyphicon-bold:before {
content: "\e048"
}
.glyphicon-italic:before {
content: "\e049"
}
.glyphicon-text-height:before {
content: "\e050"
}
.glyphicon-text-width:before {
content: "\e051"
}
.glyphicon-align-left:before {
content: "\e052"
}
.glyphicon-align-center:before {
content: "\e053"
}
.glyphicon-align-right:before {
content: "\e054"
}
.glyphicon-align-justify:before {
content: "\e055"
}
.glyphicon-list:before {
content: "\e056"
}
.glyphicon-indent-left:before {
content: "\e057"
}
.glyphicon-indent-right:before {
content: "\e058"
}
.glyphicon-facetime-video:before {
content: "\e059"
}
.glyphicon-picture:before {
content: "\e060"
}
.glyphicon-map-marker:before {
content: "\e062"
}
.glyphicon-adjust:before {
content: "\e063"
}
.glyphicon-tint:before {
content: "\e064"
}
.glyphicon-edit:before {
content: "\e065"
}
.glyphicon-share:before {
content: "\e066"
}
.glyphicon-check:before {
content: "\e067"
}
.glyphicon-move:before {
content: "\e068"
}
.glyphicon-step-backward:before {
content: "\e069"
}
.glyphicon-fast-backward:before {
content: "\e070"
}
.glyphicon-backward:before {
content: "\e071"
}
.glyphicon-play:before {
content: "\e072"
}
.glyphicon-pause:before {
content: "\e073"
}
.glyphicon-stop:before {
content: "\e074"
}
.glyphicon-forward:before {
content: "\e075"
}
.glyphicon-fast-forward:before {
content: "\e076"
}
.glyphicon-step-forward:before {
content: "\e077"
}
.glyphicon-eject:before {
content: "\e078"
}
.glyphicon-chevron-left:before {
content: "\e079"
}
.glyphicon-chevron-right:before {
content: "\e080"
}
.glyphicon-plus-sign:before {
content: "\e081"
}
.glyphicon-minus-sign:before {
content: "\e082"
}
.glyphicon-remove-sign:before {
content: "\e083"
}
.glyphicon-ok-sign:before {
content: "\e084"
}
.glyphicon-question-sign:before {
content: "\e085"
}
.glyphicon-info-sign:before {
content: "\e086"
}
.glyphicon-screenshot:before {
content: "\e087"
}
.glyphicon-remove-circle:before {
content: "\e088"
}
.glyphicon-ok-circle:before {
content: "\e089"
}
.glyphicon-ban-circle:before {
content: "\e090"
}
.glyphicon-arrow-left:before {
content: "\e091"
}
.glyphicon-arrow-right:before {
content: "\e092"
}
.glyphicon-arrow-up:before {
content: "\e093"
}
.glyphicon-arrow-down:before {
content: "\e094"
}
.glyphicon-share-alt:before {
content: "\e095"
}
.glyphicon-resize-full:before {
content: "\e096"
}
.glyphicon-resize-small:before {
content: "\e097"
}
.glyphicon-exclamation-sign:before {
content: "\e101"
}
.glyphicon-gift:before {
content: "\e102"
}
.glyphicon-leaf:before {
content: "\e103"
}
.glyphicon-fire:before {
content: "\e104"
}
.glyphicon-eye-open:before {
content: "\e105"
}
.glyphicon-eye-close:before {
content: "\e106"
}
.glyphicon-warning-sign:before {
content: "\e107"
}
.glyphicon-plane:before {
content: "\e108"
}
.glyphicon-calendar:before {
content: "\e109"
}
.glyphicon-random:before {
content: "\e110"
}
.glyphicon-comment:before {
content: "\e111"
}
.glyphicon-magnet:before {
content: "\e112"
}
.glyphicon-chevron-up:before {
content: "\e113"
}
.glyphicon-chevron-down:before {
content: "\e114"
}
.glyphicon-retweet:before {
content: "\e115"
}
.glyphicon-shopping-cart:before {
content: "\e116"
}
.glyphicon-folder-close:before {
content: "\e117"
}
.glyphicon-folder-open:before {
content: "\e118"
}
.glyphicon-resize-vertical:before {
content: "\e119"
}
.glyphicon-resize-horizontal:before {
content: "\e120"
}
.glyphicon-hdd:before {
content: "\e121"
}
.glyphicon-bullhorn:before {
content: "\e122"
}
.glyphicon-bell:before {
content: "\e123"
}
.glyphicon-certificate:before {
content: "\e124"
}
.glyphicon-thumbs-up:before {
content: "\e125"
}
.glyphicon-thumbs-down:before {
content: "\e126"
}
.glyphicon-hand-right:before {
content: "\e127"
}
.glyphicon-hand-left:before {
content: "\e128"
}
.glyphicon-hand-up:before {
content: "\e129"
}
.glyphicon-hand-down:before {
content: "\e130"
}
.glyphicon-circle-arrow-right:before {
content: "\e131"
}
.glyphicon-circle-arrow-left:before {
content: "\e132"
}
.glyphicon-circle-arrow-up:before {
content: "\e133"
}
.glyphicon-circle-arrow-down:before {
content: "\e134"
}
.glyphicon-globe:before {
content: "\e135"
}
.glyphicon-wrench:before {
content: "\e136"
}
.glyphicon-tasks:before {
content: "\e137"
}
.glyphicon-filter:before {
content: "\e138"
}
.glyphicon-briefcase:before {
content: "\e139"
}
.glyphicon-fullscreen:before {
content: "\e140"
}
.glyphicon-dashboard:before {
content: "\e141"
}
.glyphicon-paperclip:before {
content: "\e142"
}
.glyphicon-heart-empty:before {
content: "\e143"
}
.glyphicon-link:before {
content: "\e144"
}
.glyphicon-phone:before {
content: "\e145"
}
.glyphicon-pushpin:before {
content: "\e146"
}
.glyphicon-usd:before {
content: "\e148"
}
.glyphicon-gbp:before {
content: "\e149"
}
.glyphicon-sort:before {
content: "\e150"
}
.glyphicon-sort-by-alphabet:before {
content: "\e151"
}
.glyphicon-sort-by-alphabet-alt:before {
content: "\e152"
}
.glyphicon-sort-by-order:before {
content: "\e153"
}
.glyphicon-sort-by-order-alt:before {
content: "\e154"
}
.glyphicon-sort-by-attributes:before {
content: "\e155"
}
.glyphicon-sort-by-attributes-alt:before {
content: "\e156"
}
.glyphicon-unchecked:before {
content: "\e157"
}
.glyphicon-expand:before {
content: "\e158"
}
.glyphicon-collapse-down:before {
content: "\e159"
}
.glyphicon-collapse-up:before {
content: "\e160"
}
.glyphicon-log-in:before {
content: "\e161"
}
.glyphicon-flash:before {
content: "\e162"
}
.glyphicon-log-out:before {
content: "\e163"
}
.glyphicon-new-window:before {
content: "\e164"
}
.glyphicon-record:before {
content: "\e165"
}
.glyphicon-save:before {
content: "\e166"
}
.glyphicon-open:before {
content: "\e167"
}
.glyphicon-saved:before {
content: "\e168"
}
.glyphicon-import:before {
content: "\e169"
}
.glyphicon-export:before {
content: "\e170"
}
.glyphicon-send:before {
content: "\e171"
}
.glyphicon-floppy-disk:before {
content: "\e172"
}
.glyphicon-floppy-saved:before {
content: "\e173"
}
.glyphicon-floppy-remove:before {
content: "\e174"
}
.glyphicon-floppy-save:before {
content: "\e175"
}
.glyphicon-floppy-open:before {
content: "\e176"
}
.glyphicon-credit-card:before {
content: "\e177"
}
.glyphicon-transfer:before {
content: "\e178"
}
.glyphicon-cutlery:before {
content: "\e179"
}
.glyphicon-header:before {
content: "\e180"
}
.glyphicon-compressed:before {
content: "\e181"
}
.glyphicon-earphone:before {
content: "\e182"
}
.glyphicon-phone-alt:before {
content: "\e183"
}
.glyphicon-tower:before {
content: "\e184"
}
.glyphicon-stats:before {
content: "\e185"
}
.glyphicon-sd-video:before {
content: "\e186"
}
.glyphicon-hd-video:before {
content: "\e187"
}
.glyphicon-subtitles:before {
content: "\e188"
}
.glyphicon-sound-stereo:before {
content: "\e189"
}
.glyphicon-sound-dolby:before {
content: "\e190"
}
.glyphicon-sound-5-1:before {
content: "\e191"
}
.glyphicon-sound-6-1:before {
content: "\e192"
}
.glyphicon-sound-7-1:before {
content: "\e193"
}
.glyphicon-copyright-mark:before {
content: "\e194"
}
.glyphicon-registration-mark:before {
content: "\e195"
}
.glyphicon-cloud-download:before {
content: "\e197"
}
.glyphicon-cloud-upload:before {
content: "\e198"
}
.glyphicon-tree-conifer:before {
content: "\e199"
}
.glyphicon-tree-deciduous:before {
content: "\e200"
}
.glyphicon-cd:before {
content: "\e201"
}
.glyphicon-save-file:before {
content: "\e202"
}
.glyphicon-open-file:before {
content: "\e203"
}
.glyphicon-level-up:before {
content: "\e204"
}
.glyphicon-copy:before {
content: "\e205"
}
.glyphicon-paste:before {
content: "\e206"
}
.glyphicon-alert:before {
content: "\e209"
}
.glyphicon-equalizer:before {
content: "\e210"
}
.glyphicon-king:before {
content: "\e211"
}
.glyphicon-queen:before {
content: "\e212"
}
.glyphicon-pawn:before {
content: "\e213"
}
.glyphicon-bishop:before {
content: "\e214"
}
.glyphicon-knight:before {
content: "\e215"
}
.glyphicon-baby-formula:before {
content: "\e216"
}
.glyphicon-tent:before {
content: "\26fa"
}
.glyphicon-blackboard:before {
content: "\e218"
}
.glyphicon-bed:before {
content: "\e219"
}
.glyphicon-apple:before {
content: "\f8ff"
}
.glyphicon-erase:before {
content: "\e221"
}
.glyphicon-hourglass:before {
content: "\231b"
}
.glyphicon-lamp:before {
content: "\e223"
}
.glyphicon-duplicate:before {
content: "\e224"
}
.glyphicon-piggy-bank:before {
content: "\e225"
}
.glyphicon-scissors:before {
content: "\e226"
}
.glyphicon-bitcoin:before, .glyphicon-btc:before, .glyphicon-xbt:before {
content: "\e227"
}
.glyphicon-jpy:before, .glyphicon-yen:before {
content: "\00a5"
}
.glyphicon-rub:before, .glyphicon-ruble:before {
content: "\20bd"
}
.glyphicon-scale:before {
content: "\e230"
}
.glyphicon-ice-lolly:before {
content: "\e231"
}
.glyphicon-ice-lolly-tasted:before {
content: "\e232"
}
.glyphicon-education:before {
content: "\e233"
}
.glyphicon-option-horizontal:before {
content: "\e234"
}
.glyphicon-option-vertical:before {
content: "\e235"
}
.glyphicon-menu-hamburger:before {
content: "\e236"
}
.glyphicon-modal-window:before {
content: "\e237"
}
.glyphicon-oil:before {
content: "\e238"
}
.glyphicon-grain:before {
content: "\e239"
}
.glyphicon-sunglasses:before {
content: "\e240"
}
.glyphicon-text-size:before {
content: "\e241"
}
.glyphicon-text-color:before {
content: "\e242"
}
.glyphicon-text-background:before {
content: "\e243"
}
.glyphicon-object-align-top:before {
content: "\e244"
}
.glyphicon-object-align-bottom:before {
content: "\e245"
}
.glyphicon-object-align-horizontal:before {
content: "\e246"
}
.glyphicon-object-align-left:before {
content: "\e247"
}
.glyphicon-object-align-vertical:before {
content: "\e248"
}
.glyphicon-object-align-right:before {
content: "\e249"
}
.glyphicon-triangle-right:before {
content: "\e250"
}
.glyphicon-triangle-left:before {
content: "\e251"
}
.glyphicon-triangle-bottom:before {
content: "\e252"
}
.glyphicon-triangle-top:before {
content: "\e253"
}
.glyphicon-console:before {
content: "\e254"
}
.glyphicon-superscript:before {
content: "\e255"
}
.glyphicon-subscript:before {
content: "\e256"
}
.glyphicon-menu-left:before {
content: "\e257"
}
.glyphicon-menu-right:before {
content: "\e258"
}
.glyphicon-menu-down:before {
content: "\e259"
}
.glyphicon-menu-up:before {
content: "\e260"
}
*, :after, :before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box
}
html {
font-size: 10px;
-webkit-tap-highlight-color: transparent
}
button, input, select, textarea {
font-family: inherit;
font-size: inherit;
line-height: inherit
}
a {
background-color: transparent;
color: #337ab7;
text-decoration: none
}
a:focus, a:hover {
color: #23527c;
text-decoration: underline
}
a:focus {
outline: -webkit-focus-ring-color auto 5px;
outline-offset: -2px
}
figure {
margin: 0
}
.carousel-inner > .item > a > img, .carousel-inner > .item > img, .img-responsive, .thumbnail a > img, .thumbnail > img {
display: block;
max-width: 100%;
height: auto
}
.img-rounded {
border-radius: 6px
}
.img-thumbnail {
padding: 4px;
line-height: 1.42857143;
background-color: #fff;
border: 1px solid #ddd;
border-radius: 4px;
-webkit-transition: .2s ease-in-out;
-o-transition: .2s ease-in-out;
transition: .2s ease-in-out;
display: inline-block;
max-width: 100%;
height: auto
}
.img-circle {
border-radius: 50%
}
hr {
margin-top: 20px;
margin-bottom: 20px;
border: 0;
border-top: 1px solid #eee
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
border: 0
}
.sr-only-focusable:active, .sr-only-focusable:focus {
position: static;
width: auto;
height: auto;
margin: 0;
overflow: visible;
clip: auto
}
[role=button] {
cursor: pointer
}
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit
}
.h1 .small, .h1 small, .h2 .small, .h2 small, .h3 .small, .h3 small, .h4 .small, .h4 small, .h5 .small, .h5 small, .h6 .small, .h6 small, h1 .small, h1 small, h2 .small, h2 small, h3 .small, h3 small, h4 .small, h4 small, h5 .small, h5 small, h6 .small, h6 small {
font-weight: 400;
line-height: 1;
color: #777
}
.h1, .h2, .h3, h1, h2, h3 {
margin-top: 20px;
margin-bottom: 10px
}
.h1 .small, .h1 small, .h2 .small, .h2 small, .h3 .small, .h3 small, h1 .small, h1 small, h2 .small, h2 small, h3 .small, h3 small {
font-size: 65%
}
.h4, .h5, .h6, h4, h5, h6 {
margin-top: 10px;
margin-bottom: 10px
}
.h4 .small, .h4 small, .h5 .small, .h5 small, .h6 .small, .h6 small, h4 .small, h4 small, h5 .small, h5 small, h6 .small, h6 small {
font-size: 75%
}
.h1, h1 {
font-size: 36px
}
.h2, h2 {
font-size: 30px
}
.h3, h3 {
font-size: 24px
}
.h4, h4 {
font-size: 18px
}
.h5, h5 {
font-size: 14px
}
.h6, h6 {
font-size: 12px
}
p {
margin: 0 0 10px
}
.lead {
margin-bottom: 20px;
font-size: 16px;
font-weight: 300;
line-height: 1.4
}
@media (min-width:768px) {
.lead {
font-size: 21px
}
.dl-horizontal dt {
float: left;
width: 160px;
clear: left;
text-align: right;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap
}
.dl-horizontal dd {
margin-left: 180px
}
}
.small, small {
font-size: 85%
}
.mark, mark {
padding: .2em;
background-color: #fcf8e3
}
.text-left {
text-align: left
}
.text-right {
text-align: right
}
.text-center {
text-align: center
}
.text-justify {
text-align: justify
}
.text-nowrap {
white-space: nowrap
}
.text-lowercase {
text-transform: lowercase
}
.text-uppercase {
text-transform: uppercase
}
.text-capitalize {
text-transform: capitalize
}
.text-muted {
color: #777
}
.text-primary {
color: #337ab7
}
a.text-primary:focus, a.text-primary:hover {
color: #286090
}
.text-success {
color: #3c763d
}
a.text-success:focus, a.text-success:hover {
color: #2b542c
}
.text-info {
color: #31708f
}
a.text-info:focus, a.text-info:hover {
color: #245269
}
.text-warning {
color: #8a6d3b
}
a.text-warning:focus, a.text-warning:hover {
color: #66512c
}
.text-danger {
color: #a94442
}
a.text-danger:focus, a.text-danger:hover {
color: #843534
}
.bg-primary {
color: #fff;
background-color: #337ab7
}
a.bg-primary:focus, a.bg-primary:hover {
background-color: #286090
}
.bg-success {
background-color: #dff0d8
}
a.bg-success:focus, a.bg-success:hover {
background-color: #c1e2b3
}
.bg-info {
background-color: #d9edf7
}
a.bg-info:focus, a.bg-info:hover {
background-color: #afd9ee
}
.bg-warning {
background-color: #fcf8e3
}
a.bg-warning:focus, a.bg-warning:hover {
background-color: #f7ecb5
}
.bg-danger {
background-color: #f2dede
}
a.bg-danger:focus, a.bg-danger:hover {
background-color: #e4b9b9
}
.page-header {
padding-bottom: 9px;
margin: 40px 0 20px;
border-bottom: 1px solid #eee
}
ol, ul {
margin-top: 0;
margin-bottom: 10px
}
ol ol, ol ul, ul ol, ul ul {
margin-bottom: 0
}
.list-unstyled {
padding-left: 0;
list-style: none
}
.list-inline {
padding-left: 0;
list-style: none;
margin-left: -5px
}
.list-inline > li {
display: inline-block;
padding-right: 5px;
padding-left: 5px
}
dl {
margin-top: 0;
margin-bottom: 20px
}
dd, dt {
line-height: 1.42857143
}
dt {
font-weight: 700
}
dd {
margin-left: 0
}
abbr[data-original-title], abbr[title] {
cursor: help
}
.initialism {
font-size: 90%;
text-transform: uppercase
}
blockquote {
padding: 10px 20px;
margin: 0 0 20px;
font-size: 17.5px;
border-left: 5px solid #eee
}
blockquote ol:last-child, blockquote p:last-child, blockquote ul:last-child {
margin-bottom: 0
}
blockquote .small, blockquote footer, blockquote small {
display: block;
font-size: 80%;
line-height: 1.42857143;
color: #777
}
blockquote .small:before, blockquote footer:before, blockquote small:before {
content: "\2014 \00A0"
}
.blockquote-reverse, blockquote.pull-right {
padding-right: 15px;
padding-left: 0;
text-align: right;
border-right: 5px solid #eee;
border-left: 0
}
.blockquote-reverse .small:before, .blockquote-reverse footer:before, .blockquote-reverse small:before, blockquote.pull-right .small:before, blockquote.pull-right footer:before, blockquote.pull-right small:before {
content: ""
}
.blockquote-reverse .small:after, .blockquote-reverse footer:after, .blockquote-reverse small:after, blockquote.pull-right .small:after, blockquote.pull-right footer:after, blockquote.pull-right small:after {
content: "\00A0 \2014"
}
address {
margin-bottom: 20px;
font-style: normal;
line-height: 1.42857143
}
code, kbd, pre, samp {
font-family: Menlo,Monaco,Consolas,"Courier New",monospace
}
code {
padding: 2px 4px;
font-size: 90%;
color: #c7254e;
background-color: #f9f2f4;
border-radius: 4px
}
kbd {
padding: 2px 4px;
font-size: 90%;
color: #fff;
background-color: #333;
border-radius: 3px;
-webkit-box-shadow: inset 0 -1px 0 rgba(0,0,0,.25);
box-shadow: inset 0 -1px 0 rgba(0,0,0,.25)
}
kbd kbd {
padding: 0;
font-size: 100%;
font-weight: 700;
-webkit-box-shadow: none;
box-shadow: none
}
pre {
overflow: auto;
display: block;
padding: 9.5px;
margin: 0 0 10px;
font-size: 13px;
line-height: 1.42857143;
color: #333;
word-break: break-all;
word-wrap: break-word;
background-color: #f5f5f5;
border: 1px solid #ccc;
border-radius: 4px
}
pre code {
padding: 0;
font-size: inherit;
color: inherit;
white-space: pre-wrap;
background-color: transparent;
border-radius: 0
}
.pre-scrollable {
max-height: 340px;
overflow-y: scroll
}
.container {
padding-right: 15px;
padding-left: 15px;
margin-right: auto;
margin-left: auto
}
@media (min-width:768px) {
.container {
width: 750px
}
}
@media (min-width:992px) {
.container {
width: 970px
}
}
@media (min-width:1200px) {
.container {
width: 1170px
}
}
.container-fluid {
padding-right: 15px;
padding-left: 15px;
margin-right: auto;
margin-left: auto
}
.row {
margin-right: -15px;
margin-left: -15px
}
.row-no-gutters {
margin-right: 0;
margin-left: 0
}
.row-no-gutters [class*=col-] {
padding-right: 0;
padding-left: 0
}
.col-lg-1, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-md-1, .col-md-10, .col-md-11, .col-md-12, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-sm-1, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-xs-1, .col-xs-10, .col-xs-11, .col-xs-12, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9 {
position: relative;
min-height: 1px;
padding-right: 15px;
padding-left: 15px
}
.col-xs-1, .col-xs-10, .col-xs-11, .col-xs-12, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9 {
float: left
}
.col-xs-12 {
width: 100%
}
.col-xs-11 {
width: 91.66666667%
}
.col-xs-10 {
width: 83.33333333%
}
.col-xs-9 {
width: 75%
}
.col-xs-8 {
width: 66.66666667%
}
.col-xs-7 {
width: 58.33333333%
}
.col-xs-6 {
width: 50%
}
.col-xs-5 {
width: 41.66666667%
}
.col-xs-4 {
width: 33.33333333%
}
.col-xs-3 {
width: 25%
}
.col-xs-2 {
width: 16.66666667%
}
.col-xs-1 {
width: 8.33333333%
}
.col-xs-pull-12 {
right: 100%
}
.col-xs-pull-11 {
right: 91.66666667%
}
.col-xs-pull-10 {
right: 83.33333333%
}
.col-xs-pull-9 {
right: 75%
}
.col-xs-pull-8 {
right: 66.66666667%
}
.col-xs-pull-7 {
right: 58.33333333%
}
.col-xs-pull-6 {
right: 50%
}
.col-xs-pull-5 {
right: 41.66666667%
}
.col-xs-pull-4 {
right: 33.33333333%
}
.col-xs-pull-3 {
right: 25%
}
.col-xs-pull-2 {
right: 16.66666667%
}
.col-xs-pull-1 {
right: 8.33333333%
}
.col-xs-pull-0 {
right: auto
}
.col-xs-push-12 {
left: 100%
}
.col-xs-push-11 {
left: 91.66666667%
}
.col-xs-push-10 {
left: 83.33333333%
}
.col-xs-push-9 {
left: 75%
}
.col-xs-push-8 {
left: 66.66666667%
}
.col-xs-push-7 {
left: 58.33333333%
}
.col-xs-push-6 {
left: 50%
}
.col-xs-push-5 {
left: 41.66666667%
}
.col-xs-push-4 {
left: 33.33333333%
}
.col-xs-push-3 {
left: 25%
}
.col-xs-push-2 {
left: 16.66666667%
}
.col-xs-push-1 {
left: 8.33333333%
}
.col-xs-push-0 {
left: auto
}
.col-xs-offset-12 {
margin-left: 100%
}
.col-xs-offset-11 {
margin-left: 91.66666667%
}
.col-xs-offset-10 {
margin-left: 83.33333333%
}
.col-xs-offset-9 {
margin-left: 75%
}
.col-xs-offset-8 {
margin-left: 66.66666667%
}
.col-xs-offset-7 {
margin-left: 58.33333333%
}
.col-xs-offset-6 {
margin-left: 50%
}
.col-xs-offset-5 {
margin-left: 41.66666667%
}
.col-xs-offset-4 {
margin-left: 33.33333333%
}
.col-xs-offset-3 {
margin-left: 25%
}
.col-xs-offset-2 {
margin-left: 16.66666667%
}
.col-xs-offset-1 {
margin-left: 8.33333333%
}
.col-xs-offset-0 {
margin-left: 0
}
@media (min-width:768px) {
.col-sm-1, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9 {
float: left
}
.col-sm-12 {
width: 100%
}
.col-sm-11 {
width: 91.66666667%
}
.col-sm-10 {
width: 83.33333333%
}
.col-sm-9 {
width: 75%
}
.col-sm-8 {
width: 66.66666667%
}
.col-sm-7 {
width: 58.33333333%
}
.col-sm-6 {
width: 50%
}
.col-sm-5 {
width: 41.66666667%
}
.col-sm-4 {
width: 33.33333333%
}
.col-sm-3 {
width: 25%
}
.col-sm-2 {
width: 16.66666667%
}
.col-sm-1 {
width: 8.33333333%
}
.col-sm-pull-12 {
right: 100%
}
.col-sm-pull-11 {
right: 91.66666667%
}
.col-sm-pull-10 {
right: 83.33333333%
}
.col-sm-pull-9 {
right: 75%
}
.col-sm-pull-8 {
right: 66.66666667%
}
.col-sm-pull-7 {
right: 58.33333333%
}
.col-sm-pull-6 {
right: 50%
}
.col-sm-pull-5 {
right: 41.66666667%
}
.col-sm-pull-4 {
right: 33.33333333%
}
.col-sm-pull-3 {
right: 25%
}
.col-sm-pull-2 {
right: 16.66666667%
}
.col-sm-pull-1 {
right: 8.33333333%
}
.col-sm-pull-0 {
right: auto
}
.col-sm-push-12 {
left: 100%
}
.col-sm-push-11 {
left: 91.66666667%
}
.col-sm-push-10 {
left: 83.33333333%
}
.col-sm-push-9 {
left: 75%
}
.col-sm-push-8 {
left: 66.66666667%
}
.col-sm-push-7 {
left: 58.33333333%
}
.col-sm-push-6 {
left: 50%
}
.col-sm-push-5 {
left: 41.66666667%
}
.col-sm-push-4 {
left: 33.33333333%
}
.col-sm-push-3 {
left: 25%
}
.col-sm-push-2 {
left: 16.66666667%
}
.col-sm-push-1 {
left: 8.33333333%
}
.col-sm-push-0 {
left: auto
}
.col-sm-offset-12 {
margin-left: 100%
}
.col-sm-offset-11 {
margin-left: 91.66666667%
}
.col-sm-offset-10 {
margin-left: 83.33333333%
}
.col-sm-offset-9 {
margin-left: 75%
}
.col-sm-offset-8 {
margin-left: 66.66666667%
}
.col-sm-offset-7 {
margin-left: 58.33333333%
}
.col-sm-offset-6 {
margin-left: 50%
}
.col-sm-offset-5 {
margin-left: 41.66666667%
}
.col-sm-offset-4 {
margin-left: 33.33333333%
}
.col-sm-offset-3 {
margin-left: 25%
}
.col-sm-offset-2 {
margin-left: 16.66666667%
}
.col-sm-offset-1 {
margin-left: 8.33333333%
}
.col-sm-offset-0 {
margin-left: 0
}
}
@media (min-width:992px) {
.col-md-1, .col-md-10, .col-md-11, .col-md-12, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9 {
float: left
}
.col-md-12 {
width: 100%
}
.col-md-11 {
width: 91.66666667%
}
.col-md-10 {
width: 83.33333333%
}
.col-md-9 {
width: 75%
}
.col-md-8 {
width: 66.66666667%
}
.col-md-7 {
width: 58.33333333%
}
.col-md-6 {
width: 50%
}
.col-md-5 {
width: 41.66666667%
}
.col-md-4 {
width: 33.33333333%
}
.col-md-3 {
width: 25%
}
.col-md-2 {
width: 16.66666667%
}
.col-md-1 {
width: 8.33333333%
}
.col-md-pull-12 {
right: 100%
}
.col-md-pull-11 {
right: 91.66666667%
}
.col-md-pull-10 {
right: 83.33333333%
}
.col-md-pull-9 {
right: 75%
}
.col-md-pull-8 {
right: 66.66666667%
}
.col-md-pull-7 {
right: 58.33333333%
}
.col-md-pull-6 {
right: 50%
}
.col-md-pull-5 {
right: 41.66666667%
}
.col-md-pull-4 {
right: 33.33333333%
}
.col-md-pull-3 {
right: 25%
}
.col-md-pull-2 {
right: 16.66666667%
}
.col-md-pull-1 {
right: 8.33333333%
}
.col-md-pull-0 {
right: auto
}
.col-md-push-12 {
left: 100%
}
.col-md-push-11 {
left: 91.66666667%
}
.col-md-push-10 {
left: 83.33333333%
}
.col-md-push-9 {
left: 75%
}
.col-md-push-8 {
left: 66.66666667%
}
.col-md-push-7 {
left: 58.33333333%
}
.col-md-push-6 {
left: 50%
}
.col-md-push-5 {
left: 41.66666667%
}
.col-md-push-4 {
left: 33.33333333%
}
.col-md-push-3 {
left: 25%
}
.col-md-push-2 {
left: 16.66666667%
}
.col-md-push-1 {
left: 8.33333333%
}
.col-md-push-0 {
left: auto
}
.col-md-offset-12 {
margin-left: 100%
}
.col-md-offset-11 {
margin-left: 91.66666667%
}
.col-md-offset-10 {
margin-left: 83.33333333%
}
.col-md-offset-9 {
margin-left: 75%
}
.col-md-offset-8 {
margin-left: 66.66666667%
}
.col-md-offset-7 {
margin-left: 58.33333333%
}
.col-md-offset-6 {
margin-left: 50%
}
.col-md-offset-5 {
margin-left: 41.66666667%
}
.col-md-offset-4 {
margin-left: 33.33333333%
}
.col-md-offset-3 {
margin-left: 25%
}
.col-md-offset-2 {
margin-left: 16.66666667%
}
.col-md-offset-1 {
margin-left: 8.33333333%
}
.col-md-offset-0 {
margin-left: 0
}
}
@media (min-width:1200px) {
.col-lg-1, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9 {
float: left
}
.col-lg-12 {
width: 100%
}
.col-lg-11 {
width: 91.66666667%
}
.col-lg-10 {
width: 83.33333333%
}
.col-lg-9 {
width: 75%
}
.col-lg-8 {
width: 66.66666667%
}
.col-lg-7 {
width: 58.33333333%
}
.col-lg-6 {
width: 50%
}
.col-lg-5 {
width: 41.66666667%
}
.col-lg-4 {
width: 33.33333333%
}
.col-lg-3 {
width: 25%
}
.col-lg-2 {
width: 16.66666667%
}
.col-lg-1 {
width: 8.33333333%
}
.col-lg-pull-12 {
right: 100%
}
.col-lg-pull-11 {
right: 91.66666667%
}
.col-lg-pull-10 {
right: 83.33333333%
}
.col-lg-pull-9 {
right: 75%
}
.col-lg-pull-8 {
right: 66.66666667%
}
.col-lg-pull-7 {
right: 58.33333333%
}
.col-lg-pull-6 {
right: 50%
}
.col-lg-pull-5 {
right: 41.66666667%
}
.col-lg-pull-4 {
right: 33.33333333%
}
.col-lg-pull-3 {
right: 25%
}
.col-lg-pull-2 {
right: 16.66666667%
}
.col-lg-pull-1 {
right: 8.33333333%
}
.col-lg-pull-0 {
right: auto
}
.col-lg-push-12 {
left: 100%
}
.col-lg-push-11 {
left: 91.66666667%
}
.col-lg-push-10 {
left: 83.33333333%
}
.col-lg-push-9 {
left: 75%
}
.col-lg-push-8 {
left: 66.66666667%
}
.col-lg-push-7 {
left: 58.33333333%
}
.col-lg-push-6 {
left: 50%
}
.col-lg-push-5 {
left: 41.66666667%
}
.col-lg-push-4 {
left: 33.33333333%
}
.col-lg-push-3 {
left: 25%
}
.col-lg-push-2 {
left: 16.66666667%
}
.col-lg-push-1 {
left: 8.33333333%
}
.col-lg-push-0 {
left: auto
}
.col-lg-offset-12 {
margin-left: 100%
}
.col-lg-offset-11 {
margin-left: 91.66666667%
}
.col-lg-offset-10 {
margin-left: 83.33333333%
}
.col-lg-offset-9 {
margin-left: 75%
}
.col-lg-offset-8 {
margin-left: 66.66666667%
}
.col-lg-offset-7 {
margin-left: 58.33333333%
}
.col-lg-offset-6 {
margin-left: 50%
}
.col-lg-offset-5 {
margin-left: 41.66666667%
}
.col-lg-offset-4 {
margin-left: 33.33333333%
}
.col-lg-offset-3 {
margin-left: 25%
}
.col-lg-offset-2 {
margin-left: 16.66666667%
}
.col-lg-offset-1 {
margin-left: 8.33333333%
}
.col-lg-offset-0 {
margin-left: 0
}
}
table {
border-collapse: collapse;
border-spacing: 0;
background-color: transparent
}
table col[class*=col-] {
position: static;
display: table-column;
float: none
}
table td[class*=col-], table th[class*=col-] {
position: static;
display: table-cell;
float: none
}
caption {
padding-top: 8px;
padding-bottom: 8px;
color: #777;
text-align: left
}
th {
text-align: left
}
.table {
width: 100%;
max-width: 100%;
margin-bottom: 20px
}
.table > tbody > tr > td, .table > tbody > tr > th, .table > tfoot > tr > td, .table > tfoot > tr > th, .table > thead > tr > td, .table > thead > tr > th {
padding: 8px;
line-height: 1.42857143;
vertical-align: top;
border-top: 1px solid #ddd
}
.table > thead > tr > th {
vertical-align: bottom;
border-bottom: 2px solid #ddd
}
.table > caption + thead > tr:first-child > td, .table > caption + thead > tr:first-child > th, .table > colgroup + thead > tr:first-child > td, .table > colgroup + thead > tr:first-child > th, .table > thead:first-child > tr:first-child > td, .table > thead:first-child > tr:first-child > th {
border-top: 0
}
.table > tbody + tbody {
border-top: 2px solid #ddd
}
.table .table {
background-color: #fff
}
.table-condensed > tbody > tr > td, .table-condensed > tbody > tr > th, .table-condensed > tfoot > tr > td, .table-condensed > tfoot > tr > th, .table-condensed > thead > tr > td, .table-condensed > thead > tr > th {
padding: 5px
}
.table-bordered, .table-bordered > tbody > tr > td, .table-bordered > tbody > tr > th, .table-bordered > tfoot > tr > td, .table-bordered > tfoot > tr > th, .table-bordered > thead > tr > td, .table-bordered > thead > tr > th {
border: 1px solid #ddd
}
.table-bordered > thead > tr > td, .table-bordered > thead > tr > th {
border-bottom-width: 2px
}
.table-striped > tbody > tr:nth-of-type(odd) {
background-color: #f9f9f9
}
.table-hover > tbody > tr:hover, .table > tbody > tr.active > td, .table > tbody > tr.active > th, .table > tbody > tr > td.active, .table > tbody > tr > th.active, .table > tfoot > tr.active > td, .table > tfoot > tr.active > th, .table > tfoot > tr > td.active, .table > tfoot > tr > th.active, .table > thead > tr.active > td, .table > thead > tr.active > th, .table > thead > tr > td.active, .table > thead > tr > th.active {
background-color: #f5f5f5
}
.table-hover > tbody > tr.active:hover > td, .table-hover > tbody > tr.active:hover > th, .table-hover > tbody > tr:hover > .active, .table-hover > tbody > tr > td.active:hover, .table-hover > tbody > tr > th.active:hover {
background-color: #e8e8e8
}
.table > tbody > tr.success > td, .table > tbody > tr.success > th, .table > tbody > tr > td.success, .table > tbody > tr > th.success, .table > tfoot > tr.success > td, .table > tfoot > tr.success > th, .table > tfoot > tr > td.success, .table > tfoot > tr > th.success, .table > thead > tr.success > td, .table > thead > tr.success > th, .table > thead > tr > td.success, .table > thead > tr > th.success {
background-color: #dff0d8
}
.table-hover > tbody > tr.success:hover > td, .table-hover > tbody > tr.success:hover > th, .table-hover > tbody > tr:hover > .success, .table-hover > tbody > tr > td.success:hover, .table-hover > tbody > tr > th.success:hover {
background-color: #d0e9c6
}
.table > tbody > tr.info > td, .table > tbody > tr.info > th, .table > tbody > tr > td.info, .table > tbody > tr > th.info, .table > tfoot > tr.info > td, .table > tfoot > tr.info > th, .table > tfoot > tr > td.info, .table > tfoot > tr > th.info, .table > thead > tr.info > td, .table > thead > tr.info > th, .table > thead > tr > td.info, .table > thead > tr > th.info {
background-color: #d9edf7
}
.table-hover > tbody > tr.info:hover > td, .table-hover > tbody > tr.info:hover > th, .table-hover > tbody > tr:hover > .info, .table-hover > tbody > tr > td.info:hover, .table-hover > tbody > tr > th.info:hover {
background-color: #c4e3f3
}
.table > tbody > tr.warning > td, .table > tbody > tr.warning > th, .table > tbody > tr > td.warning, .table > tbody > tr > th.warning, .table > tfoot > tr.warning > td, .table > tfoot > tr.warning > th, .table > tfoot > tr > td.warning, .table > tfoot > tr > th.warning, .table > thead > tr.warning > td, .table > thead > tr.warning > th, .table > thead > tr > td.warning, .table > thead > tr > th.warning {
background-color: #fcf8e3
}
.table-hover > tbody > tr.warning:hover > td, .table-hover > tbody > tr.warning:hover > th, .table-hover > tbody > tr:hover > .warning, .table-hover > tbody > tr > td.warning:hover, .table-hover > tbody > tr > th.warning:hover {
background-color: #faf2cc
}
.table > tbody > tr.danger > td, .table > tbody > tr.danger > th, .table > tbody > tr > td.danger, .table > tbody > tr > th.danger, .table > tfoot > tr.danger > td, .table > tfoot > tr.danger > th, .table > tfoot > tr > td.danger, .table > tfoot > tr > th.danger, .table > thead > tr.danger > td, .table > thead > tr.danger > th, .table > thead > tr > td.danger, .table > thead > tr > th.danger {
background-color: #f2dede
}
.table-hover > tbody > tr.danger:hover > td, .table-hover > tbody > tr.danger:hover > th, .table-hover > tbody > tr:hover > .danger, .table-hover > tbody > tr > td.danger:hover, .table-hover > tbody > tr > th.danger:hover {
background-color: #ebcccc
}
.table-responsive {
min-height: .01%;
overflow-x: auto
}
@media screen and (max-width:767px) {
.table-responsive {
width: 100%;
margin-bottom: 15px;
overflow-y: hidden;
-ms-overflow-style: -ms-autohiding-scrollbar;
border: 1px solid #ddd
}
.table-responsive > .table {
margin-bottom: 0
}
.table-responsive > .table > tbody > tr > td, .table-responsive > .table > tbody > tr > th, .table-responsive > .table > tfoot > tr > td, .table-responsive > .table > tfoot > tr > th, .table-responsive > .table > thead > tr > td, .table-responsive > .table > thead > tr > th {
white-space: nowrap
}
.table-responsive > .table-bordered {
border: 0
}
.table-responsive > .table-bordered > tbody > tr > td:first-child, .table-responsive > .table-bordered > tbody > tr > th:first-child, .table-responsive > .table-bordered > tfoot > tr > td:first-child, .table-responsive > .table-bordered > tfoot > tr > th:first-child, .table-responsive > .table-bordered > thead > tr > td:first-child, .table-responsive > .table-bordered > thead > tr > th:first-child {
border-left: 0
}
.table-responsive > .table-bordered > tbody > tr > td:last-child, .table-responsive > .table-bordered > tbody > tr > th:last-child, .table-responsive > .table-bordered > tfoot > tr > td:last-child, .table-responsive > .table-bordered > tfoot > tr > th:last-child, .table-responsive > .table-bordered > thead > tr > td:last-child, .table-responsive > .table-bordered > thead > tr > th:last-child {
border-right: 0
}
.table-responsive > .table-bordered > tbody > tr:last-child > td, .table-responsive > .table-bordered > tbody > tr:last-child > th, .table-responsive > .table-bordered > tfoot > tr:last-child > td, .table-responsive > .table-bordered > tfoot > tr:last-child > th {
border-bottom: 0
}
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0
}
legend {
display: block;
width: 100%;
padding: 0;
margin-bottom: 20px;
font-size: 21px;
line-height: inherit;
color: #333;
border: 0;
border-bottom: 1px solid #e5e5e5
}
label {
display: inline-block;
max-width: 100%;
margin-bottom: 5px;
font-weight: 700
}
input[type=search] {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none
}
input[type=checkbox], input[type=radio] {
margin: 4px 0 0;
line-height: normal
}
fieldset[disabled] input[type=checkbox], fieldset[disabled] input[type=radio], input[type=checkbox].disabled, input[type=checkbox][disabled], input[type=radio].disabled, input[type=radio][disabled] {
cursor: not-allowed
}
input[type=file] {
display: block
}
input[type=range] {
display: block;
width: 100%
}
select[multiple], select[size] {
height: auto
}
input[type=checkbox]:focus, input[type=file]:focus, input[type=radio]:focus {
outline: -webkit-focus-ring-color auto 5px;
outline-offset: -2px
}
output {
display: block;
padding-top: 7px;
font-size: 14px;
line-height: 1.42857143;
color: #555
}
.form-control {
display: block;
width: 100%;
height: 34px;
padding: 6px 12px;
font-size: 14px;
line-height: 1.42857143;
color: #555;
background-color: #fff;
background-image: none;
border: 1px solid #ccc;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
-webkit-transition: border-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;
-o-transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-box-shadow .15s ease-in-out
}
.form-control:focus {
border-color: #66afe9;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)
}
.form-control::-moz-placeholder {
color: #999;
opacity: 1
}
.form-control:-ms-input-placeholder {
color: #999
}
.form-control::-webkit-input-placeholder {
color: #999
}
.form-control::-ms-expand {
background-color: transparent;
border: 0
}
.form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control {
background-color: #eee;
opacity: 1
}
.form-control[disabled], fieldset[disabled] .form-control {
cursor: not-allowed
}
textarea.form-control {
height: auto
}
@media screen and (-webkit-min-device-pixel-ratio:0) {
input[type=date].form-control, input[type=datetime-local].form-control, input[type=month].form-control, input[type=time].form-control {
line-height: 34px
}
.input-group-sm input[type=date], .input-group-sm input[type=datetime-local], .input-group-sm input[type=month], .input-group-sm input[type=time], input[type=date].input-sm, input[type=datetime-local].input-sm, input[type=month].input-sm, input[type=time].input-sm {
line-height: 30px
}
.input-group-lg input[type=date], .input-group-lg input[type=datetime-local], .input-group-lg input[type=month], .input-group-lg input[type=time], input[type=date].input-lg, input[type=datetime-local].input-lg, input[type=month].input-lg, input[type=time].input-lg {
line-height: 46px
}
}
.form-group {
margin-bottom: 15px
}
.checkbox, .radio {
position: relative;
display: block;
margin-top: 10px;
margin-bottom: 10px
}
.checkbox.disabled label, .radio.disabled label, fieldset[disabled] .checkbox label, fieldset[disabled] .radio label {
cursor: not-allowed
}
.checkbox label, .radio label {
min-height: 20px;
padding-left: 20px;
margin-bottom: 0;
font-weight: 400;
cursor: pointer
}
.checkbox input[type=checkbox], .checkbox-inline input[type=checkbox], .radio input[type=radio], .radio-inline input[type=radio] {
position: absolute;
margin-left: -20px
}
.checkbox + .checkbox, .radio + .radio {
margin-top: -5px
}
.checkbox-inline, .radio-inline {
position: relative;
display: inline-block;
padding-left: 20px;
margin-bottom: 0;
font-weight: 400;
vertical-align: middle;
cursor: pointer
}
.checkbox-inline.disabled, .radio-inline.disabled, fieldset[disabled] .checkbox-inline, fieldset[disabled] .radio-inline {
cursor: not-allowed
}
.checkbox-inline + .checkbox-inline, .radio-inline + .radio-inline {
margin-top: 0;
margin-left: 10px
}
.form-control-static {
min-height: 34px;
padding-top: 7px;
padding-bottom: 7px;
margin-bottom: 0
}
.form-control-static.input-lg, .form-control-static.input-sm {
padding-right: 0;
padding-left: 0
}
.input-sm {
height: 30px;
padding: 5px 10px;
font-size: 12px;
line-height: 1.5;
border-radius: 3px
}
select.input-sm {
height: 30px;
line-height: 30px
}
select[multiple].input-sm, textarea.input-sm {
height: auto
}
.form-group-sm .form-control {
height: 30px;
padding: 5px 10px;
font-size: 12px;
line-height: 1.5;
border-radius: 3px
}
.form-group-sm select.form-control {
height: 30px;
line-height: 30px
}
.form-group-sm select[multiple].form-control, .form-group-sm textarea.form-control {
height: auto
}
.form-group-sm .form-control-static {
height: 30px;
min-height: 32px;
padding: 6px 10px;
font-size: 12px;
line-height: 1.5
}
.input-lg {
height: 46px;
padding: 10px 16px;
font-size: 18px;
line-height: 1.3333333;
border-radius: 6px
}
select.input-lg {
height: 46px;
line-height: 46px
}
select[multiple].input-lg, textarea.input-lg {
height: auto
}
.form-group-lg .form-control {
height: 46px;
padding: 10px 16px;
font-size: 18px;
line-height: 1.3333333;
border-radius: 6px
}
.form-group-lg select.form-control {
height: 46px;
line-height: 46px
}
.form-group-lg select[multiple].form-control, .form-group-lg textarea.form-control {
height: auto
}
.form-group-lg .form-control-static {
height: 46px;
min-height: 38px;
padding: 11px 16px;
font-size: 18px;
line-height: 1.3333333
}
.has-feedback {
position: relative
}
.has-feedback .form-control {
padding-right: 42.5px
}
.form-control-feedback {
position: absolute;
top: 0;
right: 0;
z-index: 2;
display: block;
width: 34px;
height: 34px;
line-height: 34px;
text-align: center;
pointer-events: none
}
.form-group-lg .form-control + .form-control-feedback, .input-group-lg + .form-control-feedback, .input-lg + .form-control-feedback {
width: 46px;
height: 46px;
line-height: 46px
}
.form-group-sm .form-control + .form-control-feedback, .input-group-sm + .form-control-feedback, .input-sm + .form-control-feedback {
width: 30px;
height: 30px;
line-height: 30px
}
.has-success .checkbox, .has-success .checkbox-inline, .has-success .control-label, .has-success .help-block, .has-success .radio, .has-success .radio-inline, .has-success.checkbox label, .has-success.checkbox-inline label, .has-success.radio label, .has-success.radio-inline label {
color: #3c763d
}
.has-success .form-control {
border-color: #3c763d;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075)
}
.has-success .form-control:focus {
border-color: #2b542c;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;
box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168
}
.has-success .input-group-addon {
color: #3c763d;
background-color: #dff0d8;
border-color: #3c763d
}
.has-success .form-control-feedback {
color: #3c763d
}
.has-warning .checkbox, .has-warning .checkbox-inline, .has-warning .control-label, .has-warning .help-block, .has-warning .radio, .has-warning .radio-inline, .has-warning.checkbox label, .has-warning.checkbox-inline label, .has-warning.radio label, .has-warning.radio-inline label {
color: #8a6d3b
}
.has-warning .form-control {
border-color: #8a6d3b;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075)
}
.has-warning .form-control:focus {
border-color: #66512c;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;
box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b
}
.has-warning .input-group-addon {
color: #8a6d3b;
background-color: #fcf8e3;
border-color: #8a6d3b
}
.has-warning .form-control-feedback {
color: #8a6d3b
}
.has-error .checkbox, .has-error .checkbox-inline, .has-error .control-label, .has-error .help-block, .has-error .radio, .has-error .radio-inline, .has-error.checkbox label, .has-error.checkbox-inline label, .has-error.radio label, .has-error.radio-inline label {
color: #a94442
}
.has-error .form-control {
border-color: #a94442;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075)
}
.has-error .form-control:focus {
border-color: #843534;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;
box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483
}
.has-error .input-group-addon {
color: #a94442;
background-color: #f2dede;
border-color: #a94442
}
.has-error .form-control-feedback {
color: #a94442
}
.has-feedback label ~ .form-control-feedback {
top: 25px
}
.has-feedback label.sr-only ~ .form-control-feedback {
top: 0
}
.help-block {
display: block;
margin-top: 5px;
margin-bottom: 10px;
color: #737373
}
.form-horizontal .checkbox, .form-horizontal .checkbox-inline, .form-horizontal .radio, .form-horizontal .radio-inline {
padding-top: 7px;
margin-top: 0;
margin-bottom: 0
}
.form-horizontal .checkbox, .form-horizontal .radio {
min-height: 27px
}
.form-horizontal .form-group {
margin-right: -15px;
margin-left: -15px
}
.form-horizontal .has-feedback .form-control-feedback {
right: 15px
}
@media (min-width:768px) {
.form-inline .form-group {
display: inline-block;
margin-bottom: 0;
vertical-align: middle
}
.form-inline .form-control {
display: inline-block;
width: auto;
vertical-align: middle
}
.form-inline .form-control-static {
display: inline-block
}
.form-inline .input-group {
display: inline-table;
vertical-align: middle
}
.form-inline .input-group .form-control, .form-inline .input-group .input-group-addon, .form-inline .input-group .input-group-btn {
width: auto
}
.form-inline .input-group > .form-control {
width: 100%
}
.form-inline .control-label {
margin-bottom: 0;
vertical-align: middle
}
.form-inline .checkbox, .form-inline .radio {
display: inline-block;
margin-top: 0;
margin-bottom: 0;
vertical-align: middle
}
.form-inline .checkbox label, .form-inline .radio label {
padding-left: 0
}
.form-inline .checkbox input[type=checkbox], .form-inline .radio input[type=radio] {
position: relative;
margin-left: 0
}
.form-inline .has-feedback .form-control-feedback {
top: 0
}
.form-horizontal .control-label {
padding-top: 7px;
margin-bottom: 0;
text-align: right
}
.form-horizontal .form-group-lg .control-label {
padding-top: 11px;
font-size: 18px
}
.form-horizontal .form-group-sm .control-label {
padding-top: 6px;
font-size: 12px
}
}
.btn {
display: inline-block;
margin-bottom: 0;
font-weight: 400;
text-align: center;
white-space: nowrap;
vertical-align: middle;
-ms-touch-action: manipulation;
touch-action: manipulation;
cursor: pointer;
background-image: none;
border: 1px solid transparent;
padding: 6px 12px;
font-size: 14px;
line-height: 1.42857143;
border-radius: 4px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none
}
.btn.active.focus, .btn.active:focus, .btn.focus, .btn:active.focus, .btn:active:focus, .btn:focus {
outline: -webkit-focus-ring-color auto 5px;
outline-offset: -2px
}
.btn.focus, .btn:focus, .btn:hover {
color: #333;
text-decoration: none
}
.btn.active, .btn:active {
background-image: none;
outline: 0;
-webkit-box-shadow: inset 0 3px 5px rgba(0,0,0,.125);
box-shadow: inset 0 3px 5px rgba(0,0,0,.125)
}
.btn.disabled, .btn[disabled], fieldset[disabled] .btn {
cursor: not-allowed;
opacity: .65;
-webkit-box-shadow: none;
box-shadow: none
}
a.btn.disabled, fieldset[disabled] a.btn {
pointer-events: none
}
.btn-default {
color: #333;
background-color: #fff;
border-color: #ccc
}
.btn-default.focus, .btn-default:focus {
color: #333;
background-color: #e6e6e6;
border-color: #8c8c8c
}
.btn-default:hover {
color: #333;
background-color: #e6e6e6;
border-color: #adadad
}
.btn-default.active, .btn-default:active, .open > .dropdown-toggle.btn-default {
color: #333;
background-color: #e6e6e6;
background-image: none;
border-color: #adadad
}
.btn-default.active.focus, .btn-default.active:focus, .btn-default.active:hover, .btn-default:active.focus, .btn-default:active:focus, .btn-default:active:hover, .open > .dropdown-toggle.btn-default.focus, .open > .dropdown-toggle.btn-default:focus, .open > .dropdown-toggle.btn-default:hover {
color: #333;
background-color: #d4d4d4;
border-color: #8c8c8c
}
.btn-default.disabled.focus, .btn-default.disabled:focus, .btn-default.disabled:hover, .btn-default[disabled].focus, .btn-default[disabled]:focus, .btn-default[disabled]:hover, fieldset[disabled] .btn-default.focus, fieldset[disabled] .btn-default:focus, fieldset[disabled] .btn-default:hover {
background-color: #fff;
border-color: #ccc
}
.btn-default .badge {
color: #fff;
background-color: #333
}
.btn-primary {
color: #fff;
background-color: #337ab7;
border-color: #2e6da4
}
.btn-primary.focus, .btn-primary:focus {
color: #fff;
background-color: #286090;
border-color: #122b40
}
.btn-primary:hover {
color: #fff;
background-color: #286090;
border-color: #204d74
}
.btn-primary.active, .btn-primary:active, .open > .dropdown-toggle.btn-primary {
color: #fff;
background-color: #286090;
background-image: none;
border-color: #204d74
}
.btn-primary.active.focus, .btn-primary.active:focus, .btn-primary.active:hover, .btn-primary:active.focus, .btn-primary:active:focus, .btn-primary:active:hover, .open > .dropdown-toggle.btn-primary.focus, .open > .dropdown-toggle.btn-primary:focus, .open > .dropdown-toggle.btn-primary:hover {
color: #fff;
background-color: #204d74;
border-color: #122b40
}
.btn-primary.disabled.focus, .btn-primary.disabled:focus, .btn-primary.disabled:hover, .btn-primary[disabled].focus, .btn-primary[disabled]:focus, .btn-primary[disabled]:hover, fieldset[disabled] .btn-primary.focus, fieldset[disabled] .btn-primary:focus, fieldset[disabled] .btn-primary:hover {
background-color: #337ab7;
border-color: #2e6da4
}
.btn-primary .badge {
color: #337ab7;
background-color: #fff
}
.btn-success {
color: #fff;
background-color: #5cb85c;
border-color: #4cae4c
}
.btn-success.focus, .btn-success:focus {
color: #fff;
background-color: #449d44;
border-color: #255625
}
.btn-success:hover {
color: #fff;
background-color: #449d44;
border-color: #398439
}
.btn-success.active, .btn-success:active, .open > .dropdown-toggle.btn-success {
color: #fff;
background-color: #449d44;
background-image: none;
border-color: #398439
}
.btn-success.active.focus, .btn-success.active:focus, .btn-success.active:hover, .btn-success:active.focus, .btn-success:active:focus, .btn-success:active:hover, .open > .dropdown-toggle.btn-success.focus, .open > .dropdown-toggle.btn-success:focus, .open > .dropdown-toggle.btn-success:hover {
color: #fff;
background-color: #398439;
border-color: #255625
}
.btn-success.disabled.focus, .btn-success.disabled:focus, .btn-success.disabled:hover, .btn-success[disabled].focus, .btn-success[disabled]:focus, .btn-success[disabled]:hover, fieldset[disabled] .btn-success.focus, fieldset[disabled] .btn-success:focus, fieldset[disabled] .btn-success:hover {
background-color: #5cb85c;
border-color: #4cae4c
}
.btn-success .badge {
color: #5cb85c;
background-color: #fff
}
.btn-info {
color: #fff;
background-color: #5bc0de;
border-color: #46b8da
}
.btn-info.focus, .btn-info:focus {
color: #fff;
background-color: #31b0d5;
border-color: #1b6d85
}
.btn-info:hover {
color: #fff;
background-color: #31b0d5;
border-color: #269abc
}
.btn-info.active, .btn-info:active, .open > .dropdown-toggle.btn-info {
color: #fff;
background-color: #31b0d5;
background-image: none;
border-color: #269abc
}
.btn-info.active.focus, .btn-info.active:focus, .btn-info.active:hover, .btn-info:active.focus, .btn-info:active:focus, .btn-info:active:hover, .open > .dropdown-toggle.btn-info.focus, .open > .dropdown-toggle.btn-info:focus, .open > .dropdown-toggle.btn-info:hover {
color: #fff;
background-color: #269abc;
border-color: #1b6d85
}
.btn-info.disabled.focus, .btn-info.disabled:focus, .btn-info.disabled:hover, .btn-info[disabled].focus, .btn-info[disabled]:focus, .btn-info[disabled]:hover, fieldset[disabled] .btn-info.focus, fieldset[disabled] .btn-info:focus, fieldset[disabled] .btn-info:hover {
background-color: #5bc0de;
border-color: #46b8da
}
.btn-info .badge {
color: #5bc0de;
background-color: #fff
}
.btn-warning {
color: #fff;
background-color: #f0ad4e;
border-color: #eea236
}
.btn-warning.focus, .btn-warning:focus {
color: #fff;
background-color: #ec971f;
border-color: #985f0d
}
.btn-warning:hover {
color: #fff;
background-color: #ec971f;
border-color: #d58512
}
.btn-warning.active, .btn-warning:active, .open > .dropdown-toggle.btn-warning {
color: #fff;
background-color: #ec971f;
background-image: none;
border-color: #d58512
}
.btn-warning.active.focus, .btn-warning.active:focus, .btn-warning.active:hover, .btn-warning:active.focus, .btn-warning:active:focus, .btn-warning:active:hover, .open > .dropdown-toggle.btn-warning.focus, .open > .dropdown-toggle.btn-warning:focus, .open > .dropdown-toggle.btn-warning:hover {
color: #fff;
background-color: #d58512;
border-color: #985f0d
}
.btn-warning.disabled.focus, .btn-warning.disabled:focus, .btn-warning.disabled:hover, .btn-warning[disabled].focus, .btn-warning[disabled]:focus, .btn-warning[disabled]:hover, fieldset[disabled] .btn-warning.focus, fieldset[disabled] .btn-warning:focus, fieldset[disabled] .btn-warning:hover {
background-color: #f0ad4e;
border-color: #eea236
}
.btn-warning .badge {
color: #f0ad4e;
background-color: #fff
}
.btn-danger {
color: #fff;
background-color: #d9534f;
border-color: #d43f3a
}
.btn-danger.focus, .btn-danger:focus {
color: #fff;
background-color: #c9302c;
border-color: #761c19
}
.btn-danger:hover {
color: #fff;
background-color: #c9302c;
border-color: #ac2925
}
.btn-danger.active, .btn-danger:active, .open > .dropdown-toggle.btn-danger {
color: #fff;
background-color: #c9302c;
background-image: none;
border-color: #ac2925
}
.btn-danger.active.focus, .btn-danger.active:focus, .btn-danger.active:hover, .btn-danger:active.focus, .btn-danger:active:focus, .btn-danger:active:hover, .open > .dropdown-toggle.btn-danger.focus, .open > .dropdown-toggle.btn-danger:focus, .open > .dropdown-toggle.btn-danger:hover {
color: #fff;
background-color: #ac2925;
border-color: #761c19
}
.btn-danger.disabled.focus, .btn-danger.disabled:focus, .btn-danger.disabled:hover, .btn-danger[disabled].focus, .btn-danger[disabled]:focus, .btn-danger[disabled]:hover, fieldset[disabled] .btn-danger.focus, fieldset[disabled] .btn-danger:focus, fieldset[disabled] .btn-danger:hover {
background-color: #d9534f;
border-color: #d43f3a
}
.btn-danger .badge {
color: #d9534f;
background-color: #fff
}
.btn-link {
font-weight: 400;
color: #337ab7;
border-radius: 0
}
.btn-link, .btn-link.active, .btn-link:active, .btn-link[disabled], fieldset[disabled] .btn-link {
background-color: transparent;
-webkit-box-shadow: none;
box-shadow: none
}
.btn-link, .btn-link:active, .btn-link:focus, .btn-link:hover {
border-color: transparent
}
.btn-link:focus, .btn-link:hover {
color: #23527c;
text-decoration: underline;
background-color: transparent
}
.btn-link[disabled]:focus, .btn-link[disabled]:hover, fieldset[disabled] .btn-link:focus, fieldset[disabled] .btn-link:hover {
color: #777;
text-decoration: none
}
.btn-group-lg > .btn, .btn-lg {
padding: 10px 16px;
font-size: 18px;
line-height: 1.3333333;
border-radius: 6px
}
.btn-group-sm > .btn, .btn-sm {
padding: 5px 10px;
font-size: 12px;
line-height: 1.5;
border-radius: 3px
}
.btn-group-xs > .btn, .btn-xs {
padding: 1px 5px;
font-size: 12px;
line-height: 1.5;
border-radius: 3px
}
.btn-block {
display: block;
width: 100%
}
.btn-block + .btn-block {
margin-top: 5px
}
input[type=button].btn-block, input[type=reset].btn-block, input[type=submit].btn-block {
width: 100%
}
.fade {
opacity: 0;
-webkit-transition: opacity .15s linear;
-o-transition: opacity .15s linear;
transition: opacity .15s linear
}
.fade.in {
opacity: 1
}
.collapse {
display: none
}
.collapse.in {
display: block
}
tr.collapse.in {
display: table-row
}
tbody.collapse.in {
display: table-row-group
}
.collapsing {
position: relative;
height: 0;
overflow: hidden;
-webkit-transition-property: height,visibility;
-o-transition-property: height,visibility;
transition-property: height,visibility;
-webkit-transition-duration: .35s;
-o-transition-duration: .35s;
transition-duration: .35s;
-webkit-transition-timing-function: ease;
-o-transition-timing-function: ease;
transition-timing-function: ease
}
.caret {
display: inline-block;
width: 0;
height: 0;
margin-left: 2px;
vertical-align: middle;
border-top: 4px dashed;
border-right: 4px solid transparent;
border-left: 4px solid transparent
}
.dropdown, .dropup {
position: relative
}
.dropdown-toggle:focus {
outline: 0
}
.dropdown-menu {
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
display: none;
float: left;
min-width: 160px;
padding: 5px 0;
margin: 2px 0 0;
font-size: 14px;
text-align: left;
list-style: none;
background-color: #fff;
background-clip: padding-box;
border: 1px solid rgba(0,0,0,.15);
border-radius: 4px;
-webkit-box-shadow: 0 6px 12px rgba(0,0,0,.175);
box-shadow: 0 6px 12px rgba(0,0,0,.175)
}
.dropdown-menu.pull-right {
right: 0;
left: auto
}
.dropdown-menu .divider {
height: 1px;
margin: 9px 0;
overflow: hidden;
background-color: #e5e5e5
}
.dropdown-menu > li > a {
display: block;
padding: 3px 20px;
clear: both;
font-weight: 400;
line-height: 1.42857143;
color: #333;
white-space: nowrap
}
.dropdown-menu > li > a:focus, .dropdown-menu > li > a:hover {
color: #262626;
text-decoration: none;
background-color: #f5f5f5
}
.dropdown-menu > .active > a, .dropdown-menu > .active > a:focus, .dropdown-menu > .active > a:hover {
color: #fff;
text-decoration: none;
background-color: #337ab7;
outline: 0
}
.dropdown-menu > .disabled > a, .dropdown-menu > .disabled > a:focus, .dropdown-menu > .disabled > a:hover {
color: #777
}
.dropdown-menu > .disabled > a:focus, .dropdown-menu > .disabled > a:hover {
text-decoration: none;
cursor: not-allowed;
background-color: transparent;
background-image: none
}
.open > .dropdown-menu {
display: block
}
.open > a {
outline: 0
}
.dropdown-menu-right {
right: 0;
left: auto
}
.dropdown-menu-left {
right: auto;
left: 0
}
.dropdown-header {
display: block;
padding: 3px 20px;
font-size: 12px;
line-height: 1.42857143;
color: #777;
white-space: nowrap
}
.dropdown-backdrop {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 990
}
.pull-right > .dropdown-menu {
right: 0;
left: auto
}
.dropup .caret, .navbar-fixed-bottom .dropdown .caret {
content: "";
border-top: 0;
border-bottom: 4px dashed
}
.dropup .dropdown-menu, .navbar-fixed-bottom .dropdown .dropdown-menu {
top: auto;
bottom: 100%;
margin-bottom: 2px
}
.btn-group, .btn-group-vertical {
position: relative;
display: inline-block;
vertical-align: middle
}
.btn-group-vertical > .btn, .btn-group > .btn {
position: relative;
float: left
}
.btn-group-vertical > .btn.active, .btn-group-vertical > .btn:active, .btn-group-vertical > .btn:focus, .btn-group-vertical > .btn:hover, .btn-group > .btn.active, .btn-group > .btn:active, .btn-group > .btn:focus, .btn-group > .btn:hover {
z-index: 2
}
.btn-group .btn + .btn, .btn-group .btn + .btn-group, .btn-group .btn-group + .btn, .btn-group .btn-group + .btn-group {
margin-left: -1px
}
.btn-toolbar {
margin-left: -5px
}
.btn-toolbar .btn, .btn-toolbar .btn-group, .btn-toolbar .input-group {
float: left
}
.btn-toolbar > .btn, .btn-toolbar > .btn-group, .btn-toolbar > .input-group {
margin-left: 5px
}
.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {
border-radius: 0
}
.btn-group > .btn:first-child {
margin-left: 0
}
.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {
border-top-right-radius: 0;
border-bottom-right-radius: 0
}
.btn-group > .btn:last-child:not(:first-child), .btn-group > .dropdown-toggle:not(:first-child) {
border-top-left-radius: 0;
border-bottom-left-radius: 0
}
.btn-group > .btn-group {
float: left
}
.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {
border-radius: 0
}
.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child, .btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle {
border-top-right-radius: 0;
border-bottom-right-radius: 0
}
.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {
border-top-left-radius: 0;
border-bottom-left-radius: 0
}
.btn-group .dropdown-toggle:active, .btn-group.open .dropdown-toggle {
outline: 0
}
.btn-group > .btn + .dropdown-toggle {
padding-right: 8px;
padding-left: 8px
}
.btn-group > .btn-lg + .dropdown-toggle {
padding-right: 12px;
padding-left: 12px
}
.btn-group.open .dropdown-toggle {
-webkit-box-shadow: inset 0 3px 5px rgba(0,0,0,.125);
box-shadow: inset 0 3px 5px rgba(0,0,0,.125)
}
.btn-group.open .dropdown-toggle.btn-link {
-webkit-box-shadow: none;
box-shadow: none
}
.btn .caret {
margin-left: 0
}
.btn-lg .caret {
border-width: 5px 5px 0
}
.dropup .btn-lg .caret {
border-width: 0 5px 5px
}
.btn-group-vertical > .btn, .btn-group-vertical > .btn-group, .btn-group-vertical > .btn-group > .btn {
display: block;
float: none;
width: 100%;
max-width: 100%
}
.btn-group-vertical > .btn-group > .btn {
float: none
}
.btn-group-vertical > .btn + .btn, .btn-group-vertical > .btn + .btn-group, .btn-group-vertical > .btn-group + .btn, .btn-group-vertical > .btn-group + .btn-group {
margin-top: -1px;
margin-left: 0
}
.btn-group-vertical > .btn:not(:first-child):not(:last-child) {
border-radius: 0
}
.btn-group-vertical > .btn:first-child:not(:last-child) {
border-radius: 4px 4px 0 0
}
.btn-group-vertical > .btn:last-child:not(:first-child) {
border-radius: 0 0 4px 4px
}
.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {
border-radius: 0
}
.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, .btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0
}
.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {
border-top-left-radius: 0;
border-top-right-radius: 0
}
.btn-group-justified {
display: table;
width: 100%;
table-layout: fixed;
border-collapse: separate
}
.btn-group-justified > .btn, .btn-group-justified > .btn-group {
display: table-cell;
float: none;
width: 1%
}
.btn-group-justified > .btn-group .btn {
width: 100%
}
.btn-group-justified > .btn-group .dropdown-menu {
left: auto
}
[data-toggle=buttons] > .btn input[type=checkbox], [data-toggle=buttons] > .btn input[type=radio], [data-toggle=buttons] > .btn-group > .btn input[type=checkbox], [data-toggle=buttons] > .btn-group > .btn input[type=radio] {
position: absolute;
clip: rect(0,0,0,0);
pointer-events: none
}
.input-group {
position: relative;
display: table;
border-collapse: separate
}
.input-group[class*=col-] {
float: none;
padding-right: 0;
padding-left: 0
}
.input-group .form-control {
position: relative;
z-index: 2;
float: left;
width: 100%;
margin-bottom: 0
}
.input-group .form-control:focus {
z-index: 3
}
.input-group-lg > .form-control, .input-group-lg > .input-group-addon, .input-group-lg > .input-group-btn > .btn {
height: 46px;
padding: 10px 16px;
font-size: 18px;
line-height: 1.3333333;
border-radius: 6px
}
select.input-group-lg > .form-control, select.input-group-lg > .input-group-addon, select.input-group-lg > .input-group-btn > .btn {
height: 46px;
line-height: 46px
}
select[multiple].input-group-lg > .form-control, select[multiple].input-group-lg > .input-group-addon, select[multiple].input-group-lg > .input-group-btn > .btn, textarea.input-group-lg > .form-control, textarea.input-group-lg > .input-group-addon, textarea.input-group-lg > .input-group-btn > .btn {
height: auto
}
.input-group-sm > .form-control, .input-group-sm > .input-group-addon, .input-group-sm > .input-group-btn > .btn {
height: 30px;
padding: 5px 10px;
font-size: 12px;
line-height: 1.5;
border-radius: 3px
}
select.input-group-sm > .form-control, select.input-group-sm > .input-group-addon, select.input-group-sm > .input-group-btn > .btn {
height: 30px;
line-height: 30px
}
select[multiple].input-group-sm > .form-control, select[multiple].input-group-sm > .input-group-addon, select[multiple].input-group-sm > .input-group-btn > .btn, textarea.input-group-sm > .form-control, textarea.input-group-sm > .input-group-addon, textarea.input-group-sm > .input-group-btn > .btn {
height: auto
}
.input-group .form-control, .input-group-addon, .input-group-btn {
display: table-cell
}
.input-group .form-control:not(:first-child):not(:last-child), .input-group-addon:not(:first-child):not(:last-child), .input-group-btn:not(:first-child):not(:last-child) {
border-radius: 0
}
.input-group-addon, .input-group-btn {
width: 1%;
white-space: nowrap;
vertical-align: middle
}
.input-group-addon {
padding: 6px 12px;
font-size: 14px;
font-weight: 400;
line-height: 1;
color: #555;
text-align: center;
background-color: #eee;
border: 1px solid #ccc;
border-radius: 4px
}
.input-group-addon.input-sm {
padding: 5px 10px;
font-size: 12px;
border-radius: 3px
}
.input-group-addon.input-lg {
padding: 10px 16px;
font-size: 18px;
border-radius: 6px
}
.input-group-addon input[type=checkbox], .input-group-addon input[type=radio] {
margin-top: 0
}
.input-group .form-control:first-child, .input-group-addon:first-child, .input-group-btn:first-child > .btn, .input-group-btn:first-child > .btn-group > .btn, .input-group-btn:first-child > .dropdown-toggle, .input-group-btn:last-child > .btn-group:not(:last-child) > .btn, .input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle) {
border-top-right-radius: 0;
border-bottom-right-radius: 0
}
.input-group-addon:first-child {
border-right: 0
}
.input-group .form-control:last-child, .input-group-addon:last-child, .input-group-btn:first-child > .btn-group:not(:first-child) > .btn, .input-group-btn:first-child > .btn:not(:first-child), .input-group-btn:last-child > .btn, .input-group-btn:last-child > .btn-group > .btn, .input-group-btn:last-child > .dropdown-toggle {
border-top-left-radius: 0;
border-bottom-left-radius: 0
}
.input-group-addon:last-child {
border-left: 0
}
.input-group-btn {
position: relative;
font-size: 0;
white-space: nowrap
}
.input-group-btn > .btn {
position: relative
}
.input-group-btn > .btn + .btn {
margin-left: -1px
}
.input-group-btn > .btn:active, .input-group-btn > .btn:focus, .input-group-btn > .btn:hover {
z-index: 2
}
.input-group-btn:first-child > .btn, .input-group-btn:first-child > .btn-group {
margin-right: -1px
}
.input-group-btn:last-child > .btn, .input-group-btn:last-child > .btn-group {
z-index: 2;
margin-left: -1px
}
.nav {
padding-left: 0;
margin-bottom: 0;
list-style: none
}
.nav > li {
position: relative;
display: block
}
.nav > li > a {
position: relative;
display: block;
padding: 10px 15px
}
.nav > li > a:focus, .nav > li > a:hover {
text-decoration: none;
background-color: #eee
}
.nav > li.disabled > a {
color: #777
}
.nav > li.disabled > a:focus, .nav > li.disabled > a:hover {
color: #777;
text-decoration: none;
cursor: not-allowed;
background-color: transparent
}
.nav .open > a, .nav .open > a:focus, .nav .open > a:hover {
background-color: #eee;
border-color: #337ab7
}
.nav .nav-divider {
height: 1px;
margin: 9px 0;
overflow: hidden;
background-color: #e5e5e5
}
.nav > li > a > img {
max-width: none
}
.nav-tabs {
border-bottom: 1px solid #ddd
}
.nav-tabs > li {
float: left;
margin-bottom: -1px
}
.nav-tabs > li > a {
margin-right: 2px;
line-height: 1.42857143;
border: 1px solid transparent;
border-radius: 4px 4px 0 0
}
.nav-tabs > li > a:hover {
border-color: #eee #eee #ddd
}
.nav-tabs > li.active > a, .nav-tabs > li.active > a:focus, .nav-tabs > li.active > a:hover {
color: #555;
cursor: default;
background-color: #fff;
border: 1px solid #ddd;
border-bottom-color: transparent
}
.nav-tabs.nav-justified {
width: 100%;
border-bottom: 0
}
.nav-tabs.nav-justified > li {
float: none
}
.nav-tabs.nav-justified > li > a {
margin-bottom: 5px;
text-align: center;
margin-right: 0;
border-radius: 4px
}
.nav-tabs.nav-justified > .dropdown .dropdown-menu {
top: auto;
left: auto
}
.nav-tabs.nav-justified > .active > a, .nav-tabs.nav-justified > .active > a:focus, .nav-tabs.nav-justified > .active > a:hover {
border: 1px solid #ddd
}
.nav-pills > li {
float: left
}
.nav-pills > li > a {
border-radius: 4px
}
.nav-pills > li + li {
margin-left: 2px
}
.nav-pills > li.active > a, .nav-pills > li.active > a:focus, .nav-pills > li.active > a:hover {
color: #fff;
background-color: #337ab7
}
.nav-stacked > li {
float: none
}
.nav-stacked > li + li {
margin-top: 2px;
margin-left: 0
}
.nav-justified {
width: 100%
}
.nav-justified > li {
float: none
}
.nav-justified > li > a {
margin-bottom: 5px;
text-align: center
}
.nav-justified > .dropdown .dropdown-menu {
top: auto;
left: auto
}
@media (min-width:768px) {
.navbar-right .dropdown-menu {
right: 0;
left: auto
}
.navbar-right .dropdown-menu-left {
right: auto;
left: 0
}
.nav-tabs.nav-justified > li {
display: table-cell;
width: 1%
}
.nav-tabs.nav-justified > li > a {
margin-bottom: 0;
border-bottom: 1px solid #ddd;
border-radius: 4px 4px 0 0
}
.nav-tabs.nav-justified > .active > a, .nav-tabs.nav-justified > .active > a:focus, .nav-tabs.nav-justified > .active > a:hover {
border-bottom-color: #fff
}
.nav-justified > li {
display: table-cell;
width: 1%
}
.nav-justified > li > a {
margin-bottom: 0
}
}
.nav-tabs-justified {
border-bottom: 0
}
.nav-tabs-justified > li > a {
margin-right: 0;
border-radius: 4px
}
.nav-tabs-justified > .active > a, .nav-tabs-justified > .active > a:focus, .nav-tabs-justified > .active > a:hover {
border: 1px solid #ddd
}
@media (min-width:768px) {
.nav-tabs-justified > li > a {
border-bottom: 1px solid #ddd;
border-radius: 4px 4px 0 0
}
.nav-tabs-justified > .active > a, .nav-tabs-justified > .active > a:focus, .nav-tabs-justified > .active > a:hover {
border-bottom-color: #fff
}
}
.tab-content > .tab-pane {
display: none
}
.tab-content > .active {
display: block
}
.nav-tabs .dropdown-menu {
margin-top: -1px;
border-top-left-radius: 0;
border-top-right-radius: 0
}
.navbar {
position: relative;
min-height: 50px;
margin-bottom: 20px;
border: 1px solid transparent
}
.navbar-collapse {
padding-right: 15px;
padding-left: 15px;
overflow-x: visible;
border-top: 1px solid transparent;
-webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.1);
box-shadow: inset 0 1px 0 rgba(255,255,255,.1);
-webkit-overflow-scrolling: touch
}
.navbar-collapse.in {
overflow-y: auto
}
.navbar-fixed-bottom, .navbar-fixed-top {
position: fixed;
right: 0;
left: 0;
z-index: 1030
}
.navbar-fixed-bottom .navbar-collapse, .navbar-fixed-top .navbar-collapse {
max-height: 340px
}
@media (max-device-width:480px) and (orientation:landscape) {
.navbar-fixed-bottom .navbar-collapse, .navbar-fixed-top .navbar-collapse {
max-height: 200px
}
}
@media (min-width:768px) {
.navbar {
border-radius: 4px
}
.navbar-header {
float: left
}
.navbar-collapse {
width: auto;
border-top: 0;
-webkit-box-shadow: none;
box-shadow: none
}
.navbar-collapse.collapse {
display: block !important;
height: auto !important;
padding-bottom: 0;
overflow: visible !important
}
.navbar-collapse.in {
overflow-y: visible
}
.navbar-fixed-bottom .navbar-collapse, .navbar-fixed-top .navbar-collapse, .navbar-static-top .navbar-collapse {
padding-right: 0;
padding-left: 0
}
.navbar-fixed-bottom, .navbar-fixed-top {
border-radius: 0
}
}
.navbar-fixed-top {
top: 0;
border-width: 0 0 1px
}
.navbar-fixed-bottom {
bottom: 0;
margin-bottom: 0;
border-width: 1px 0 0
}
.container-fluid > .navbar-collapse, .container-fluid > .navbar-header, .container > .navbar-collapse, .container > .navbar-header {
margin-right: -15px;
margin-left: -15px
}
.navbar-static-top {
z-index: 1000;
border-width: 0 0 1px
}
.navbar-brand {
float: left;
height: 50px;
padding: 15px;
font-size: 18px;
line-height: 20px
}
.navbar-brand:focus, .navbar-brand:hover {
text-decoration: none
}
.navbar-brand > img {
display: block
}
@media (min-width:768px) {
.container-fluid > .navbar-collapse, .container-fluid > .navbar-header, .container > .navbar-collapse, .container > .navbar-header {
margin-right: 0;
margin-left: 0
}
.navbar-static-top {
border-radius: 0
}
.navbar > .container .navbar-brand, .navbar > .container-fluid .navbar-brand {
margin-left: -15px
}
.navbar-toggle {
display: none
}
}
.navbar-toggle {
position: relative;
float: right;
padding: 9px 10px;
margin-right: 15px;
margin-top: 8px;
margin-bottom: 8px;
background-color: transparent;
background-image: none;
border: 1px solid transparent;
border-radius: 4px
}
.navbar-toggle:focus {
outline: 0
}
.navbar-toggle .icon-bar {
display: block;
width: 22px;
height: 2px;
border-radius: 1px
}
.navbar-toggle .icon-bar + .icon-bar {
margin-top: 4px
}
.navbar-nav {
margin: 7.5px -15px
}
.navbar-nav > li > a {
padding-top: 10px;
padding-bottom: 10px;
line-height: 20px
}
@media (max-width:767px) {
.navbar-nav .open .dropdown-menu {
position: static;
float: none;
width: auto;
margin-top: 0;
background-color: transparent;
border: 0;
-webkit-box-shadow: none;
box-shadow: none
}
.navbar-nav .open .dropdown-menu .dropdown-header, .navbar-nav .open .dropdown-menu > li > a {
padding: 5px 15px 5px 25px
}
.navbar-nav .open .dropdown-menu > li > a {
line-height: 20px
}
.navbar-nav .open .dropdown-menu > li > a:focus, .navbar-nav .open .dropdown-menu > li > a:hover {
background-image: none
}
}
@media (min-width:768px) {
.navbar-nav {
float: left;
margin: 0
}
.navbar-nav > li {
float: left
}
.navbar-nav > li > a {
padding-top: 15px;
padding-bottom: 15px
}
.navbar-form .form-group {
display: inline-block;
margin-bottom: 0;
vertical-align: middle
}
.navbar-form .form-control {
display: inline-block;
width: auto;
vertical-align: middle
}
.navbar-form .form-control-static {
display: inline-block
}
.navbar-form .input-group {
display: inline-table;
vertical-align: middle
}
.navbar-form .input-group .form-control, .navbar-form .input-group .input-group-addon, .navbar-form .input-group .input-group-btn {
width: auto
}
.navbar-form .input-group > .form-control {
width: 100%
}
.navbar-form .control-label {
margin-bottom: 0;
vertical-align: middle
}
.navbar-form .checkbox, .navbar-form .radio {
display: inline-block;
margin-top: 0;
margin-bottom: 0;
vertical-align: middle
}
.navbar-form .checkbox label, .navbar-form .radio label {
padding-left: 0
}
.navbar-form .checkbox input[type=checkbox], .navbar-form .radio input[type=radio] {
position: relative;
margin-left: 0
}
.navbar-form .has-feedback .form-control-feedback {
top: 0
}
}
.navbar-form {
padding: 10px 15px;
border-top: 1px solid transparent;
border-bottom: 1px solid transparent;
-webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);
box-shadow: inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);
margin: 8px -15px
}
@media (max-width:767px) {
.navbar-form .form-group {
margin-bottom: 5px
}
.navbar-form .form-group:last-child {
margin-bottom: 0
}
.navbar-default .navbar-nav .open .dropdown-menu > li > a {
color: #777
}
.navbar-default .navbar-nav .open .dropdown-menu > li > a:focus, .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover {
color: #333;
background-color: transparent
}
.navbar-default .navbar-nav .open .dropdown-menu > .active > a, .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus, .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover {
color: #555;
background-color: #e7e7e7
}
.navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus, .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover {
color: #ccc;
background-color: transparent
}
}
@media (min-width:768px) {
.navbar-form {
width: auto;
padding-top: 0;
padding-bottom: 0;
margin-right: 0;
margin-left: 0;
border: 0;
-webkit-box-shadow: none;
box-shadow: none
}
.navbar-text {
float: left;
margin-right: 15px;
margin-left: 15px
}
}
.navbar-nav > li > .dropdown-menu {
margin-top: 0;
border-top-left-radius: 0;
border-top-right-radius: 0
}
.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {
margin-bottom: 0;
border-radius: 4px 4px 0 0
}
.navbar-btn {
margin-top: 8px;
margin-bottom: 8px
}
.navbar-btn.btn-sm {
margin-top: 10px;
margin-bottom: 10px
}
.navbar-btn.btn-xs {
margin-top: 14px;
margin-bottom: 14px
}
.navbar-text {
margin-top: 15px;
margin-bottom: 15px
}
@media (min-width:768px) {
.navbar-left {
float: left !important
}
.navbar-right {
float: right !important;
margin-right: -15px
}
.navbar-right ~ .navbar-right {
margin-right: 0
}
}
.navbar-default {
background-color: #f8f8f8;
border-color: #e7e7e7
}
.navbar-default .navbar-brand {
color: #777
}
.navbar-default .navbar-brand:focus, .navbar-default .navbar-brand:hover {
color: #5e5e5e;
background-color: transparent
}
.navbar-default .navbar-nav > li > a, .navbar-default .navbar-text {
color: #777
}
.navbar-default .navbar-nav > li > a:focus, .navbar-default .navbar-nav > li > a:hover {
color: #333;
background-color: transparent
}
.navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:focus, .navbar-default .navbar-nav > .active > a:hover {
color: #555;
background-color: #e7e7e7
}
.navbar-default .navbar-nav > .disabled > a, .navbar-default .navbar-nav > .disabled > a:focus, .navbar-default .navbar-nav > .disabled > a:hover {
color: #ccc;
background-color: transparent
}
.navbar-default .navbar-nav > .open > a, .navbar-default .navbar-nav > .open > a:focus, .navbar-default .navbar-nav > .open > a:hover {
color: #555;
background-color: #e7e7e7
}
.navbar-default .navbar-toggle {
border-color: #ddd
}
.navbar-default .navbar-toggle:focus, .navbar-default .navbar-toggle:hover {
background-color: #ddd
}
.navbar-default .navbar-toggle .icon-bar {
background-color: #888
}
.navbar-default .navbar-collapse, .navbar-default .navbar-form {
border-color: #e7e7e7
}
.navbar-default .navbar-link {
color: #777
}
.navbar-default .navbar-link:hover {
color: #333
}
.navbar-default .btn-link {
color: #777
}
.navbar-default .btn-link:focus, .navbar-default .btn-link:hover {
color: #333
}
.navbar-default .btn-link[disabled]:focus, .navbar-default .btn-link[disabled]:hover, fieldset[disabled] .navbar-default .btn-link:focus, fieldset[disabled] .navbar-default .btn-link:hover {
color: #ccc
}
.navbar-inverse {
background-color: #222;
border-color: #080808
}
.navbar-inverse .navbar-brand {
color: #9d9d9d
}
.navbar-inverse .navbar-brand:focus, .navbar-inverse .navbar-brand:hover {
color: #fff;
background-color: transparent
}
.navbar-inverse .navbar-nav > li > a, .navbar-inverse .navbar-text {
color: #9d9d9d
}
.navbar-inverse .navbar-nav > li > a:focus, .navbar-inverse .navbar-nav > li > a:hover {
color: #fff;
background-color: transparent
}
.navbar-inverse .navbar-nav > .active > a, .navbar-inverse .navbar-nav > .active > a:focus, .navbar-inverse .navbar-nav > .active > a:hover {
color: #fff;
background-color: #080808
}
.navbar-inverse .navbar-nav > .disabled > a, .navbar-inverse .navbar-nav > .disabled > a:focus, .navbar-inverse .navbar-nav > .disabled > a:hover {
color: #444;
background-color: transparent
}
.navbar-inverse .navbar-nav > .open > a, .navbar-inverse .navbar-nav > .open > a:focus, .navbar-inverse .navbar-nav > .open > a:hover {
color: #fff;
background-color: #080808
}
@media (max-width:767px) {
.navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {
border-color: #080808
}
.navbar-inverse .navbar-nav .open .dropdown-menu .divider {
background-color: #080808
}
.navbar-inverse .navbar-nav .open .dropdown-menu > li > a {
color: #9d9d9d
}
.navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus, .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover {
color: #fff;
background-color: transparent
}
.navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus, .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover {
color: #fff;
background-color: #080808
}
.navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus, .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover {
color: #444;
background-color: transparent
}
}
.navbar-inverse .navbar-toggle {
border-color: #333
}
.navbar-inverse .navbar-toggle:focus, .navbar-inverse .navbar-toggle:hover {
background-color: #333
}
.navbar-inverse .navbar-toggle .icon-bar {
background-color: #fff
}
.navbar-inverse .navbar-collapse, .navbar-inverse .navbar-form {
border-color: #101010
}
.navbar-inverse .navbar-link {
color: #9d9d9d
}
.navbar-inverse .navbar-link:hover {
color: #fff
}
.navbar-inverse .btn-link {
color: #9d9d9d
}
.navbar-inverse .btn-link:focus, .navbar-inverse .btn-link:hover {
color: #fff
}
.navbar-inverse .btn-link[disabled]:focus, .navbar-inverse .btn-link[disabled]:hover, fieldset[disabled] .navbar-inverse .btn-link:focus, fieldset[disabled] .navbar-inverse .btn-link:hover {
color: #444
}
.breadcrumb {
padding: 8px 15px;
margin-bottom: 20px;
list-style: none;
background-color: #f5f5f5;
border-radius: 4px
}
.breadcrumb > li {
display: inline-block
}
.breadcrumb > li + li:before {
padding: 0 5px;
color: #ccc;
content: "/\00a0"
}
.breadcrumb > .active {
color: #777
}
.pagination {
display: inline-block;
padding-left: 0;
margin: 20px 0;
border-radius: 4px
}
.pagination > li {
display: inline
}
.pagination > li > a, .pagination > li > span {
position: relative;
float: left;
padding: 6px 12px;
margin-left: -1px;
line-height: 1.42857143;
color: #337ab7;
text-decoration: none;
background-color: #fff;
border: 1px solid #ddd
}
.pagination > li > a:focus, .pagination > li > a:hover, .pagination > li > span:focus, .pagination > li > span:hover {
z-index: 2;
color: #23527c;
background-color: #eee;
border-color: #ddd
}
.pagination > li:first-child > a, .pagination > li:first-child > span {
margin-left: 0;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px
}
.pagination > li:last-child > a, .pagination > li:last-child > span {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px
}
.pagination > .active > a, .pagination > .active > a:focus, .pagination > .active > a:hover, .pagination > .active > span, .pagination > .active > span:focus, .pagination > .active > span:hover {
z-index: 3;
color: #fff;
cursor: default;
background-color: #337ab7;
border-color: #337ab7
}
.pagination > .disabled > a, .pagination > .disabled > a:focus, .pagination > .disabled > a:hover, .pagination > .disabled > span, .pagination > .disabled > span:focus, .pagination > .disabled > span:hover {
color: #777;
cursor: not-allowed;
background-color: #fff;
border-color: #ddd
}
.pagination-lg > li > a, .pagination-lg > li > span {
padding: 10px 16px;
font-size: 18px;
line-height: 1.3333333
}
.pagination-lg > li:first-child > a, .pagination-lg > li:first-child > span {
border-top-left-radius: 6px;
border-bottom-left-radius: 6px
}
.pagination-lg > li:last-child > a, .pagination-lg > li:last-child > span {
border-top-right-radius: 6px;
border-bottom-right-radius: 6px
}
.pagination-sm > li > a, .pagination-sm > li > span {
padding: 5px 10px;
font-size: 12px;
line-height: 1.5
}
.pagination-sm > li:first-child > a, .pagination-sm > li:first-child > span {
border-top-left-radius: 3px;
border-bottom-left-radius: 3px
}
.pagination-sm > li:last-child > a, .pagination-sm > li:last-child > span {
border-top-right-radius: 3px;
border-bottom-right-radius: 3px
}
.pager {
padding-left: 0;
margin: 20px 0;
text-align: center;
list-style: none
}
.pager li {
display: inline
}
.pager li > a, .pager li > span {
display: inline-block;
padding: 5px 14px;
background-color: #fff;
border: 1px solid #ddd;
border-radius: 15px
}
.pager li > a:focus, .pager li > a:hover {
text-decoration: none;
background-color: #eee
}
.pager .next > a, .pager .next > span {
float: right
}
.pager .previous > a, .pager .previous > span {
float: left
}
.pager .disabled > a, .pager .disabled > a:focus, .pager .disabled > a:hover, .pager .disabled > span {
color: #777;
cursor: not-allowed;
background-color: #fff
}
.label {
display: inline;
padding: .2em .6em .3em;
font-size: 75%;
font-weight: 700;
line-height: 1;
color: #fff;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
border-radius: .25em
}
a.label:focus, a.label:hover {
color: #fff;
text-decoration: none;
cursor: pointer
}
.label:empty {
display: none
}
.btn .label {
position: relative;
top: -1px
}
.label-default {
background-color: #777
}
.label-default[href]:focus, .label-default[href]:hover {
background-color: #5e5e5e
}
.label-primary {
background-color: #337ab7
}
.label-primary[href]:focus, .label-primary[href]:hover {
background-color: #286090
}
.label-success {
background-color: #5cb85c
}
.label-success[href]:focus, .label-success[href]:hover {
background-color: #449d44
}
.label-info {
background-color: #5bc0de
}
.label-info[href]:focus, .label-info[href]:hover {
background-color: #31b0d5
}
.label-warning {
background-color: #f0ad4e
}
.label-warning[href]:focus, .label-warning[href]:hover {
background-color: #ec971f
}
.label-danger {
background-color: #d9534f
}
.label-danger[href]:focus, .label-danger[href]:hover {
background-color: #c9302c
}
.badge {
display: inline-block;
min-width: 10px;
padding: 3px 7px;
font-size: 12px;
font-weight: 700;
line-height: 1;
color: #fff;
text-align: center;
white-space: nowrap;
vertical-align: middle;
background-color: #777;
border-radius: 10px
}
.badge:empty {
display: none
}
.btn .badge {
position: relative;
top: -1px
}
.btn-group-xs > .btn .badge, .btn-xs .badge {
top: 0;
padding: 1px 5px
}
a.badge:focus, a.badge:hover {
color: #fff;
text-decoration: none;
cursor: pointer
}
.list-group-item.active > .badge, .nav-pills > .active > a > .badge {
color: #337ab7;
background-color: #fff
}
.list-group-item > .badge {
float: right
}
.list-group-item > .badge + .badge {
margin-right: 5px
}
.nav-pills > li > a > .badge {
margin-left: 3px
}
.jumbotron {
padding-top: 30px;
padding-bottom: 30px;
margin-bottom: 30px;
color: inherit;
background-color: #eee
}
.jumbotron .h1, .jumbotron h1 {
color: inherit
}
.jumbotron p {
margin-bottom: 15px;
font-size: 21px;
font-weight: 200
}
.jumbotron > hr {
border-top-color: #d5d5d5
}
.container .jumbotron, .container-fluid .jumbotron {
padding-right: 15px;
padding-left: 15px;
border-radius: 6px
}
.jumbotron .container {
max-width: 100%
}
@media screen and (min-width:768px) {
.jumbotron {
padding-top: 48px;
padding-bottom: 48px
}
.container .jumbotron, .container-fluid .jumbotron {
padding-right: 60px;
padding-left: 60px
}
.jumbotron .h1, .jumbotron h1 {
font-size: 63px
}
}
.thumbnail {
display: block;
padding: 4px;
margin-bottom: 20px;
line-height: 1.42857143;
background-color: #fff;
border: 1px solid #ddd;
border-radius: 4px;
-webkit-transition: border .2s ease-in-out;
-o-transition: border .2s ease-in-out;
transition: border .2s ease-in-out
}
.thumbnail a > img, .thumbnail > img {
margin-right: auto;
margin-left: auto
}
a.thumbnail.active, a.thumbnail:focus, a.thumbnail:hover {
border-color: #337ab7
}
.thumbnail .caption {
padding: 9px;
color: #333
}
.alert {
padding: 15px;
margin-bottom: 20px;
border: 1px solid transparent;
border-radius: 4px
}
.alert h4 {
margin-top: 0;
color: inherit
}
.alert .alert-link {
font-weight: 700
}
.alert > p, .alert > ul {
margin-bottom: 0
}
.alert > p + p {
margin-top: 5px
}
.alert-dismissable, .alert-dismissible {
padding-right: 35px
}
.alert-dismissable .close, .alert-dismissible .close {
position: relative;
top: -2px;
right: -21px;
color: inherit
}
.alert-success {
color: #3c763d;
background-color: #dff0d8;
border-color: #d6e9c6
}
.alert-success hr {
border-top-color: #c9e2b3
}
.alert-success .alert-link {
color: #2b542c
}
.alert-info {
color: #31708f;
background-color: #d9edf7;
border-color: #bce8f1
}
.alert-info hr {
border-top-color: #a6e1ec
}
.alert-info .alert-link {
color: #245269
}
.alert-warning {
color: #8a6d3b;
background-color: #fcf8e3;
border-color: #faebcc
}
.alert-warning hr {
border-top-color: #f7e1b5
}
.alert-warning .alert-link {
color: #66512c
}
.alert-danger {
color: #a94442;
background-color: #f2dede;
border-color: #ebccd1
}
.alert-danger hr {
border-top-color: #e4b9c0
}
.alert-danger .alert-link {
color: #843534
}
@-webkit-keyframes progress-bar-stripes {
from {
background-position: 40px 0
}
to {
background-position: 0 0
}
}
@-o-keyframes progress-bar-stripes {
from {
background-position: 40px 0
}
to {
background-position: 0 0
}
}
@keyframes progress-bar-stripes {
from {
background-position: 40px 0
}
to {
background-position: 0 0
}
}
.progress {
height: 20px;
margin-bottom: 20px;
overflow: hidden;
background-color: #f5f5f5;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 2px rgba(0,0,0,.1);
box-shadow: inset 0 1px 2px rgba(0,0,0,.1)
}
.progress-bar {
float: left;
width: 0%;
height: 100%;
font-size: 12px;
line-height: 20px;
color: #fff;
text-align: center;
background-color: #337ab7;
-webkit-box-shadow: inset 0 -1px 0 rgba(0,0,0,.15);
box-shadow: inset 0 -1px 0 rgba(0,0,0,.15);
-webkit-transition: width .6s;
-o-transition: width .6s;
transition: width .6s
}
.progress-bar-striped, .progress-striped .progress-bar {
background-image: -webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
background-image: -o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
background-image: linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
-webkit-background-size: 40px 40px;
background-size: 40px 40px
}
.progress-bar.active, .progress.active .progress-bar {
-webkit-animation: 2s linear infinite progress-bar-stripes;
-o-animation: 2s linear infinite progress-bar-stripes;
animation: 2s linear infinite progress-bar-stripes
}
.progress-bar-success {
background-color: #5cb85c
}
.progress-striped .progress-bar-success {
background-image: -webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
background-image: -o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
background-image: linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)
}
.progress-bar-info {
background-color: #5bc0de
}
.progress-striped .progress-bar-info {
background-image: -webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
background-image: -o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
background-image: linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)
}
.progress-bar-warning {
background-color: #f0ad4e
}
.progress-striped .progress-bar-warning {
background-image: -webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
background-image: -o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
background-image: linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)
}
.progress-bar-danger {
background-color: #d9534f
}
.progress-striped .progress-bar-danger {
background-image: -webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
background-image: -o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
background-image: linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)
}
.media {
margin-top: 15px
}
.media:first-child {
margin-top: 0
}
.media, .media-body {
overflow: hidden;
zoom: 1
}
.media-body {
width: 10000px
}
.media-object {
display: block
}
.media-object.img-thumbnail {
max-width: none
}
.media-right, .media > .pull-right {
padding-left: 10px
}
.media-left, .media > .pull-left {
padding-right: 10px
}
.media-body, .media-left, .media-right {
display: table-cell;
vertical-align: top
}
.media-middle {
vertical-align: middle
}
.media-bottom {
vertical-align: bottom
}
.media-heading {
margin-top: 0;
margin-bottom: 5px
}
.media-list {
padding-left: 0;
list-style: none
}
.list-group {
padding-left: 0;
margin-bottom: 20px
}
.list-group-item {
position: relative;
display: block;
padding: 10px 15px;
margin-bottom: -1px;
background-color: #fff;
border: 1px solid #ddd
}
.list-group-item:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px
}
.list-group-item:last-child {
margin-bottom: 0;
border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px
}
.list-group-item.disabled, .list-group-item.disabled:focus, .list-group-item.disabled:hover {
color: #777;
cursor: not-allowed;
background-color: #eee
}
.list-group-item.disabled .list-group-item-heading, .list-group-item.disabled:focus .list-group-item-heading, .list-group-item.disabled:hover .list-group-item-heading {
color: inherit
}
.list-group-item.disabled .list-group-item-text, .list-group-item.disabled:focus .list-group-item-text, .list-group-item.disabled:hover .list-group-item-text {
color: #777
}
.list-group-item.active, .list-group-item.active:focus, .list-group-item.active:hover {
z-index: 2;
color: #fff;
background-color: #337ab7;
border-color: #337ab7
}
.list-group-item.active .list-group-item-heading, .list-group-item.active .list-group-item-heading > .small, .list-group-item.active .list-group-item-heading > small, .list-group-item.active:focus .list-group-item-heading, .list-group-item.active:focus .list-group-item-heading > .small, .list-group-item.active:focus .list-group-item-heading > small, .list-group-item.active:hover .list-group-item-heading, .list-group-item.active:hover .list-group-item-heading > .small, .list-group-item.active:hover .list-group-item-heading > small {
color: inherit
}
.list-group-item.active .list-group-item-text, .list-group-item.active:focus .list-group-item-text, .list-group-item.active:hover .list-group-item-text {
color: #c7ddef
}
a.list-group-item, button.list-group-item {
color: #555
}
a.list-group-item .list-group-item-heading, button.list-group-item .list-group-item-heading {
color: #333
}
a.list-group-item:focus, a.list-group-item:hover, button.list-group-item:focus, button.list-group-item:hover {
color: #555;
text-decoration: none;
background-color: #f5f5f5
}
button.list-group-item {
width: 100%;
text-align: left
}
.list-group-item-success {
color: #3c763d;
background-color: #dff0d8
}
a.list-group-item-success, button.list-group-item-success {
color: #3c763d
}
a.list-group-item-success .list-group-item-heading, button.list-group-item-success .list-group-item-heading {
color: inherit
}
a.list-group-item-success:focus, a.list-group-item-success:hover, button.list-group-item-success:focus, button.list-group-item-success:hover {
color: #3c763d;
background-color: #d0e9c6
}
a.list-group-item-success.active, a.list-group-item-success.active:focus, a.list-group-item-success.active:hover, button.list-group-item-success.active, button.list-group-item-success.active:focus, button.list-group-item-success.active:hover {
color: #fff;
background-color: #3c763d;
border-color: #3c763d
}
.list-group-item-info {
color: #31708f;
background-color: #d9edf7
}
a.list-group-item-info, button.list-group-item-info {
color: #31708f
}
a.list-group-item-info .list-group-item-heading, button.list-group-item-info .list-group-item-heading {
color: inherit
}
a.list-group-item-info:focus, a.list-group-item-info:hover, button.list-group-item-info:focus, button.list-group-item-info:hover {
color: #31708f;
background-color: #c4e3f3
}
a.list-group-item-info.active, a.list-group-item-info.active:focus, a.list-group-item-info.active:hover, button.list-group-item-info.active, button.list-group-item-info.active:focus, button.list-group-item-info.active:hover {
color: #fff;
background-color: #31708f;
border-color: #31708f
}
.list-group-item-warning {
color: #8a6d3b;
background-color: #fcf8e3
}
a.list-group-item-warning, button.list-group-item-warning {
color: #8a6d3b
}
a.list-group-item-warning .list-group-item-heading, button.list-group-item-warning .list-group-item-heading {
color: inherit
}
a.list-group-item-warning:focus, a.list-group-item-warning:hover, button.list-group-item-warning:focus, button.list-group-item-warning:hover {
color: #8a6d3b;
background-color: #faf2cc
}
a.list-group-item-warning.active, a.list-group-item-warning.active:focus, a.list-group-item-warning.active:hover, button.list-group-item-warning.active, button.list-group-item-warning.active:focus, button.list-group-item-warning.active:hover {
color: #fff;
background-color: #8a6d3b;
border-color: #8a6d3b
}
.list-group-item-danger {
color: #a94442;
background-color: #f2dede
}
a.list-group-item-danger, button.list-group-item-danger {
color: #a94442
}
a.list-group-item-danger .list-group-item-heading, button.list-group-item-danger .list-group-item-heading {
color: inherit
}
a.list-group-item-danger:focus, a.list-group-item-danger:hover, button.list-group-item-danger:focus, button.list-group-item-danger:hover {
color: #a94442;
background-color: #ebcccc
}
a.list-group-item-danger.active, a.list-group-item-danger.active:focus, a.list-group-item-danger.active:hover, button.list-group-item-danger.active, button.list-group-item-danger.active:focus, button.list-group-item-danger.active:hover {
color: #fff;
background-color: #a94442;
border-color: #a94442
}
.list-group-item-heading {
margin-top: 0;
margin-bottom: 5px
}
.list-group-item-text {
margin-bottom: 0;
line-height: 1.3
}
.panel {
margin-bottom: 20px;
background-color: #fff;
border: 1px solid transparent;
border-radius: 4px;
-webkit-box-shadow: 0 1px 1px rgba(0,0,0,.05);
box-shadow: 0 1px 1px rgba(0,0,0,.05)
}
.panel-body {
padding: 15px
}
.panel-heading {
padding: 10px 15px;
border-bottom: 1px solid transparent;
border-top-left-radius: 3px;
border-top-right-radius: 3px
}
.panel-heading > .dropdown .dropdown-toggle {
color: inherit
}
.panel-title {
margin-top: 0;
margin-bottom: 0;
font-size: 16px;
color: inherit
}
.panel-title > .small, .panel-title > .small > a, .panel-title > a, .panel-title > small, .panel-title > small > a {
color: inherit
}
.panel-footer {
padding: 10px 15px;
background-color: #f5f5f5;
border-top: 1px solid #ddd;
border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px
}
.panel > .list-group, .panel > .panel-collapse > .list-group {
margin-bottom: 0
}
.panel > .list-group .list-group-item, .panel > .panel-collapse > .list-group .list-group-item {
border-width: 1px 0;
border-radius: 0
}
.panel > .list-group:first-child .list-group-item:first-child, .panel > .panel-collapse > .list-group:first-child .list-group-item:first-child {
border-top: 0;
border-top-left-radius: 3px;
border-top-right-radius: 3px
}
.panel > .list-group:last-child .list-group-item:last-child, .panel > .panel-collapse > .list-group:last-child .list-group-item:last-child {
border-bottom: 0;
border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px
}
.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child {
border-top-left-radius: 0;
border-top-right-radius: 0
}
.list-group + .panel-footer, .panel-heading + .list-group .list-group-item:first-child {
border-top-width: 0
}
.panel > .panel-collapse > .table, .panel > .table, .panel > .table-responsive > .table {
margin-bottom: 0
}
.panel > .panel-collapse > .table caption, .panel > .table caption, .panel > .table-responsive > .table caption {
padding-right: 15px;
padding-left: 15px
}
.panel > .table-responsive:first-child > .table:first-child, .panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child, .panel > .table:first-child, .panel > .table:first-child > tbody:first-child > tr:first-child, .panel > .table:first-child > thead:first-child > tr:first-child {
border-top-left-radius: 3px;
border-top-right-radius: 3px
}
.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, .panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, .panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, .panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, .panel > .table:first-child > thead:first-child > tr:first-child td:first-child, .panel > .table:first-child > thead:first-child > tr:first-child th:first-child {
border-top-left-radius: 3px
}
.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, .panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, .panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, .panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, .panel > .table:first-child > thead:first-child > tr:first-child td:last-child, .panel > .table:first-child > thead:first-child > tr:first-child th:last-child {
border-top-right-radius: 3px
}
.panel > .table-responsive:last-child > .table:last-child, .panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child, .panel > .table:last-child, .panel > .table:last-child > tbody:last-child > tr:last-child, .panel > .table:last-child > tfoot:last-child > tr:last-child {
border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px
}
.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, .panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child, .panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, .panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, .panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, .panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child {
border-bottom-left-radius: 3px
}
.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, .panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child, .panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, .panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, .panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, .panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child {
border-bottom-right-radius: 3px
}
.panel > .panel-body + .table, .panel > .panel-body + .table-responsive, .panel > .table + .panel-body, .panel > .table-responsive + .panel-body {
border-top: 1px solid #ddd
}
.panel > .table > tbody:first-child > tr:first-child td, .panel > .table > tbody:first-child > tr:first-child th {
border-top: 0
}
.panel > .table-bordered, .panel > .table-responsive > .table-bordered {
border: 0
}
.panel > .table-bordered > tbody > tr > td:first-child, .panel > .table-bordered > tbody > tr > th:first-child, .panel > .table-bordered > tfoot > tr > td:first-child, .panel > .table-bordered > tfoot > tr > th:first-child, .panel > .table-bordered > thead > tr > td:first-child, .panel > .table-bordered > thead > tr > th:first-child, .panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, .panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, .panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child, .panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, .panel > .table-responsive > .table-bordered > thead > tr > td:first-child, .panel > .table-responsive > .table-bordered > thead > tr > th:first-child {
border-left: 0
}
.panel > .table-bordered > tbody > tr > td:last-child, .panel > .table-bordered > tbody > tr > th:last-child, .panel > .table-bordered > tfoot > tr > td:last-child, .panel > .table-bordered > tfoot > tr > th:last-child, .panel > .table-bordered > thead > tr > td:last-child, .panel > .table-bordered > thead > tr > th:last-child, .panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, .panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, .panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child, .panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, .panel > .table-responsive > .table-bordered > thead > tr > td:last-child, .panel > .table-responsive > .table-bordered > thead > tr > th:last-child {
border-right: 0
}
.panel > .table-bordered > tbody > tr:first-child > td, .panel > .table-bordered > tbody > tr:first-child > th, .panel > .table-bordered > tbody > tr:last-child > td, .panel > .table-bordered > tbody > tr:last-child > th, .panel > .table-bordered > tfoot > tr:last-child > td, .panel > .table-bordered > tfoot > tr:last-child > th, .panel > .table-bordered > thead > tr:first-child > td, .panel > .table-bordered > thead > tr:first-child > th, .panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, .panel > .table-responsive > .table-bordered > tbody > tr:first-child > th, .panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, .panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, .panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, .panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th, .panel > .table-responsive > .table-bordered > thead > tr:first-child > td, .panel > .table-responsive > .table-bordered > thead > tr:first-child > th {
border-bottom: 0
}
.panel > .table-responsive {
margin-bottom: 0;
border: 0
}
.panel-group {
margin-bottom: 20px
}
.panel-group .panel {
margin-bottom: 0;
border-radius: 4px
}
.panel-group .panel + .panel {
margin-top: 5px
}
.panel-group .panel-heading {
border-bottom: 0
}
.panel-group .panel-heading + .panel-collapse > .list-group, .panel-group .panel-heading + .panel-collapse > .panel-body {
border-top: 1px solid #ddd
}
.panel-group .panel-footer {
border-top: 0
}
.panel-group .panel-footer + .panel-collapse .panel-body {
border-bottom: 1px solid #ddd
}
.panel-default {
border-color: #ddd
}
.panel-default > .panel-heading {
color: #333;
background-color: #f5f5f5;
border-color: #ddd
}
.panel-default > .panel-heading + .panel-collapse > .panel-body {
border-top-color: #ddd
}
.panel-default > .panel-heading .badge {
color: #f5f5f5;
background-color: #333
}
.panel-default > .panel-footer + .panel-collapse > .panel-body {
border-bottom-color: #ddd
}
.panel-primary {
border-color: #337ab7
}
.panel-primary > .panel-heading {
color: #fff;
background-color: #337ab7;
border-color: #337ab7
}
.panel-primary > .panel-heading + .panel-collapse > .panel-body {
border-top-color: #337ab7
}
.panel-primary > .panel-heading .badge {
color: #337ab7;
background-color: #fff
}
.panel-primary > .panel-footer + .panel-collapse > .panel-body {
border-bottom-color: #337ab7
}
.panel-success {
border-color: #d6e9c6
}
.panel-success > .panel-heading {
color: #3c763d;
background-color: #dff0d8;
border-color: #d6e9c6
}
.panel-success > .panel-heading + .panel-collapse > .panel-body {
border-top-color: #d6e9c6
}
.panel-success > .panel-heading .badge {
color: #dff0d8;
background-color: #3c763d
}
.panel-success > .panel-footer + .panel-collapse > .panel-body {
border-bottom-color: #d6e9c6
}
.panel-info {
border-color: #bce8f1
}
.panel-info > .panel-heading {
color: #31708f;
background-color: #d9edf7;
border-color: #bce8f1
}
.panel-info > .panel-heading + .panel-collapse > .panel-body {
border-top-color: #bce8f1
}
.panel-info > .panel-heading .badge {
color: #d9edf7;
background-color: #31708f
}
.panel-info > .panel-footer + .panel-collapse > .panel-body {
border-bottom-color: #bce8f1
}
.panel-warning {
border-color: #faebcc
}
.panel-warning > .panel-heading {
color: #8a6d3b;
background-color: #fcf8e3;
border-color: #faebcc
}
.panel-warning > .panel-heading + .panel-collapse > .panel-body {
border-top-color: #faebcc
}
.panel-warning > .panel-heading .badge {
color: #fcf8e3;
background-color: #8a6d3b
}
.panel-warning > .panel-footer + .panel-collapse > .panel-body {
border-bottom-color: #faebcc
}
.panel-danger {
border-color: #ebccd1
}
.panel-danger > .panel-heading {
color: #a94442;
background-color: #f2dede;
border-color: #ebccd1
}
.panel-danger > .panel-heading + .panel-collapse > .panel-body {
border-top-color: #ebccd1
}
.panel-danger > .panel-heading .badge {
color: #f2dede;
background-color: #a94442
}
.panel-danger > .panel-footer + .panel-collapse > .panel-body {
border-bottom-color: #ebccd1
}
.embed-responsive {
position: relative;
display: block;
height: 0;
padding: 0;
overflow: hidden
}
.embed-responsive .embed-responsive-item, .embed-responsive embed, .embed-responsive iframe, .embed-responsive object, .embed-responsive video {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
border: 0
}
.embed-responsive-16by9 {
padding-bottom: 56.25%
}
.embed-responsive-4by3 {
padding-bottom: 75%
}
.well {
min-height: 20px;
padding: 19px;
margin-bottom: 20px;
background-color: #f5f5f5;
border: 1px solid #e3e3e3;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.05);
box-shadow: inset 0 1px 1px rgba(0,0,0,.05)
}
.well blockquote {
border-color: rgba(0,0,0,.15)
}
.well-lg {
padding: 24px;
border-radius: 6px
}
.well-sm {
padding: 9px;
border-radius: 3px
}
.close {
float: right;
font-size: 21px;
font-weight: 700;
line-height: 1;
color: #000;
text-shadow: 0 1px 0 #fff;
opacity: .2
}
.close:focus, .close:hover {
color: #000;
text-decoration: none;
cursor: pointer;
opacity: .5
}
button.close {
padding: 0;
cursor: pointer;
background: 0 0;
border: 0;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none
}
.modal-open {
overflow: hidden
}
.modal {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1050;
display: none;
overflow: hidden;
-webkit-overflow-scrolling: touch;
outline: 0
}
.modal.fade .modal-dialog {
-webkit-transform: translate(0,-25%);
-ms-transform: translate(0,-25%);
-o-transform: translate(0,-25%);
transform: translate(0,-25%);
-webkit-transition: -webkit-transform .3s ease-out;
-o-transition: -o-transform .3s ease-out;
transition: transform .3s ease-out;
transition: transform .3s ease-out,-webkit-transform .3s ease-out,-o-transform .3s ease-out
}
.modal.in .modal-dialog {
-webkit-transform: translate(0,0);
-ms-transform: translate(0,0);
-o-transform: translate(0,0);
transform: translate(0,0)
}
.modal-open .modal {
overflow-x: hidden;
overflow-y: auto
}
.modal-dialog {
position: relative;
width: auto;
margin: 10px
}
.modal-content {
position: relative;
background-color: #fff;
background-clip: padding-box;
border: 1px solid rgba(0,0,0,.2);
border-radius: 6px;
-webkit-box-shadow: 0 3px 9px rgba(0,0,0,.5);
box-shadow: 0 3px 9px rgba(0,0,0,.5);
outline: 0
}
.modal-backdrop {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1040;
background-color: #000
}
.modal-backdrop.fade {
opacity: 0
}
.modal-backdrop.in {
opacity: .5
}
.modal-header {
padding: 15px;
border-bottom: 1px solid #e5e5e5
}
.modal-header .close {
margin-top: -2px
}
.modal-title {
margin: 0;
line-height: 1.42857143
}
.modal-body {
position: relative;
padding: 15px
}
.modal-footer {
padding: 15px;
text-align: right;
border-top: 1px solid #e5e5e5
}
.modal-footer .btn + .btn {
margin-bottom: 0;
margin-left: 5px
}
.modal-footer .btn-group .btn + .btn {
margin-left: -1px
}
.modal-footer .btn-block + .btn-block {
margin-left: 0
}
.modal-scrollbar-measure {
position: absolute;
top: -9999px;
width: 50px;
height: 50px;
overflow: scroll
}
@media (min-width:768px) {
.modal-dialog {
width: 600px;
margin: 30px auto
}
.modal-content {
-webkit-box-shadow: 0 5px 15px rgba(0,0,0,.5);
box-shadow: 0 5px 15px rgba(0,0,0,.5)
}
.modal-sm {
width: 300px
}
}
@media (min-width:992px) {
.modal-lg {
width: 900px
}
}
.tooltip {
position: absolute;
z-index: 1070;
display: block;
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
font-style: normal;
font-weight: 400;
line-height: 1.42857143;
line-break: auto;
text-align: left;
text-align: start;
text-decoration: none;
text-shadow: none;
text-transform: none;
letter-spacing: normal;
word-break: normal;
word-spacing: normal;
word-wrap: normal;
white-space: normal;
font-size: 12px;
opacity: 0
}
.tooltip.in {
opacity: .9
}
.tooltip.top {
padding: 5px 0;
margin-top: -3px
}
.tooltip.right {
padding: 0 5px;
margin-left: 3px
}
.tooltip.bottom {
padding: 5px 0;
margin-top: 3px
}
.tooltip.left {
padding: 0 5px;
margin-left: -3px
}
.tooltip.top .tooltip-arrow {
bottom: 0;
left: 50%;
margin-left: -5px;
border-width: 5px 5px 0;
border-top-color: #000
}
.tooltip.top-left .tooltip-arrow {
right: 5px;
bottom: 0;
margin-bottom: -5px;
border-width: 5px 5px 0;
border-top-color: #000
}
.tooltip.top-right .tooltip-arrow {
bottom: 0;
left: 5px;
margin-bottom: -5px;
border-width: 5px 5px 0;
border-top-color: #000
}
.tooltip.right .tooltip-arrow {
top: 50%;
left: 0;
margin-top: -5px;
border-width: 5px 5px 5px 0;
border-right-color: #000
}
.tooltip.left .tooltip-arrow {
top: 50%;
right: 0;
margin-top: -5px;
border-width: 5px 0 5px 5px;
border-left-color: #000
}
.tooltip.bottom .tooltip-arrow {
top: 0;
left: 50%;
margin-left: -5px;
border-width: 0 5px 5px;
border-bottom-color: #000
}
.tooltip.bottom-left .tooltip-arrow {
top: 0;
right: 5px;
margin-top: -5px;
border-width: 0 5px 5px;
border-bottom-color: #000
}
.tooltip.bottom-right .tooltip-arrow {
top: 0;
left: 5px;
margin-top: -5px;
border-width: 0 5px 5px;
border-bottom-color: #000
}
.tooltip-inner {
max-width: 200px;
padding: 3px 8px;
color: #fff;
text-align: center;
background-color: #000;
border-radius: 4px
}
.tooltip-arrow {
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid
}
.popover {
position: absolute;
top: 0;
left: 0;
z-index: 1060;
display: none;
max-width: 276px;
padding: 1px;
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
font-style: normal;
font-weight: 400;
line-height: 1.42857143;
line-break: auto;
text-align: left;
text-align: start;
text-decoration: none;
text-shadow: none;
text-transform: none;
letter-spacing: normal;
word-break: normal;
word-spacing: normal;
word-wrap: normal;
white-space: normal;
font-size: 14px;
background-color: #fff;
background-clip: padding-box;
border: 1px solid rgba(0,0,0,.2);
border-radius: 6px;
-webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
box-shadow: 0 5px 10px rgba(0,0,0,.2)
}
.popover.top {
margin-top: -10px
}
.popover.right {
margin-left: 10px
}
.popover.bottom {
margin-top: 10px
}
.popover.left {
margin-left: -10px
}
.popover > .arrow {
border-width: 11px
}
.popover > .arrow, .popover > .arrow:after {
position: absolute;
display: block;
width: 0;
height: 0;
border-color: transparent;
border-style: solid
}
.popover > .arrow:after {
content: "";
border-width: 10px
}
.popover.top > .arrow {
bottom: -11px;
left: 50%;
margin-left: -11px;
border-top-color: rgba(0,0,0,.25);
border-bottom-width: 0
}
.popover.top > .arrow:after {
bottom: 1px;
margin-left: -10px;
content: " ";
border-top-color: #fff;
border-bottom-width: 0
}
.popover.right > .arrow {
top: 50%;
left: -11px;
margin-top: -11px;
border-right-color: rgba(0,0,0,.25);
border-left-width: 0
}
.popover.right > .arrow:after {
bottom: -10px;
left: 1px;
content: " ";
border-right-color: #fff;
border-left-width: 0
}
.popover.bottom > .arrow {
top: -11px;
left: 50%;
margin-left: -11px;
border-top-width: 0;
border-bottom-color: rgba(0,0,0,.25)
}
.popover.bottom > .arrow:after {
top: 1px;
margin-left: -10px;
content: " ";
border-top-width: 0;
border-bottom-color: #fff
}
.popover.left > .arrow {
top: 50%;
right: -11px;
margin-top: -11px;
border-right-width: 0;
border-left-color: rgba(0,0,0,.25)
}
.popover.left > .arrow:after {
right: 1px;
bottom: -10px;
content: " ";
border-right-width: 0;
border-left-color: #fff
}
.popover-title {
padding: 8px 14px;
margin: 0;
font-size: 14px;
background-color: #f7f7f7;
border-bottom: 1px solid #ebebeb;
border-radius: 5px 5px 0 0
}
.popover-content {
padding: 9px 14px
}
.carousel {
position: relative
}
.carousel-inner {
position: relative;
width: 100%;
overflow: hidden
}
.carousel-inner > .item {
position: relative;
display: none;
-webkit-transition: left .6s ease-in-out;
-o-transition: left .6s ease-in-out;
transition: left .6s ease-in-out
}
.carousel-inner > .item > a > img, .carousel-inner > .item > img {
line-height: 1
}
@media all and (transform-3d),(-webkit-transform-3d) {
.carousel-inner > .item {
-webkit-transition: -webkit-transform .6s ease-in-out;
-o-transition: -o-transform .6s ease-in-out;
transition: transform .6s ease-in-out;
transition: transform .6s ease-in-out,-webkit-transform .6s ease-in-out,-o-transform .6s ease-in-out;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-perspective: 1000px;
perspective: 1000px
}
.carousel-inner > .item.active.right, .carousel-inner > .item.next {
-webkit-transform: translate3d(100%,0,0);
transform: translate3d(100%,0,0);
left: 0
}
.carousel-inner > .item.active.left, .carousel-inner > .item.prev {
-webkit-transform: translate3d(-100%,0,0);
transform: translate3d(-100%,0,0);
left: 0
}
.carousel-inner > .item.active, .carousel-inner > .item.next.left, .carousel-inner > .item.prev.right {
-webkit-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
left: 0
}
}
.carousel-inner > .active, .carousel-inner > .next, .carousel-inner > .prev {
display: block
}
.carousel-inner > .active {
left: 0
}
.carousel-inner > .next, .carousel-inner > .prev {
position: absolute;
top: 0;
width: 100%
}
.carousel-inner > .next {
left: 100%
}
.carousel-inner > .prev {
left: -100%
}
.carousel-inner > .next.left, .carousel-inner > .prev.right {
left: 0
}
.carousel-inner > .active.left {
left: -100%
}
.carousel-inner > .active.right {
left: 100%
}
.carousel-control {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 15%;
font-size: 20px;
color: #fff;
text-align: center;
text-shadow: 0 1px 2px rgba(0,0,0,.6);
background-color: rgba(0,0,0,0);
opacity: .5
}
.carousel-control.left {
background-image: -webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);
background-image: -o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);
background-image: -webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));
background-image: linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);
background-repeat: repeat-x
}
.carousel-control.right {
right: 0;
left: auto;
background-image: -webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);
background-image: -o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);
background-image: -webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));
background-image: linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);
background-repeat: repeat-x
}
.carousel-control:focus, .carousel-control:hover {
color: #fff;
text-decoration: none;
outline: 0;
opacity: .9
}
.carousel-control .glyphicon-chevron-left, .carousel-control .glyphicon-chevron-right, .carousel-control .icon-next, .carousel-control .icon-prev {
position: absolute;
top: 50%;
z-index: 5;
display: inline-block;
margin-top: -10px
}
.carousel-control .glyphicon-chevron-left, .carousel-control .icon-prev {
left: 50%;
margin-left: -10px
}
.carousel-control .glyphicon-chevron-right, .carousel-control .icon-next {
right: 50%;
margin-right: -10px
}
.carousel-control .icon-next, .carousel-control .icon-prev {
width: 20px;
height: 20px;
font-family: serif;
line-height: 1
}
.carousel-control .icon-prev:before {
content: "\2039"
}
.carousel-control .icon-next:before {
content: "\203a"
}
.carousel-indicators {
position: absolute;
bottom: 10px;
left: 50%;
z-index: 15;
width: 60%;
padding-left: 0;
margin-left: -30%;
text-align: center;
list-style: none
}
.carousel-indicators li {
display: inline-block;
width: 10px;
height: 10px;
margin: 1px;
text-indent: -999px;
cursor: pointer;
background-color: rgba(0,0,0,0);
border: 1px solid #fff;
border-radius: 10px
}
.carousel-indicators .active {
width: 12px;
height: 12px;
margin: 0;
background-color: #fff
}
.carousel-caption {
position: absolute;
right: 15%;
bottom: 20px;
left: 15%;
z-index: 10;
padding-top: 20px;
padding-bottom: 20px;
color: #fff;
text-align: center;
text-shadow: 0 1px 2px rgba(0,0,0,.6)
}
.carousel-caption .btn {
text-shadow: none
}
@media screen and (min-width:768px) {
.carousel-control .glyphicon-chevron-left, .carousel-control .glyphicon-chevron-right, .carousel-control .icon-next, .carousel-control .icon-prev {
width: 30px;
height: 30px;
margin-top: -10px;
font-size: 30px
}
.carousel-control .glyphicon-chevron-left, .carousel-control .icon-prev {
margin-left: -10px
}
.carousel-control .glyphicon-chevron-right, .carousel-control .icon-next {
margin-right: -10px
}
.carousel-caption {
right: 20%;
left: 20%;
padding-bottom: 30px
}
.carousel-indicators {
bottom: 20px
}
}
.btn-group-vertical > .btn-group:after, .btn-group-vertical > .btn-group:before, .btn-toolbar:after, .btn-toolbar:before, .clearfix:after, .clearfix:before, .container-fluid:after, .container-fluid:before, .container:after, .container:before, .dl-horizontal dd:after, .dl-horizontal dd:before, .form-horizontal .form-group:after, .form-horizontal .form-group:before, .modal-footer:after, .modal-footer:before, .modal-header:after, .modal-header:before, .nav:after, .nav:before, .navbar-collapse:after, .navbar-collapse:before, .navbar-header:after, .navbar-header:before, .navbar:after, .navbar:before, .pager:after, .pager:before, .panel-body:after, .panel-body:before, .row:after, .row:before {
display: table;
content: " "
}
.btn-group-vertical > .btn-group:after, .btn-toolbar:after, .clearfix:after, .container-fluid:after, .container:after, .dl-horizontal dd:after, .form-horizontal .form-group:after, .modal-footer:after, .modal-header:after, .nav:after, .navbar-collapse:after, .navbar-header:after, .navbar:after, .pager:after, .panel-body:after, .row:after {
clear: both
}
.center-block {
display: block;
margin-right: auto;
margin-left: auto
}
.pull-right {
float: right !important
}
.pull-left {
float: left !important
}
.hide {
display: none !important
}
.show {
display: block !important
}
.invisible {
visibility: hidden
}
.text-hide {
font: 0/0 a;
color: transparent;
text-shadow: none;
background-color: transparent;
border: 0
}
.hidden {
display: none !important
}
.affix {
position: fixed
}
@-ms-viewport {
width: device-width
}
.visible-lg, .visible-lg-block, .visible-lg-inline, .visible-lg-inline-block, .visible-md, .visible-md-block, .visible-md-inline, .visible-md-inline-block, .visible-sm, .visible-sm-block, .visible-sm-inline, .visible-sm-inline-block, .visible-xs, .visible-xs-block, .visible-xs-inline, .visible-xs-inline-block {
display: none !important
}
@media (max-width:767px) {
.visible-xs {
display: block !important
}
table.visible-xs {
display: table !important
}
tr.visible-xs {
display: table-row !important
}
td.visible-xs, th.visible-xs {
display: table-cell !important
}
.visible-xs-block {
display: block !important
}
.visible-xs-inline {
display: inline !important
}
.visible-xs-inline-block {
display: inline-block !important
}
}
@media (min-width:768px) and (max-width:991px) {
.visible-sm {
display: block !important
}
table.visible-sm {
display: table !important
}
tr.visible-sm {
display: table-row !important
}
td.visible-sm, th.visible-sm {
display: table-cell !important
}
.visible-sm-block {
display: block !important
}
.visible-sm-inline {
display: inline !important
}
.visible-sm-inline-block {
display: inline-block !important
}
}
@media (min-width:992px) and (max-width:1199px) {
.visible-md {
display: block !important
}
table.visible-md {
display: table !important
}
tr.visible-md {
display: table-row !important
}
td.visible-md, th.visible-md {
display: table-cell !important
}
.visible-md-block {
display: block !important
}
.visible-md-inline {
display: inline !important
}
.visible-md-inline-block {
display: inline-block !important
}
}
@media (min-width:1200px) {
.visible-lg {
display: block !important
}
table.visible-lg {
display: table !important
}
tr.visible-lg {
display: table-row !important
}
td.visible-lg, th.visible-lg {
display: table-cell !important
}
.visible-lg-block {
display: block !important
}
.visible-lg-inline {
display: inline !important
}
.visible-lg-inline-block {
display: inline-block !important
}
.hidden-lg {
display: none !important
}
}
@media (max-width:767px) {
.hidden-xs {
display: none !important
}
}
@media (min-width:768px) and (max-width:991px) {
.hidden-sm {
display: none !important
}
}
@media (min-width:992px) and (max-width:1199px) {
.hidden-md {
display: none !important
}
}
.visible-print {
display: none !important
}
@media print {
.visible-print {
display: block !important
}
table.visible-print {
display: table !important
}
tr.visible-print {
display: table-row !important
}
td.visible-print, th.visible-print {
display: table-cell !important
}
}
.visible-print-block {
display: none !important
}
@media print {
.visible-print-block {
display: block !important
}
}
.visible-print-inline {
display: none !important
}
@media print {
.visible-print-inline {
display: inline !important
}
}
.visible-print-inline-block {
display: none !important
}
@media print {
.visible-print-inline-block {
display: inline-block !important
}
.hidden-print {
display: none !important
}
}
.hljs {
display: block;
background: #fff;
padding: .5em;
color: #333;
overflow-x: auto
}
.hljs-comment, .hljs-meta {
color: #969896
}
.hljs-emphasis, .hljs-quote, .hljs-string, .hljs-strong, .hljs-template-variable, .hljs-variable {
color: #df5000
}
.hljs-keyword, .hljs-selector-tag, .hljs-type {
color: #a71d5d
}
.hljs-attribute, .hljs-bullet, .hljs-literal, .hljs-symbol {
color: #0086b3
}
.hljs-name, .hljs-section {
color: #63a35c
}
.hljs-tag {
color: #333
}
.hljs-attr, .hljs-selector-attr, .hljs-selector-class, .hljs-selector-id, .hljs-selector-pseudo, .hljs-title {
color: #795da3
}
.hljs-addition {
color: #55a532;
background-color: #eaffea
}
.hljs-deletion {
color: #bd2c00;
background-color: #ffecec
}
.hljs-link {
text-decoration: underline
}
/* COLOR VARIABLES*/
:root {
--header-bg-color: #212121;
--header-ft-color: #fefefe;
--highlight-light: rgb(9, 105, 218);
--highlight-dark: rgb(9, 105, 218);
--accent-dim: #e0e0e0;
--accent-super-dim: #f3f3f3;
--font-color: rgb(31, 35, 40);
--toc-font-color: #222222;
--tab-hover-color: #444444;
--card-box-shadow: 0 1px 2px 0 rgba(61, 65, 68, 0.06), 0 1px 3px 1px rgba(61, 65, 68, 0.16);
--search-box-shadow: 0 1px 2px 0 rgba(41, 45, 48, 0.36), 0 1px 3px 1px rgba(41, 45, 48, 0.46);
--transition: 350ms;
}
body {
background: white;
color: var(--font-color);
font-family: "Roboto", sans-serif;
line-height: 1.5;
font-size: 15px !important;
/* -ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;*/
word-wrap: break-word;
}
body div, body p, body ul {
color: var(--font-color);
font-family: "Roboto", sans-serif !important;
line-height: 1.5 !important;
font-size: 15px !important;
/* -ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;*/
word-wrap: break-word;
}
body, p, blockquote, ul, ol, dl, table, pre, code, tr {
color: var(--font-color);
}
/* HIGHLIGHT COLOR */
p {
margin: 0 0 12px;
/*text-align: justify;*/
}
button,
a {
color: var(--highlight-dark);
cursor: pointer;
}
button:hover,
button:focus,
a:hover,
a:focus {
color: var(--highlight-light);
text-decoration: none;
}
.toc .nav > li.active > a {
color: var(--highlight-dark);
}
.toc .nav > li.active > a:hover,
.toc .nav > li.active > a:focus {
color: var(--highlight-light);
}
/*.pagination > .active > a {
background-color: var(--header-bg-color);
border-color: var(--header-bg-color);
}
.pagination > .active > a,
.pagination > .active > a:focus,
.pagination > .active > a:hover,
.pagination > .active > span,
.pagination > .active > span:focus,
.pagination > .active > span:hover {
background-color: var(--highlight-light);
border-color: var(--highlight-light);
}
*/
/* HEADINGS */
h1 {
font-weight: 300;
font-size: 34px;
color: #000000b0;
}
h2 {
font-weight: 500;
font-size: 22px;
line-height: 1.8;
}
h3 {
font-weight: 400;
font-size: 18px;
line-height: 1.8;
}
h4 {
font-weight: 500;
}
h5 {
font-size: 14px;
font-weight: 500;
padding: 10px 0px;
}
.h6, h6 {
font-size: 13px;
}
article h1 {
margin-top: 45px;
margin-bottom: 20px;
}
article > h1:first-child {
margin-top: 10px;
}
article h2,
article h3,
article h4 {
margin-top: 25px;
margin-bottom: 10px;
}
article h2 {
margin-top: 35px;
}
article h3 {
margin-top: 30px;
}
article h4 {
padding-bottom: 6px;
border-bottom: 1px solid #ddd;
font-weight: 500;
font-size: 17px;
}
th {
font-weight: 500;
}
h1 + h2, h2 + h3, h3 + h4 {
margin-top: 0;
}
article ul {
margin-bottom: 12px;
}
article li {
margin-bottom: 4px;
}
#fields, #properties, #methods, #events {
font-weight: 500;
margin-top: 2em;
}
/* SIDEBAR */
.toc .nav > li > a {
color: var(--toc-font-color);
}
.sidefilter {
background-color: #fff;
border-left: none;
border-right: none;
}
.sidefilter {
background-color: #fff;
border-left: none;
border-right: none;
}
.toc-filter {
padding: 5px;
margin: 0;
box-shadow: var(--card-box-shadow);
transition: var(--transition);
opacity: 0.5;
}
.toc-filter:hover, .toc-filter:focus, .toc-filter:focus-within {
opacity: 1;
}
.toc-filter > input {
border: none;
background-color: inherit;
transition: inherit;
}
.toc-filter > .filter-icon {
display: none;
}
.sidetoc > .toc {
background-color: #fff;
overflow-x: unset;
}
.sidetoc {
background-color: #fff;
border: none;
}
.toc {
margin: 0;
padding: 0;
font-size: 14px;
}
.toc .nav > li {
position: relative;
display: block;
margin-bottom: 8px;
text-transform: capitalize;
}
.toc ul {
margin-top: 8px;
margin-left: 10px;
font-size: 14px;
}
.toc:first-child > ul {
margin-top: 0;
}
.toc:first-child > ul:first-child > li {
margin-top: 0;
}
.sidetoc {
overflow-x: auto;
overflow-wrap: normal;
width: 260px;
}
.toc .level1 > li {
font-weight: 400;
margin-top: 8px;
position: relative;
font-size: 14px;
}
.toc .level2 {
font-weight: normal;
font-size: 14px;
}
.toc UL.level2 {
margin: 8px 0 0 15px;
font-size: 14px;
}
.expand-stub {
left: -10px;
font-family: "WebComponentsIcons";
}
.toc .nav > li.active > .expand-stub::before,
.toc .nav > li.in > .expand-stub::before,
.toc .nav > li.in.active > .expand-stub::before,
.toc .nav > li.filtered > .expand-stub::before {
content: "\e015";
}
.toc .nav > li > .expand-stub::before,
.toc .nav > li.active > .expand-stub::before {
content: "\e014";
}
/* ALERTS */
.alert {
padding: 0px 0px 5px 0px;
margin-top: 20px;
color: inherit;
background-color: inherit;
border: none;
box-shadow: var(--card-box-shadow);
}
.alert > p {
margin-bottom: 0;
padding: 5px 10px;
}
.alert > ul {
margin-bottom: 0;
padding: 5px 40px;
}
.alert > h5 {
padding: 10px 15px;
margin-top: 0;
text-transform: uppercase;
font-weight: 500;
border-radius: 4px 4px 0 0;
}
.alert-info > h5 {
color: #1976d2;
border-bottom: 4px solid #1976d2;
background-color: #e3f2fd;
}
.alert-warning > h5 {
color: #f57f17;
border-bottom: 4px solid #f57f17;
background-color: #fff3e0;
}
.alert-danger > h5 {
color: #d32f2f;
border-bottom: 4px solid #d32f2f;
background-color: #ffebee;
}
/* CODE HIGHLIGHT */
pre {
padding: 9.5px;
margin: 20px 0;
font-size: 15px;
word-break: break-all;
word-wrap: break-word;
background-color: #f8f8f7;
border-radius: 4px;
border: none;
box-shadow: var(--card-box-shadow);
}
/* STYLE FOR IMAGES */
.article .small-image {
margin-top: 15px;
box-shadow: var(--card-box-shadow);
max-width: 350px;
}
.article .medium-image {
margin-top: 15px;
box-shadow: var(--card-box-shadow);
max-width: 550px;
}
.article .large-image {
margin-top: 15px;
box-shadow: var(--card-box-shadow);
max-width: 700px;
}
*, :after, :before {
box-sizing: border-box;
}
header {
background-color: var(--header-bg-color);
}
.article {
margin-top: 200px;
margin-bottom: 115px;
}
.sidefilter {
top: 200px;
margin: 0;
padding: 0;
width: 260px;
}
.toc-filter {
border-radius: 5px;
background: unset;
color: unset;
padding: 0;
position: relative;
margin: 0;
}
#toc_filter_input {
width: 100%;
height: 26px;
padding: 6px 12px;
font-size: 14px;
line-height: 1.42857143;
border: 1px solid #ccc;
}
.sidetoc {
top: 240px;
}
.navbar-brand {
height: unset;
}
.hidden-sm.col-md-2 {
padding-left: 0;
}
.sideaffix {
top: 150px;
font-size: 13px;
max-width: calc(12% - 5px);
margin-left: 5px;
}
.affix {
text-transform: capitalize;
}
.affix ul > li > a {
text-transform: capitalize;
}
.affix ul > li > a {
padding-top: 3px;
padding-right: 12px;
padding-bottom: 3px;
padding-left: 14px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.affix h5 {
font-weight: 500;
}
.affix ul ul > li > a:before {
top: unset;
}
.article.grid-right {
margin-left: 280px;
}
@media only screen and (max-width: 768px) {
.article.grid-right {
margin-left: 0;
}
}
.sdkversion {
color: var(--highlight-dark);
font-weight: 500;
}
#search .form-control {
width: 100%;
height: 26px;
/* padding: 6px 12px; */
font-size: 14px;
line-height: 1.42857143;
border: 1px solid #ccc;
}
.container #breadcrumb {
float: left;
}
.breadcrumb li {
font-size: 14px;
text-transform: capitalize;
}
.opspan {
text-transform: uppercase;
padding: 3px 10px;
color: white;
font-weight: bold;
border-radius: 5px;
}
.opspan.opspan-post {
background: #49cc90;
}
.opspan.opspan-put {
background: #fca130;
}
.opspan.opspan-delete {
background: #f93e3e;
}
.opspan.opspan-get {
background: #61affe;
}
.opspan.opspan-patch {
background: #50e3c2;
}
.opspan.opspan-head {
background: #9012fe;
}
.opspan.opspan-options {
background: #0d5aa7;
}
.grad-bottom {
background: linear-gradient(rgb(0 0 0 / 0%), rgb(0 0 0 / 8%));
height: 5px;
}
.footer {
border-top: 1px solid #ededed;
background-color: #111;
padding: 15px 0;
color: #757575;
}
.footer a.lnk {
color: #9e9e9e;
}
.refdoc pre {
margin: 0;
}
.refdoc h4 {
margin-top: 40px;
}
.refdoc h4 {
margin-top: 50px;
padding-bottom: 3px;
}
.refdoc h5 {
padding-bottom: 3px;
margin-top: 16px;
}
.hljs-keyword {
color: rgb(86,156,214);
}
.hljs-string, .hljs-meta-string {
color: rgb(214, 157, 133);
}
.card.fw-horizontal {
text-align: left;
padding: 1.4rem;
flex-direction: row;
border-radius: 0.75rem;
box-shadow: 3px 3px 13px 0px rgb(255 255 255 / 42%);
/* height: 100%; */
align-items: flex-start;
gap: 1rem;
margin: 1rem 1rem;
max-height: 12rem;
}
@media (min-width: 768px) {
.card.fw-horizontal:first-child {
width: calc(66.66666667% + 2rem);
}
}
@media (min-width: 990px) {
.card.fw-horizontal:first-child {
margin: 1rem;
width: 25%;
}
}
.card {
padding: 1rem 2rem;
margin-bottom: 1rem;
}
.card {
position: relative;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
flex-direction: column;
min-width: 0;
word-wrap: break-word;
/* background-color: var(--fw-card-bg); */
/* background-clip: border-box; */
border: 1px solid var(--fw-card-border-color);
border-radius: 0.5rem;
background: linear-gradient(12deg, #e3e3e3, white);
align-items: center;
margin: 1rem;
box-shadow: 3px 3px 10px 0px rgb(99 113 119 / 17%);
}
.card .category-icon {
height: 4.5rem;
margin: 0 0.2em 0.6rem 0;
}
.card.fw-horizontal .fw-category-icon {
width: 4.5rem;
margin: 0.2rem 0 0 0;
}
.card-body {
color: var(--fw-card-fg);
}
.card.fw-horizontal .card-title {
margin-bottom: 0.3rem;
padding-bottom: 0;
}
.cardRow {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
align-content: inherit;
align-items: stretch;
/* gap: 2rem; */
margin-bottom: 70px;
}
.card.fw-horizontal .card-body {
width: 75%;
overflow: hidden;
max-height: 100%;
}
.card-text {
color: var(--font-color);
font-size: 15px;
overflow: hidden;
}
H4.card-title {
margin-top: 0;
line-height: 1.3;
border: none;
}
H2.card-title {
margin-top: 0;
line-height: 1.2;
margin-bottom: 0.4rem;
}
.landingBackground {
background-image: url(../images/books_background.jpg);
position: absolute;
top: -50px;
left: 0;
right: 0;
height: 350px;
background-position: center center;
-webkit-background-size: cover;
background-size: cover;
z-index: -1;
}
.landingBackground.searchResults {
height: 220px;
}
.landing-search {
margin-top: 180px;
margin-bottom: 20px;
}
@media only screen and (max-width: 990px) {
.landingBackground {
top: 0px;
height: 410px;
}
.landingBackground.searchResults {
height: 180px;
}
.landing-search {
margin-top: 140px;
}
}
@media only screen and (max-width: 950px) {
.landing-search {
margin-top: 120px;
margin-bottom: 0;
}
.landingBackground {
top: -30px;
height: 420px;
}
}
@media only screen and (max-width: 900px) {
.landing-search {
margin-top: 80px;
margin-bottom: 0;
}
.landingBackground {
height: 440px;
}
}
@media only screen and (max-width: 768px) {
.landing-search {
margin-top: 20px;
margin-bottom: 40px;
}
.landingBackground.searchResults {
height: 250px;
}
.landingBackground {
height: 250px;
}
}
.search-form {
margin: auto;
width: 32rem;
display: flex !important;
flex-direction: column;
align-items: center;
}
#search.search-form .form-control {
width: unset;
font-size: 24px;
height: 42px;
width: 36rem;
}
.search-heading h2 {
color: white;
font-size: 29px;
white-space: nowrap;
font-weight: 300;
margin-bottom: 0.1rem;
}
.landingPage H1 {
text-align: center;
}
.landingPage #search-results {
margin-top: 60px;
}
.card.fw-horizontal .card-text {
-webkit-line-clamp: 2;
line-clamp: 2;
display: -webkit-inline-box;
-webkit-box-orient: vertical;
}
.landingPage ARTICLE H1:first-child {
display: none;
}
body {
padding: 20px !important;
}
body > div:first-child > hr + p {
display: none;
}
body > div:first-child hr {
display: none;
}
body > div:first-child {
margin-top: 20px;
}
================================================
FILE: global.json
================================================
{
"sdk": {
"version": "10.0.100",
"rollForward": "feature",
"allowPrerelease": false
}
}
================================================
FILE: nuke/.editorconfig
================================================
[*.cs]
dotnet_style_qualification_for_field = false:warning
dotnet_style_qualification_for_property = false:warning
dotnet_style_qualification_for_method = false:warning
dotnet_style_qualification_for_event = false:warning
dotnet_style_require_accessibility_modifiers = never:warning
csharp_style_expression_bodied_methods = true:silent
csharp_style_expression_bodied_properties = true:warning
csharp_style_expression_bodied_indexers = true:warning
csharp_style_expression_bodied_accessors = true:warning
================================================
FILE: nuke/Build.cs
================================================
using Microsoft.Build.Exceptions;
using Nuke.Common;
using Nuke.Common.CI.GitHubActions;
using Nuke.Common.IO;
using Nuke.Common.ProjectModel;
using Nuke.Common.Tooling;
using Nuke.Common.Tools.DotNet;
using Nuke.Common.Tools.GitHub;
using Nuke.Common.Tools.NuGet;
using Nuke.Common.Utilities.Collections;
using Octokit;
using Octokit.Internal;
using Serilog;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using static Nuke.Common.IO.PathConstruction;
using static Nuke.Common.Tools.DotNet.DotNetTasks;
// ReSharper disable ArrangeThisQualifier
class Build : NukeBuild
{
/// Support plugins are available for:
/// - JetBrains ReSharper https://nuke.build/resharper
/// - JetBrains Rider https://nuke.build/rider
/// - Microsoft VisualStudio https://nuke.build/visualstudio
/// - Microsoft VSCode https://nuke.build/vscode
public static int Main() => Execute(x => x.RunUnitTests);
[Nuke.Common.Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")]
readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release;
[Nuke.Common.Parameter("ReleaseNotesFilePath - To determine the lates changelog version")]
readonly AbsolutePath ReleaseNotesFilePath = RootDirectory / "Changelog.md";
[Nuke.Common.Parameter("common.props file path - to determine the configured version")]
readonly AbsolutePath CommonPropsFilePath = RootDirectory / "src" / "common.props";
[Solution]
readonly Solution Solution;
AbsolutePath SourceDirectory => RootDirectory / "src";
AbsolutePath ResultDirectory => RootDirectory / "artifacts";
GitHubActions GitHubActions => GitHubActions.Instance;
IReadOnlyList ChangeLog { get; set; }
ReleaseNotes LatestReleaseNotes { get; set; }
SemVersion SemVersion { get; set; }
string Version { get; set; }
string VersionPostFix { get; set; }
protected override void OnBuildInitialized()
{
var parser = new ReleaseNotesParser();
Log.Debug("Reading ChangeLog {FilePath}...", ReleaseNotesFilePath);
ChangeLog = parser.Parse(File.ReadAllText(ReleaseNotesFilePath));
ChangeLog.NotNull("ChangeLog / ReleaseNotes could not be read!");
LatestReleaseNotes = ChangeLog.First();
LatestReleaseNotes.NotNull("LatestVersion could not be read!");
var propsParser = new CommonPropsParser();
var propsVersion = propsParser.Parse(CommonPropsFilePath);
propsVersion.NotNull("Version from common.props could not be read!");
Assert.True(propsVersion == LatestReleaseNotes.Version,
$"The version in common.props ({propsVersion}) does not " +
$"equal the latest version in the changelog ({LatestReleaseNotes.Version})");
Log.Debug("Using version: {LatestVersion}", propsVersion);
SemVersion = LatestReleaseNotes.SemVersion;
Version = propsVersion.ToString();
if (GitHubActions != null)
{
Log.Debug("Add Version Postfix if under CI - GithubAction(s)...");
var buildNumber = GitHubActions.RunNumber;
if (ScheduledTargets.Contains(Default))
{
VersionPostFix = $"-ci.{buildNumber}";
}
else if (ScheduledTargets.Contains(PrePublish))
{
VersionPostFix = $"-pre.{buildNumber}";
}
}
else if (ScheduledTargets.Contains(PrePublish))
{
VersionPostFix = $"-pre";
}
Log.Information("Building version {Version} with postfix {VersionPostFix}", Version, VersionPostFix);
}
Target Clean => _ => _
.Before(Restore)
.Executes(() =>
{
SourceDirectory.GlobDirectories("**/bin", "**/obj").ForEach(dir => dir.DeleteDirectory());
});
Target Restore => _ => _
.Executes(() =>
{
DotNetRestore(s => s.SetProjectFile(Solution));
});
Target Compile => _ => _
.DependsOn(Restore)
.Executes(() =>
{
DotNetBuild(s => s
.SetProjectFile(Solution)
.SetConfiguration(Configuration)
.SetProperty("GeneratePackageOnBuild", "True")
.SetProperty("VersionPostFix", VersionPostFix ?? string.Empty));
});
Target RunUnitTests => _ => _
.DependsOn(Compile)
.Executes(() =>
{
var TestProject = SourceDirectory / "ElectronNET.IntegrationTests" / "ElectronNET.IntegrationTests.csproj";
DotNetTest(s => s
.SetProjectFile(TestProject)
.SetConfiguration(Configuration)
.When(_ => GitHubActions.Instance is not null, x => x.SetLoggers("GitHubActions"))
);
});
Target PublishPackages => _ => _
.DependsOn(Compile)
.DependsOn(RunUnitTests)
.Executes(() =>
{
var apiKey = Environment.GetEnvironmentVariable("NUGET_API_KEY");
if (apiKey.IsNullOrEmpty())
{
throw new BuildAbortedException("Could not resolve the NuGet API key.");
}
foreach (var nupkg in ResultDirectory.GlobFiles("*.nupkg"))
{
DotNetNuGetPush(s => s
.SetTargetPath(nupkg)
.SetSource("https://api.nuget.org/v3/index.json")
.SetApiKey(apiKey));
}
});
Target PublishPreRelease => _ => _
.DependsOn(PublishPackages)
.Executes(() =>
{
string gitHubToken;
if (GitHubActions != null)
{
gitHubToken = GitHubActions.Token;
}
else
{
gitHubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN");
}
if (gitHubToken.IsNullOrEmpty())
{
throw new BuildAbortedException("Could not resolve GitHub token.");
}
var credentials = new Credentials(gitHubToken);
GitHubTasks.GitHubClient = new GitHubClient(
new ProductHeaderValue(nameof(NukeBuild)),
new InMemoryCredentialStore(credentials));
GitHubTasks.GitHubClient.Repository.Release
.Create("ElectronNET", "Electron.NET", new NewRelease(Version + VersionPostFix)
{
Name = "ElectronNET.Core " + Version + VersionPostFix,
Body = String.Join(Environment.NewLine, LatestReleaseNotes.Notes),
Prerelease = true,
TargetCommitish = "develop",
});
});
Target PublishRelease => _ => _
.DependsOn(PublishPackages)
.Executes(() =>
{
string gitHubToken;
if (GitHubActions != null)
{
gitHubToken = GitHubActions.Token;
}
else
{
gitHubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN");
}
if (gitHubToken.IsNullOrEmpty())
{
throw new BuildAbortedException("Could not resolve GitHub token.");
}
var credentials = new Credentials(gitHubToken);
GitHubTasks.GitHubClient = new GitHubClient(
new ProductHeaderValue(nameof(NukeBuild)),
new InMemoryCredentialStore(credentials));
GitHubTasks.GitHubClient.Repository.Release
.Create("ElectronNET", "Electron.NET", new NewRelease(Version)
{
Name = "ElectronNET.Core " + Version,
Body = String.Join(Environment.NewLine, LatestReleaseNotes.Notes),
Prerelease = false,
TargetCommitish = "main",
});
});
Target Package => _ => _
.DependsOn(RunUnitTests)
.DependsOn(Compile);
Target Default => _ => _
.DependsOn(Package);
Target Publish => _ => _
.DependsOn(PublishRelease);
Target PrePublish => _ => _
.DependsOn(PublishPreRelease);
}
================================================
FILE: nuke/CommonPropsParser.cs
================================================
using System;
using System.Linq;
using System.Xml.Linq;
///
/// Parses a version from an MSBuild .props file (XML).
///
public sealed class CommonPropsParser
{
///
/// Initializes a new instance of the class.
///
public CommonPropsParser()
{
}
public Version Parse(string propsPath)
{
var doc = XDocument.Load(propsPath);
var versionElement = doc
.Descendants()
.FirstOrDefault(e => e.Name.LocalName == "Version");
if (Version.TryParse(versionElement?.Value.Trim(), out var version))
{
version = new Version(version.Major, version.Minor, version.Build);
return version;
}
return null;
}
}
================================================
FILE: nuke/Configuration.cs
================================================
using System;
using System.ComponentModel;
using System.Linq;
using Nuke.Common.Tooling;
[TypeConverter(typeof(TypeConverter))]
public class Configuration : Enumeration
{
public static Configuration Debug = new Configuration { Value = nameof(Debug) };
public static Configuration Release = new Configuration { Value = nameof(Release) };
public static implicit operator string(Configuration configuration)
{
return configuration.Value;
}
}
================================================
FILE: nuke/Directory.Build.props
================================================
================================================
FILE: nuke/Directory.Build.targets
================================================
================================================
FILE: nuke/Extensions/StringExtensions.cs
================================================
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
// ReSharper disable once CheckNamespace
///
/// Contains extension methods for .
///
///
/// Original from Cake build tool source:
/// https://github.com/cake-build/cake/blob/9828d7b246d332054896e52ba56983822feb3f05/src/Cake.Core/Extensions/StringExtensions.cs
///
public static class StringExtensions
{
///
/// Quotes the specified .
///
/// The string to quote.
/// A quoted string.
public static string Quote(this string value)
{
if (!IsQuoted(value))
{
value = string.Concat("\"", value, "\"");
}
return value;
}
///
/// Unquote the specified .
///
/// The string to unquote.
/// An unquoted string.
public static string UnQuote(this string value)
{
if (IsQuoted(value))
{
value = value.Trim('"');
}
return value;
}
///
/// Splits the into lines.
///
/// The string to split.
/// The lines making up the provided string.
public static string[] SplitLines(this string content)
{
content = NormalizeLineEndings(content);
return content.Split(new[] { "\r\n" }, StringSplitOptions.None);
}
///
/// Normalizes the line endings in a .
///
/// The string to normalize line endings in.
/// A with normalized line endings.
public static string NormalizeLineEndings(this string value)
{
if (value != null)
{
value = value.Replace("\r\n", "\n");
value = value.Replace("\r", string.Empty);
return value.Replace("\n", "\r\n");
}
return string.Empty;
}
private static bool IsQuoted(this string value)
{
return value.StartsWith("\"", StringComparison.OrdinalIgnoreCase)
&& value.EndsWith("\"", StringComparison.OrdinalIgnoreCase);
}
}
================================================
FILE: nuke/ReleaseNotes.cs
================================================
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
///
/// Represent release notes.
///
///
/// Original from Cake build tool source:
/// https://github.com/cake-build/cake/blob/9828d7b246d332054896e52ba56983822feb3f05/src/Cake.Common/ReleaseNotes.cs
///
public sealed class ReleaseNotes
{
private readonly List _notes;
///
/// Gets the version.
///
/// The version.
public SemVersion SemVersion { get; }
///
/// Gets the version.
///
/// The version.
public Version Version { get; }
///
/// Gets the release notes.
///
/// The release notes.
public IReadOnlyList Notes => _notes;
///
/// Gets the raw text of the line that was extracted from.
///
/// The raw text of the Version line.
public string RawVersionLine { get; }
///
/// Initializes a new instance of the class.
///
/// The semantic version.
/// The notes.
/// The raw text of the version line.
public ReleaseNotes(SemVersion semVersion, IEnumerable notes, string rawVersionLine)
: this(
semVersion?.AssemblyVersion ?? throw new ArgumentNullException(nameof(semVersion)),
semVersion,
notes,
rawVersionLine)
{
}
///
/// Initializes a new instance of the class.
///
/// The version.
/// The notes.
/// The raw text of the version line.
public ReleaseNotes(Version version, IEnumerable notes, string rawVersionLine)
: this(
version ?? throw new ArgumentNullException(nameof(version)),
new SemVersion(version.Major, version.Minor, version.Build),
notes,
rawVersionLine)
{
}
private ReleaseNotes(Version version, SemVersion semVersion, IEnumerable notes, string rawVersionLine)
{
Version = version ?? throw new ArgumentNullException(nameof(version));
SemVersion = semVersion ?? throw new ArgumentNullException(nameof(semVersion));
RawVersionLine = rawVersionLine;
_notes = new List(notes ?? Enumerable.Empty());
}
}
================================================
FILE: nuke/ReleaseNotesParser.cs
================================================
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.Build.Exceptions;
///
/// The release notes parser.
///
///
/// Original from Cake build tool source:
/// https://github.com/cake-build/cake/blob/9828d7b246d332054896e52ba56983822feb3f05/src/Cake.Common/ReleaseNotesParser.cs
///
public sealed class ReleaseNotesParser
{
private readonly Regex _versionRegex;
///
/// Initializes a new instance of the class.
///
public ReleaseNotesParser()
{
_versionRegex = new Regex(@"(?\d+(\s*\.\s*\d+){0,3})(?-[a-z][0-9a-z-]*)?");
}
///
/// Parses all release notes.
///
/// The content.
/// All release notes.
public IReadOnlyList Parse(string content)
{
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
var lines = content.SplitLines();
if (lines.Length > 0)
{
var line = lines[0].Trim();
if (line.StartsWith("#", StringComparison.OrdinalIgnoreCase))
{
return ParseComplexFormat(lines);
}
if (line.StartsWith("*", StringComparison.OrdinalIgnoreCase))
{
return ParseSimpleFormat(lines);
}
}
throw new BuildAbortedException("Unknown release notes format.");
}
private IReadOnlyList ParseComplexFormat(string[] lines)
{
var lineIndex = 0;
var result = new List();
while (true)
{
if (lineIndex >= lines.Length)
{
break;
}
// Create release notes.
var semVer = SemVersion.Zero;
var version = SemVersion.TryParse(lines[lineIndex], out semVer);
if (!version)
{
throw new BuildAbortedException("Could not parse version from release notes header.");
}
var rawVersionLine = lines[lineIndex];
// Increase the line index.
lineIndex++;
// Parse content.
var notes = new List();
while (true)
{
// Sanity checks.
if (lineIndex >= lines.Length)
{
break;
}
if (lines[lineIndex].StartsWith("# ", StringComparison.OrdinalIgnoreCase))
{
break;
}
// Get the current line.
var line = (lines[lineIndex] ?? string.Empty).Trim();
if (!string.IsNullOrWhiteSpace(line))
{
notes.Add(line);
}
lineIndex++;
}
result.Add(new ReleaseNotes(semVer, notes, rawVersionLine));
}
return result.OrderByDescending(x => x.SemVersion).ToArray();
}
private IReadOnlyList ParseSimpleFormat(string[] lines)
{
var lineIndex = 0;
var result = new List();
while (true)
{
if (lineIndex >= lines.Length)
{
break;
}
// Trim the current line.
var line = (lines[lineIndex] ?? string.Empty);
if (string.IsNullOrWhiteSpace(line))
{
lineIndex++;
continue;
}
// Parse header.
var semVer = SemVersion.Zero;
var version = SemVersion.TryParse(lines[lineIndex], out semVer);
if (!version)
{
throw new BuildAbortedException("Could not parse version from release notes header.");
}
// Parse the description.
line = line.Substring(semVer.ToString().Length).Trim('-', ' ');
// Add the release notes to the result.
result.Add(new ReleaseNotes(semVer, new[] { line }, line));
lineIndex++;
}
return result.OrderByDescending(x => x.SemVersion).ToArray();
}
}
================================================
FILE: nuke/SemVersion.cs
================================================
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
///
/// Class for representing semantic versions.
///
///
/// Original from Cake build tool source:
/// https://github.com/cake-build/cake/blob/9828d7b246d332054896e52ba56983822feb3f05/src/Cake.Common/SemanticVersion.cs
///
public class SemVersion : IComparable, IComparable, IEquatable
{
///
/// Gets the default version of a SemanticVersion.
///
public static SemVersion Zero { get; } = new SemVersion(0, 0, 0, null, null, "0.0.0");
///
/// Regex property for parsing a semantic version number.
///
public static readonly Regex SemVerRegex =
new Regex(
@"(?0|(?:[1-9]\d*))(?:\.(?0|(?:[1-9]\d*))(?:\.(?0|(?:[1-9]\d*)))?(?:\-(?[0-9A-Z\.-]+))?(?:\+(? [0-9A-Z\.-]+))?)?",
RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase);
///
/// Gets the major number of the version.
///
public int Major { get; }
///
/// Gets the minor number of the version.
///
public int Minor { get; }
///
/// Gets the patch number of the version.
///
public int Patch { get; }
///
/// Gets the prerelease of the version.
///
public string PreRelease { get; }
///
/// Gets the meta of the version.
///
public string Meta { get; }
///
/// Gets a value indicating whether semantic version is a prerelease or not.
///
public bool IsPreRelease { get; }
///
/// Gets a value indicating whether semantic version has meta or not.
///
public bool HasMeta { get; }
///
/// Gets the VersionString of the semantic version.
///
public string VersionString { get; }
///
/// Gets the AssemblyVersion of the semantic version.
///
public Version AssemblyVersion { get; }
///
/// Initializes a new instance of the class.
///
/// Major number.
/// Minor number.
/// Patch number.
/// Prerelease string.
/// Meta string.
public SemVersion(int major, int minor, int patch, string preRelease = null, string meta = null) : this(major,
minor, patch, preRelease, meta, null)
{
}
///
/// Initializes a new instance of the class.
///
/// Major number.
/// Minor number.
/// Patch number.
/// Prerelease string.
/// Meta string.
/// The complete version number.
public SemVersion(int major, int minor, int patch, string preRelease, string meta, string versionString)
{
Major = major;
Minor = minor;
Patch = patch;
AssemblyVersion = new Version(major, minor, patch);
IsPreRelease = !string.IsNullOrEmpty(preRelease);
HasMeta = !string.IsNullOrEmpty(meta);
PreRelease = IsPreRelease ? preRelease : null;
Meta = HasMeta ? meta : null;
if (!string.IsNullOrEmpty(versionString))
{
VersionString = versionString;
}
else
{
var sb = new StringBuilder();
sb.AppendFormat(CultureInfo.InvariantCulture, "{0}.{1}.{2}", Major, Minor, Patch);
if (IsPreRelease)
{
sb.AppendFormat(CultureInfo.InvariantCulture, "-{0}", PreRelease);
}
if (HasMeta)
{
sb.AppendFormat(CultureInfo.InvariantCulture, "+{0}", Meta);
}
VersionString = sb.ToString();
}
}
///
/// Method which tries to parse a semantic version string.
///
/// the version that should be parsed.
/// the out parameter the parsed version should be stored in.
/// Returns a boolean indicating if the parse was successful.
public static bool TryParse(string version,
out SemVersion semVersion)
{
semVersion = Zero;
if (string.IsNullOrEmpty(version))
{
return false;
}
var match = SemVerRegex.Match(version);
if (!match.Success)
{
return false;
}
if (!int.TryParse(
match.Groups["Major"].Value,
NumberStyles.Integer,
CultureInfo.InvariantCulture,
out var major) ||
!int.TryParse(
match.Groups["Minor"].Value,
NumberStyles.Integer,
CultureInfo.InvariantCulture,
out var minor) ||
!int.TryParse(
match.Groups["Patch"].Value,
NumberStyles.Integer,
CultureInfo.InvariantCulture,
out var patch))
{
return false;
}
semVersion = new SemVersion(
major,
minor,
patch,
match.Groups["PreRelease"]?.Value,
match.Groups["Meta"]?.Value,
version);
return true;
}
///
/// Checks if two SemVersion objects are equal.
///
/// the other SemVersion want to test equality to.
/// A boolean indicating whether the objecst we're equal or not.
public bool Equals(SemVersion other)
{
return other is object
&& Major == other.Major
&& Minor == other.Minor
&& Patch == other.Patch
&& string.Equals(PreRelease, other.PreRelease, StringComparison.OrdinalIgnoreCase)
&& string.Equals(Meta, other.Meta, StringComparison.OrdinalIgnoreCase);
}
///
/// Compares to SemVersion objects to and another.
///
/// The SemVersion object we compare with.
/// Return 0 if the objects are identical, 1 if the version is newer and -1 if the version is older.
public int CompareTo(SemVersion other)
{
if (other is null)
{
return 1;
}
if (Equals(other))
{
return 0;
}
if (Major > other.Major)
{
return 1;
}
if (Major < other.Major)
{
return -1;
}
if (Minor > other.Minor)
{
return 1;
}
if (Minor < other.Minor)
{
return -1;
}
if (Patch > other.Patch)
{
return 1;
}
if (Patch < other.Patch)
{
return -1;
}
if (IsPreRelease != other.IsPreRelease)
{
return other.IsPreRelease ? 1 : -1;
}
switch (StringComparer.InvariantCultureIgnoreCase.Compare(PreRelease, other.PreRelease))
{
case 1:
return 1;
case -1:
return -1;
default:
{
return (string.IsNullOrEmpty(Meta) != string.IsNullOrEmpty(other.Meta))
? string.IsNullOrEmpty(Meta) ? 1 : -1
: StringComparer.InvariantCultureIgnoreCase.Compare(Meta, other.Meta);
}
}
}
///
/// Compares to SemVersion objects to and another.
///
/// The object we compare with.
/// Return 0 if the objects are identical, 1 if the version is newer and -1 if the version is older.
public int CompareTo(object obj)
{
return (obj is SemVersion semVersion)
? CompareTo(semVersion)
: -1;
}
///
/// Equals-method for the SemVersion class.
///
/// the other SemVersion want to test equality to.
/// A boolean indicating whether the objecst we're equal or not.
public override bool Equals(object obj)
{
return (obj is SemVersion semVersion)
&& Equals(semVersion);
}
///
/// Method for getting the hashcode of the SemVersion object.
///
/// The hashcode of the SemVersion object.
public override int GetHashCode()
{
unchecked
{
var hashCode = Major;
hashCode = (hashCode * 397) ^ Minor;
hashCode = (hashCode * 397) ^ Patch;
hashCode = (hashCode * 397) ^
(PreRelease != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(PreRelease) : 0);
hashCode = (hashCode * 397) ^ (Meta != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(Meta) : 0);
return hashCode;
}
}
///
/// Returns the string representation of an SemVersion object.
///
/// The string representation of the object.
public override string ToString()
{
int[] verParts = { Major, Minor, Patch };
string ver = string.Join(".", verParts);
return $"{ver}{(IsPreRelease ? "-" : string.Empty)}{PreRelease}{Meta}";
}
///
/// The greater than-operator for the SemVersion class.
///
/// first SemVersion.
/// second. SemVersion.
/// A value indicating if the operand1 was greater than operand2.
public static bool operator >(SemVersion operand1, SemVersion operand2)
=> operand1 is { } && operand1.CompareTo(operand2) == 1;
///
/// The less than-operator for the SemVersion class.
///
/// first SemVersion.
/// second. SemVersion.
/// A value indicating if the operand1 was less than operand2.
public static bool operator <(SemVersion operand1, SemVersion operand2)
=> operand1 is { }
? operand1.CompareTo(operand2) == -1
: operand2 is { };
///
/// The greater than or equal to-operator for the SemVersion class.
///
/// first SemVersion.
/// second. SemVersion.
/// A value indicating if the operand1 was greater than or equal to operand2.
public static bool operator >=(SemVersion operand1, SemVersion operand2)
=> operand1 is { }
? operand1.CompareTo(operand2) >= 0
: operand2 is null;
///
/// The lesser than or equal to-operator for the SemVersion class.
///
/// first SemVersion.
/// second. SemVersion.
/// A value indicating if the operand1 was lesser than or equal to operand2.
public static bool operator <=(SemVersion operand1, SemVersion operand2)
=> operand1 is null || operand1.CompareTo(operand2) <= 0;
///
/// The equal to-operator for the SemVersion class.
///
/// first SemVersion.
/// second. SemVersion.
/// A value indicating if the operand1 was equal to operand2.
public static bool operator ==(SemVersion operand1, SemVersion operand2)
=> operand1?.Equals(operand2) ?? operand2 is null;
///
/// The not equal to-operator for the SemVersion class.
///
/// first SemVersion.
/// second. SemVersion.
/// A value indicating if the operand1 was not equal to operand2.
public static bool operator !=(SemVersion operand1, SemVersion operand2)
=> !(operand1?.Equals(operand2) ?? operand2 is null);
}
================================================
FILE: nuke/_build.csproj
================================================
Exe
net10.0
CS0649;CS0169
..
..
1
================================================
FILE: src/.editorconfig
================================================
[*.cs]
# CA1416: Validate platform compatibility
dotnet_diagnostic.CA1416.severity = error
================================================
FILE: src/ElectronNET/.electron/.gitkeep
================================================
================================================
FILE: src/ElectronNET/ElectronNET.csproj
================================================
net6.0;net8.0;net10.0
..\..\artifacts
$(PackageNamePrefix)
$(PackageId)
$(DescriptionFirstPart) This package contains the ElectronNET project system.
false
false
disable
.electron\%(RecursiveDir)%(FileName)%(Extension)
Never
true
.electron\
true
build\
True
MSBuild:Compile
MSBuild:Compile
================================================
FILE: src/ElectronNET/build/ElectronNET.Core.props
================================================
30.4.0
26.0
win-x64
true
$(MSBuildProjectName.Replace(".", "-").ToLower())
electron-builder.json
$(MSBuildProjectName)
false
false
false
$(MSBuildThisFileDirectory)ElectronNET.LateImport.targets
$(CollectUpToDateCheckBuiltDesignTimeDependsOn);ElectronCollectUpToDateCheckBuiltDesignTime
$(FileSystemPublishDependsOn);ElectronFileSystemPublishClearItems
================================================
FILE: src/ElectronNET/build/ElectronNET.Core.targets
================================================
win
$(Title)
$(ElectronPackageId)
<_IsMsAspNetProject>False
<_IsMsAspNetProject Condition="'$(UsingMicrosoftNETSdkWeb)' == 'true'">True
$(ElectronPackageId)
================================================
FILE: src/ElectronNET/build/ElectronNET.DesignTime.targets
================================================
True
Project
Project
$(ElectronSplashScreen.Replace('$(MSBuildProjectDirectory)', '').TrimStart('\'))
$(ElectronIcon.Replace('$(MSBuildProjectDirectory)', '').TrimStart('\'))
<_ProjectDir>$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)'))\
$([System.String]::Substring($(ElectronSplashScreen),
$([System.String]::Length($(_ProjectDir)))))
================================================
FILE: src/ElectronNET/build/ElectronNET.LateImport.targets
================================================
Never
Never
Never
Never
Never
Never
$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)\..\..\ElectronNET.Host'))
$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)\..\.electron'))
.electron
$([System.IO.Path]::GetFileName($(ElectronSplashScreen)))
$([System.IO.Path]::GetFileName($(ElectronIcon)))
$(OutDir)$(ElectronDirName)\
$(TargetDir)$(ElectronDirName)\
$(IntermediateOutputPath)$(ElectronDirName)\
$(ElectronIntermediateOutputPath)package.json
<_ProjectPropertiesFolder>$(MSBuildProjectDirectory)\Properties\
<_ElectronBuilderExpectedPath>$(_ProjectPropertiesFolder)$(ElectronBuilderJson)
<_NeedCopyElectronBuilderFile>True
<_NeedCopyElectronBuilderFile Condition="Exists($(_ElectronBuilderExpectedPath))">False
true
$(TargetName)
$([System.Text.RegularExpressions.Regex]::Replace('$(Version)', '^(\d+\.\d+\.\d+)(?:\.\d+)?(.*)$', '$1$2'))
linux
$(Title)
$(Title.Replace(' ', '-'))
<_NonIntermediatePublishDir>$(PublishDir)
$(PublishDir)
$(IntermediateOutputPath)PubTmp\
false
true
$(ElectronDirName)\%(RecursiveDir)%(FileName)%(Extension)
PreserveNewest
$(ElectronDirName)\%(RelativeDir)%(FileName)%(Extension)
$(ElectronDirName)\%(FileName)%(Extension)
$(ElectronDirName)\%(DestinationRelativePath)
$(ElectronDirName)\%(DestinationRelativePath)
PreserveNewest
<_ElectronFiles Include="$(ElectronSplashScreen)" Condition="'$(ElectronSplashScreen)'!=''" />
<_ElectronFiles Include="$(ElectronIcon)" Condition="'$(ElectronIcon)'!=''" />
<_ElectronFiles Update="**">
PreserveNewest
<_ElectronFilesToCopy Include="@(_ElectronFiles->'%(FullPath)')" />
<_ElectronFilesToCopyWithTargetPath Include="@(_ElectronFilesToCopy)">
$(ElectronDirName)\%(FileName)%(Extension)
<_SourceItemsToCopyToOutputDirectoryIfDifferent Include="@(_ElectronFilesToCopyWithTargetPath)"/>
$(OutDir)$(ElectronDirName)\node_modules\electron-host-hook
x64
ia32
arm64
x64
armv7l
arm64
x64
arm64
win
linux
mac
win32
linux
darwin
$(ElectronArch)
arm
<_CurrentOSPlatform Condition="$([MSBuild]::IsOSPlatform('Windows'))">win
<_CurrentOSPlatform Condition="$([MSBuild]::IsOSPlatform('Linux'))">linux
<_CurrentOSPlatform Condition="$([MSBuild]::IsOSPlatform('OSX'))">mac
false
true
<_IsCrossCompileAllowed>false
<_IsCrossCompileAllowed Condition="'$(_CurrentOSPlatform)' == 'win' AND '$(ElectronPlatform)' == 'linux' AND '$(IsLinuxWsl)' == 'true'">true
<_IsPlatformMismatch>false
<_IsPlatformMismatch Condition="'$(_CurrentOSPlatform)' != '$(ElectronPlatform)' AND '$(_IsCrossCompileAllowed)' != 'true'">true
$([System.IO.Path]::GetFullPath('$(ElectronOutDir)'))
<_NpmCmd>npm install --no-bin-links
<_NpmCmd Condition="'$(_IsPlatformMismatch)' == 'true'">$(_NpmCmd) --os=$(NpmOs) --cpu=$(NpmCpu) --arch=$(NpmCpu) --platform=$(NpmOs)
<_NpmCmd Condition="'$(IsLinuxWsl)' == 'true'">wsl bash -ic '$(_NpmCmd)'
<_ElectronFrameworksDir>$(ElectronOutDir)node_modules\electron\dist\Electron.app\Contents\Frameworks
<_ElectronFrameworkDirs Include="$(_ElectronFrameworksDir)\Electron Framework.framework" />
<_ElectronFrameworkDirs Include="$(_ElectronFrameworksDir)\Mantle.framework" />
<_ElectronFrameworkDirs Include="$(_ElectronFrameworksDir)\ReactiveObjC.framework" />
<_ElectronFrameworkDirs Include="$(_ElectronFrameworksDir)\Squirrel.framework" />
<_OriginalPublishDir>$(PublishDir)
$(_OriginalPublishDir)bin\
true
<_PublishIntermediateOutputPathFiles Remove="@(_PublishIntermediateOutputPathFiles)">
<_ElectronPublishAppAfterTarget Condition="'$(UsingMicrosoftNETSdkWeb)' == 'true'">AfterPublish
<_ElectronPublishAppAfterTarget Condition="'$(UsingMicrosoftNETSdkWeb)' != 'true'">Publish
$(_OriginalPublishDir)
$(_NonIntermediatePublishDir)
$(_OriginalPublishDir)
$(ElectronPublishDir)%(RecursiveDir)%(FileName)%(Extension)
$([System.IO.Path]::GetFullPath('$(ElectronPublishDir)'))
$([System.IO.Path]::GetFullPath('$(PublishUrl.TrimEnd("\"))'))
false
true
<_NpmCmd>npm install electron-builder@$(ElectronBuilderVersion) --save-dev
<_NpmCmd Condition="'$(IsLinuxWsl)' == 'true'">wsl bash -ic '$(_NpmCmd)'
$(ElectronPaParams) -c.appId "$(ElectronPackageId)"
$(ElectronPaParams) -c.buildVersion "$(Version)"
$(ElectronPaParams) -c.copyright "$(Copyright)"
$(ElectronPaParams) -c.extraResources "bin/**/*"
<_NpxCmd>npx electron-builder --config=./$(ElectronBuilderJson) --$(ElectronPlatform) --$(ElectronArch) -c.electronVersion=$(ElectronVersion) -c.directories.output "$(ElectronPublishUrlFullPath)" $(ElectronPaParams)
<_NpxCmd Condition="'$(IsLinuxWsl)' == 'true'">wsl bash -ic '$(_NpxCmd)'
================================================
FILE: src/ElectronNET/build/ElectronNET.MigrationChecks.targets
================================================
ElectronCheckNoPackageJson;
ElectronCheckRootPackageJsonNoElectron;
ElectronCheckRootPackageJsonNotCopied;
ElectronCheckNoManifestJson;
ElectronCheckElectronBuilderJson;
ElectronCheckNoParentPaths;
ElectronCheckPubxmlFiles
<_InvalidPackageJson Include="$(MSBuildProjectDirectory)\**\package.json"
Exclude="$(MSBuildProjectDirectory)\package.json;
$(MSBuildProjectDirectory)\ElectronHostHook\**\package.json;
$(MSBuildProjectDirectory)\bin\**\package.json;
$(MSBuildProjectDirectory)\obj\**\package.json;
$(MSBuildProjectDirectory)\publish\**\package.json;
$(MSBuildProjectDirectory)\node_modules\**\package.json" />
<_InvalidPackageLockJson Include="$(MSBuildProjectDirectory)\**\package-lock.json"
Exclude="$(MSBuildProjectDirectory)\ElectronHostHook\**\package-lock.json;
$(MSBuildProjectDirectory)\bin\**\package-lock.json;
$(MSBuildProjectDirectory)\obj\**\package-lock.json;
$(MSBuildProjectDirectory)\publish\**\package-lock.json;
$(MSBuildProjectDirectory)\node_modules\**\package-lock.json" />
<_HasInvalidPackageJson>false
<_HasInvalidPackageJson Condition="@(_InvalidPackageJson->Count()) > 0 OR @(_InvalidPackageLockJson->Count()) > 0">true
<_RootPackageJsonLines Include="$([System.IO.File]::ReadAllLines('$(MSBuildProjectDirectory)\package.json'))" />
<_RootPackageJsonContent>@(_RootPackageJsonLines, ' ')
<_RootPackageJsonHasElectron>false
<_RootPackageJsonHasElectron Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('$(_RootPackageJsonContent)', 'electron', System.Text.RegularExpressions.RegexOptions.IgnoreCase))">true
<_RootPackageJsonFile Include="@(Content);@(None)"
Condition="'%(Identity)' == 'package.json' OR '%(Identity)' == '$(MSBuildProjectDirectory)\package.json'" />
<_RootPackageJsonIsCopied>false
<_RootPackageJsonIsCopied Condition="'@(_RootPackageJsonFile)' != '' AND ( '%(_RootPackageJsonFile.CopyToOutputDirectory)' != '' OR '%(_RootPackageJsonFile.CopyToPublishDirectory)' != '' )">true
<_InvalidManifestJson Include="$(MSBuildProjectDirectory)\**\electron.manifest.json;$(MSBuildProjectDirectory)\**\electron-manifest.json"
Exclude="$(MSBuildProjectDirectory)\bin\**\*;
$(MSBuildProjectDirectory)\obj\**\*;
$(MSBuildProjectDirectory)\publish\**\*;
$(MSBuildProjectDirectory)\node_modules\**\*" />
<_HasInvalidManifestJson>false
<_HasInvalidManifestJson Condition="@(_InvalidManifestJson->Count()) > 0">true
<_ElectronBuilderJsonInProperties Include="$(MSBuildProjectDirectory)\Properties\electron-builder.json" />
<_HasElectronBuilderJsonInProperties>false
<_HasElectronBuilderJsonInProperties Condition="Exists('$(MSBuildProjectDirectory)\Properties\electron-builder.json')">true
<_ElectronBuilderJsonWrongLocation Include="$(MSBuildProjectDirectory)\**\electron-builder.json"
Exclude="$(MSBuildProjectDirectory)\Properties\electron-builder.json;
$(MSBuildProjectDirectory)\bin\**\*;
$(MSBuildProjectDirectory)\obj\**\*;
$(MSBuildProjectDirectory)\publish\**\*;
$(MSBuildProjectDirectory)\node_modules\**\*" />
<_HasElectronBuilderJsonWrongLocation>false
<_HasElectronBuilderJsonWrongLocation Condition="@(_ElectronBuilderJsonWrongLocation->Count()) > 0">true
<_ElectronBuilderJsonPath>$(MSBuildProjectDirectory)\Properties\electron-builder.json
<_ElectronBuilderJsonContent>@(_ElectronBuilderJsonLines, ' ')
<_HasParentPathReference>false
<_HasParentPathReference Condition="$(_ElectronBuilderJsonContent.Contains('../')) OR $(_ElectronBuilderJsonContent.Contains('..\\'))" >true
<_PubxmlFiles Include="$(MSBuildProjectDirectory)\Properties\PublishProfiles\*.pubxml" />
<_IsAspNetProject>false
<_IsAspNetProject Condition="'$(UsingMicrosoftNETSdkWeb)' == 'true'">true
<_HasPubxmlFiles>false
<_HasPubxmlFiles Condition="@(_PubxmlFiles->Count()) > 0">true
<_PubxmlFileInfo Include="@(_PubxmlFiles)" Condition="'%(Identity)' != ''">
$([System.IO.File]::ReadAllText('%(Identity)'))
<_PubxmlFileInfoWithFlags Include="@(_PubxmlFileInfo)" Condition="'%(Identity)' != ''">
$([System.Text.RegularExpressions.Regex]::IsMatch('%(FileContent)', '<WebPublishMethod>'))
<_AspNetMissingWebPublishMethod Include="@(_PubxmlFileInfoWithFlags)"
Condition="'$(_IsAspNetProject)' == 'true' AND '%(HasWebPublishMethod)' == 'False'" />
<_ConsolePubxmlWithAspNetProperties Include="@(_PubxmlFileInfoWithFlags)"
Condition="'$(_IsAspNetProject)' != 'true' AND '%(HasWebPublishMethod)' == 'True'" />
================================================
FILE: src/ElectronNET/build/ElectronNETRules.Project.xaml
================================================
================================================
FILE: src/ElectronNET/build/ElectronNETRules.Project2.xaml
================================================
================================================
FILE: src/ElectronNET/build/electron-builder.json
================================================
{
"$schema": "https://raw.githubusercontent.com/electron-userland/electron-builder/refs/heads/master/packages/app-builder-lib/scheme.json",
"compression": "maximum",
"linux": {
"target": [
"tar.xz"
],
"executableArgs": [ "--no-sandbox" ],
"artifactName": "${name}-${arch}-${version}.${ext}"
},
"win": {
"target": [
{
"target": "portable",
"arch": "x64"
}
]
}
}
================================================
FILE: src/ElectronNET/build/package.template.json
================================================
{
"name": "$(ElectronPackageId)",
"productName": "$(ElectronTitle)",
"build": {
"appId": "$(ElectronPackageId)",
"linux": {
"desktop": {
"entry": { "Name": "$(Title)" }
},
"executableName": "$(ElectronPackageId)"
},
"deb": {
"desktop": {
"entry": { "Name": "$(Title)" }
}
}
},
"description": "$(Description)",
"version": "$(Version)",
"main": "main.js",
"author": {
"name": "$(Company)"
},
"license": "$(License)",
"executable": "$(TargetName)",
"singleInstance": $(ElectronSingleInstance),
"homepage": "$(ProjectUrl)",
"splashscreen": {
"imageFile": "$(ElectronSplashScreen)"
},
"scripts": {
"start": "tsc -p ."
},
"dependencies": {
"dasherize": "^2.0.0",
"electron-updater": "^6.6.2",
"image-size": "^1.2.1",
"portscanner": "^2.2.0",
"socket.io": "^4.8.1",
"electron-host-hook": "file:./ElectronHostHook"
},
"devDependencies": {
"@types/node": "^22.18",
"electron": "$(ElectronVersion)",
"eslint": "^9.37.0",
"typescript": "^5.9.3"
}
}
================================================
FILE: src/ElectronNET/build/update_electron_versions.py
================================================
#!/usr/bin/env python3
"""
Update Electron version list in ElectronNETRules.Project.xaml
This script:
1. Downloads the Electron releases feed from GitHub
2. Filters for stable releases >= 23.0.0
3. Generates the ElectronVersion enum XML
4. Updates the XAML file with the new version list
"""
import json
import re
import urllib.request
from pathlib import Path
def download_releases():
"""Download Electron releases JSON from GitHub."""
url = "https://releases.electronjs.org/releases.json"
print(f"Downloading releases from {url}...")
with urllib.request.urlopen(url) as response:
data = response.read()
print(f"Downloaded {len(data)} bytes")
return json.loads(data)
def filter_versions(releases, min_version="23.0.0"):
"""Filter and sort stable Electron versions."""
print(f"Filtering versions >= {min_version}...")
# Regular expression for stable versions (major.minor.patch)
stable_pattern = re.compile(r'^\d+\.\d+\.\d+$')
# Parse minimum version
min_parts = tuple(map(int, min_version.split('.')))
# Filter and collect versions
versions = set()
for release in releases:
version = release.get('version', '')
if stable_pattern.match(version):
parts = tuple(map(int, version.split('.')))
if parts >= min_parts:
versions.add(version)
# Sort versions
sorted_versions = sorted(versions, key=lambda v: tuple(map(int, v.split('.'))))
print(f"Found {len(sorted_versions)} stable versions")
return sorted_versions
def generate_enum_xml(versions):
"""Generate the ElectronVersion enum property XML."""
lines = [
' ',
' ',
]
for version in versions:
lines.append(f' ')
lines.append(' ')
return '\n'.join(lines)
def update_xaml_file(xaml_path, enum_xml):
"""Update the XAML file with the new enum."""
print(f"Updating {xaml_path}...")
# Read the original file
content = Path(xaml_path).read_text(encoding='utf-8')
# Find the ElectronVersion enum markers
start_marker = ' invocators;
private readonly ConcurrentDictionary invocationEventNames = new();
private readonly ConcurrentDictionary invocationMessageNames = new();
private readonly ConcurrentDictionary methodMessageNames = new();
private static readonly ConcurrentDictionary eventContainers = new();
private static readonly ConcurrentDictionary> AllInvocators = new();
private readonly object objLock = new object();
public virtual int Id
{
get => -1;
// ReSharper disable once ValueParameterNotUsed
protected set
{
}
}
protected abstract SocketTaskEventNameTypes SocketTaskEventNameType { get; }
protected virtual SocketTaskMessageNameTypes SocketTaskMessageNameType => SocketTaskMessageNameTypes.NoDashUpperFirst;
protected virtual SocketEventNameTypes SocketEventNameType => SocketEventNameTypes.DashedLower;
protected ApiBase()
{
this.objectName = this.GetType().Name.LowerFirst();
this.invocators = AllInvocators.GetOrAdd(this.objectName, _ => new ConcurrentDictionary());
}
protected void CallMethod0([CallerMemberName] string callerName = null)
{
var messageName = this.methodMessageNames.GetOrAdd(callerName, s => this.objectName + s);
if (this.Id >= 0)
{
BridgeConnector.Socket.Emit(messageName, this.Id);
}
else
{
BridgeConnector.Socket.Emit(messageName);
}
}
protected void CallMethod1(object val1, [CallerMemberName] string callerName = null)
{
var messageName = this.methodMessageNames.GetOrAdd(callerName, s => this.objectName + s);
if (this.Id >= 0)
{
BridgeConnector.Socket.Emit(messageName, this.Id, val1);
}
else
{
BridgeConnector.Socket.Emit(messageName, val1);
}
}
protected void CallMethod2(object val1, object val2, [CallerMemberName] string callerName = null)
{
var messageName = this.methodMessageNames.GetOrAdd(callerName, s => this.objectName + s);
if (this.Id >= 0)
{
BridgeConnector.Socket.Emit(messageName, this.Id, val1, val2);
}
else
{
BridgeConnector.Socket.Emit(messageName, val1, val2);
}
}
protected void CallMethod3(object val1, object val2, object val3, [CallerMemberName] string callerName = null)
{
var messageName = this.methodMessageNames.GetOrAdd(callerName, s => this.objectName + s);
if (this.Id >= 0)
{
BridgeConnector.Socket.Emit(messageName, this.Id, val1, val2, val3);
}
else
{
BridgeConnector.Socket.Emit(messageName, val1, val2, val3);
}
}
protected Task InvokeAsync(object arg = null, [CallerMemberName] string callerName = null)
{
return this.InvokeAsyncWithTimeout(InvocationTimeout, arg, callerName);
}
protected Task InvokeAsyncWithTimeout(TimeSpan invocationTimeout, object arg = null, [CallerMemberName] string callerName = null)
{
Debug.Assert(callerName != null, nameof(callerName) + " != null");
lock (this.objLock)
{
return this.invocators.GetOrAdd(callerName, _ =>
{
var getter = new Invocator(this, callerName, invocationTimeout, arg);
getter.Task().ContinueWith(_ =>
{
lock (this.objLock)
{
return this.invocators.TryRemove(callerName, out var _);
}
});
return getter;
}).Task();
}
}
protected void AddEvent(Action value, int? id = null, [CallerMemberName] string callerName = null)
{
Debug.Assert(callerName != null, nameof(callerName) + " != null");
var eventName = this.EventName(callerName);
var eventKey = this.EventKey(eventName, id);
lock (this.objLock)
{
var container = eventContainers.GetOrAdd(eventKey, _ =>
{
var container = new EventContainer();
BridgeConnector.Socket.On(eventKey, container.OnEventAction);
BridgeConnector.Socket.Emit($"register-{eventName}", id);
return container;
});
container.Register(value);
}
}
protected void RemoveEvent(Action value, int? id = null, [CallerMemberName] string callerName = null)
{
Debug.Assert(callerName != null, nameof(callerName) + " != null");
var eventName = this.EventName(callerName);
var eventKey = this.EventKey(eventName, id);
lock (this.objLock)
{
if (eventContainers.TryGetValue(eventKey, out var container) && !container.Unregister(value))
{
BridgeConnector.Socket.Off(eventKey);
eventContainers.TryRemove(eventKey, out _);
}
}
}
protected void AddEvent(Action value, int? id = null, [CallerMemberName] string callerName = null)
{
Debug.Assert(callerName != null, nameof(callerName) + " != null");
var eventName = this.EventName(callerName);
var eventKey = this.EventKey(eventName, id);
lock (this.objLock)
{
var container = eventContainers.GetOrAdd(eventKey, _ =>
{
var container = new EventContainer();
BridgeConnector.Socket.On(eventKey, container.OnEventActionT);
BridgeConnector.Socket.Emit($"register-{eventName}", id);
return container;
});
container.Register(value);
}
}
protected void RemoveEvent(Action value, int? id = null, [CallerMemberName] string callerName = null)
{
Debug.Assert(callerName != null, nameof(callerName) + " != null");
var eventName = this.EventName(callerName);
var eventKey = this.EventKey(eventName, id);
lock (this.objLock)
{
if (eventContainers.TryGetValue(eventKey, out var container) && !container.Unregister(value))
{
BridgeConnector.Socket.Off(eventKey);
eventContainers.TryRemove(eventKey, out _);
}
}
}
private string EventName(string callerName)
{
switch (this.SocketEventNameType)
{
case SocketEventNameTypes.DashedLower:
return $"{this.objectName}-{callerName.ToDashedEventName()}";
case SocketEventNameTypes.CamelCase:
return $"{this.objectName}-{callerName.ToCamelCaseEventName()}";
default:
throw new ArgumentOutOfRangeException();
}
}
private string EventKey(string eventName, int? id)
{
return string.Format(CultureInfo.InvariantCulture, "{0}{1:D}", eventName, id);
}
internal abstract class Invocator
{
public abstract Task Task();
}
internal class Invocator : Invocator
{
private readonly Task tcsTask;
private TaskCompletionSource tcs;
public Invocator(ApiBase apiBase, string callerName, TimeSpan timeout, object arg = null)
{
this.tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
this.tcsTask = this.tcs.Task;
string eventName;
string messageName;
switch (apiBase.SocketTaskEventNameType)
{
case SocketTaskEventNameTypes.DashesLowerFirst:
eventName = apiBase.invocationEventNames.GetOrAdd(callerName, s => $"{apiBase.objectName}-{s.StripAsync().LowerFirst()}-completed");
break;
case SocketTaskEventNameTypes.NoDashUpperFirst:
eventName = apiBase.invocationEventNames.GetOrAdd(callerName, s => $"{apiBase.objectName}{s.StripAsync()}Completed");
break;
default:
throw new ArgumentOutOfRangeException();
}
switch (apiBase.SocketTaskMessageNameType)
{
case SocketTaskMessageNameTypes.DashesLowerFirst:
messageName = apiBase.invocationMessageNames.GetOrAdd(callerName, s => $"{apiBase.objectName}-{s.StripAsync().LowerFirst()}");
break;
case SocketTaskMessageNameTypes.NoDashUpperFirst:
messageName = apiBase.invocationMessageNames.GetOrAdd(callerName, s => apiBase.objectName + s.StripAsync());
break;
default:
throw new ArgumentOutOfRangeException();
}
BridgeConnector.Socket.Once(eventName, (result) =>
{
lock (this)
{
try
{
var value = result;
this.tcs?.SetResult(value);
}
catch (Exception ex)
{
this.tcs?.TrySetException(ex);
}
finally
{
this.tcs = null;
}
}
});
if (arg != null)
{
_ = apiBase.Id >= 0 ? BridgeConnector.Socket.Emit(messageName, apiBase.Id, arg) : BridgeConnector.Socket.Emit(messageName, arg);
}
else
{
_ = apiBase.Id >= 0 ? BridgeConnector.Socket.Emit(messageName, apiBase.Id) : BridgeConnector.Socket.Emit(messageName);
}
System.Threading.Tasks.Task.Delay(timeout).ContinueWith(_ =>
{
if (this.tcs != null)
{
lock (this)
{
if (this.tcs != null)
{
var ex = new TimeoutException(
$"No response after {(long)timeout.TotalMilliseconds}ms trying to retrieve value {apiBase.objectName}.{callerName}()"
);
this.tcs.TrySetException(ex);
this.tcs = null;
}
}
}
});
}
public override Task Task()
{
return this.tcsTask as Task;
}
}
[SuppressMessage("ReSharper", "InconsistentlySynchronizedField")]
private class EventContainer
{
private Action eventAction;
private Delegate eventActionT;
private Action GetEventActionT()
{
return (Action)this.eventActionT;
}
private void SetEventActionT(Action actionT)
{
this.eventActionT = actionT;
}
public void OnEventAction() => this.eventAction?.Invoke();
public void OnEventActionT(T p) => this.GetEventActionT()?.Invoke(p);
public void Register(Action receiver)
{
this.eventAction += receiver;
}
public void Register(Action receiver)
{
var actionT = this.GetEventActionT();
actionT += receiver;
this.SetEventActionT(actionT);
}
public bool Unregister(Action receiver)
{
this.eventAction -= receiver;
return this.eventAction != null;
}
public bool Unregister(Action receiver)
{
var actionT = this.GetEventActionT();
actionT -= receiver;
this.SetEventActionT(actionT);
return actionT != null;
}
}
}
}
================================================
FILE: src/ElectronNET.API/API/App.cs
================================================
using ElectronNET.API.Entities;
using ElectronNET.API.Extensions;
using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
// ReSharper disable InconsistentNaming
namespace ElectronNET.API
{
///
/// Control your application's event lifecycle.
///
public sealed class App : ApiBase
{
protected override SocketTaskEventNameTypes SocketTaskEventNameType => SocketTaskEventNameTypes.NoDashUpperFirst;
protected override SocketEventNameTypes SocketEventNameType => SocketEventNameTypes.DashedLower;
///
/// Emitted when all windows have been closed.
///
/// If you do not subscribe to this event and all windows are closed, the default behavior is to quit
/// the app; however, if you subscribe, you control whether the app quits or not.If the user pressed
/// Cmd + Q, or the developer called , Electron will first try to close all the windows
/// and then emit the event, and in this case the event
/// would not be emitted.
///
public event Action WindowAllClosed
{
add
{
if (_windowAllClosed == null)
{
BridgeConnector.Socket.On("app-window-all-closed" + GetHashCode(), () =>
{
if (!Electron.WindowManager.IsQuitOnWindowAllClosed || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
_windowAllClosed();
}
});
BridgeConnector.Socket.Emit("register-app-window-all-closed", GetHashCode());
}
_windowAllClosed += value;
}
remove
{
_windowAllClosed -= value;
if (_windowAllClosed == null)
BridgeConnector.Socket.Off("app-window-all-closed" + GetHashCode());
}
}
private event Action _windowAllClosed;
///
/// Emitted before the application starts closing its windows.
///
/// Note: If application quit was initiated by then
/// is emitted after emitting close event on all windows and closing them.
///
/// Note: On Windows, this event will not be emitted if the app is closed due to a shutdown/restart of the system or a user logout.
///
public event Func BeforeQuit
{
add
{
if (_beforeQuit == null)
{
BridgeConnector.Socket.On("app-before-quit" + GetHashCode(), async () =>
{
await this._beforeQuit(new QuitEventArgs()).ConfigureAwait(false);
if (_preventQuit)
{
_preventQuit = false;
}
else
{
if (_willQuit == null && _quitting == null)
{
Exit();
}
else if (_willQuit != null)
{
await this._willQuit(new QuitEventArgs()).ConfigureAwait(false);
if (_preventQuit)
{
_preventQuit = false;
}
else
{
if (_quitting == null)
{
Exit();
}
else
{
await this._quitting().ConfigureAwait(false);
Exit();
}
}
}
else if (_quitting != null)
{
await this._quitting().ConfigureAwait(false);
Exit();
}
}
});
BridgeConnector.Socket.Emit("register-app-before-quit", GetHashCode());
}
_beforeQuit += value;
}
remove
{
_beforeQuit -= value;
if (_beforeQuit == null)
BridgeConnector.Socket.Off("app-before-quit" + GetHashCode());
}
}
private event Func _beforeQuit;
///
/// Emitted when all windows have been closed and the application will quit.
///
/// See the description of the event for the differences between the
/// and events.
///
/// Note: On Windows, this event will not be emitted if the app is closed due to a shutdown/restart of the system or a user logout.
///
public event Func WillQuit
{
add
{
if (_willQuit == null)
{
BridgeConnector.Socket.On("app-will-quit" + GetHashCode(), async () =>
{
await this._willQuit(new QuitEventArgs()).ConfigureAwait(false);
if (_preventQuit)
{
_preventQuit = false;
}
else
{
if (_quitting == null)
{
Exit();
}
else
{
await this._quitting().ConfigureAwait(false);
Exit();
}
}
});
BridgeConnector.Socket.Emit("register-app-will-quit", GetHashCode());
}
_willQuit += value;
}
remove
{
_willQuit -= value;
if (_willQuit == null)
BridgeConnector.Socket.Off("app-will-quit" + GetHashCode());
}
}
private event Func _willQuit;
///
/// Emitted when the application is quitting.
///
/// Note: On Windows, this event will not be emitted if the app is closed due to a shutdown/restart of the system or a user logout.
///
public event Func Quitting
{
add
{
if (_quitting == null)
{
BridgeConnector.Socket.On("app-will-quit" + GetHashCode() + "quitting", async () =>
{
if (_willQuit == null)
{
await this._quitting().ConfigureAwait(false);
Exit();
}
});
BridgeConnector.Socket.Emit("register-app-will-quit", GetHashCode() + "quitting");
}
_quitting += value;
}
remove
{
_quitting -= value;
if (_quitting == null)
BridgeConnector.Socket.Off("app-will-quit" + GetHashCode() + "quitting");
}
}
private event Func _quitting;
///
/// Emitted when a blurred.
///
public event Action BrowserWindowBlur
{
add => AddEvent(value, GetHashCode());
remove => RemoveEvent(value, GetHashCode());
}
///
/// Emitted when a gets focused.
///
public event Action BrowserWindowFocus
{
add => AddEvent(value, GetHashCode());
remove => RemoveEvent(value, GetHashCode());
}
///
/// Emitted when a new is created.
///
public event Action BrowserWindowCreated
{
add => AddEvent(value, GetHashCode());
remove => RemoveEvent(value, GetHashCode());
}
///
/// Emitted when a new is created.
///
public event Action WebContentsCreated
{
add => AddEvent(value, GetHashCode());
remove => RemoveEvent(value, GetHashCode());
}
///
/// Emitted when Chrome’s accessibility support changes. This event fires when assistive technologies, such as
/// screen readers, are enabled or disabled. See https://www.chromium.org/developers/design-documents/accessibility for more details.
///
/// when Chrome's accessibility support is enabled, otherwise.
[SupportedOSPlatform("macOS")]
[SupportedOSPlatform("Windows")]
public event Action AccessibilitySupportChanged
{
add => AddEvent(value, GetHashCode());
remove => RemoveEvent(value, GetHashCode());
}
///
/// Emitted when the application has finished basic startup.
///
public event Action Ready
{
add
{
if (IsReady)
{
value();
}
_ready += value;
}
remove
{
_ready -= value;
}
}
private event Action _ready;
///
/// Application host fully started.
///
public bool IsReady
{
get
{
return _isReady;
}
internal set
{
_isReady = value;
if (value)
{
_ready?.Invoke();
}
}
}
private bool _isReady = false;
///
/// Emitted when a MacOS user wants to open a file with the application. The open-file event is usually emitted
/// when the application is already open and the OS wants to reuse the application to open the file.
/// open-file is also emitted when a file is dropped onto the dock and the application is not yet running.
///
/// On Windows, you have to parse the arguments using App.CommandLine to get the filepath.
///
[SupportedOSPlatform("macOS")]
public event Action OpenFile
{
add => AddEvent(value, GetHashCode());
remove => RemoveEvent(value, GetHashCode());
}
///
/// Emitted when a MacOS user wants to open a URL with the application. Your application's Info.plist file must
/// define the URL scheme within the CFBundleURLTypes key, and set NSPrincipalClass to AtomApplication.
///
[SupportedOSPlatform("macOS")]
public event Action OpenUrl
{
add => AddEvent(value, GetHashCode());
remove => RemoveEvent(value, GetHashCode());
}
///
/// A property that indicates the current application's name, which is the name in the
/// application's package.json file.
///
/// Usually the name field of package.json is a short lowercase name, according to the npm modules spec. You
/// should usually also specify a productName field, which is your application's full capitalized name, and
/// which will be preferred over name by Electron.
///
public string Name
{
[Obsolete("Use the asynchronous version NameAsync instead")]
get
{
return NameAsync.Result;
}
set
{
BridgeConnector.Socket.Emit("appSetName", value);
}
}
///
/// A property that indicates the current application's name, which is the name in the
/// application's package.json file.
///
/// Usually the name field of package.json is a short lowercase name, according to the npm modules spec. You
/// should usually also specify a productName field, which is your application's full capitalized name, and
/// which will be preferred over name by Electron.
///
public Task NameAsync
{
get
{
return this.InvokeAsync();
}
}
internal App()
{
CommandLine = new CommandLine();
}
internal static App Instance
{
get
{
if (_app == null)
{
lock (_syncRoot)
{
if (_app == null)
{
_app = new App();
}
}
}
return _app;
}
}
private static App _app;
private static object _syncRoot = new object();
///
/// Try to close all windows. The event will be emitted first. If all windows are successfully
/// closed, the event will be emitted and by default the application will terminate. This method
/// guarantees that all beforeunload and unload event handlers are correctly executed. It is possible
/// that a window cancels the quitting by returning in the beforeunload event handler.
///
public void Quit()
{
this.CallMethod0();
}
///
/// All windows will be closed immediately without asking user and the and
/// events will not be emitted.
///
/// Exits immediately with exitCode. exitCode defaults to 0.
public void Exit(int exitCode = 0)
{
this.CallMethod1(exitCode);
}
public void DisposeSocket()
{
BridgeConnector.Socket.Dispose();
}
///
/// Relaunches the app when current instance exits. By default the new instance will use the same working directory
/// and command line arguments with current instance.
///
/// Note that this method does not quit the app when executed, you have to call or
/// after calling to make the app restart.
///
/// When is called for multiple times, multiple instances will be started after current instance
/// exited.
///
public void Relaunch()
{
this.CallMethod0();
}
///
/// Relaunches the app when current instance exits. By default the new instance will use the same working directory
/// and command line arguments with current instance. When is specified, the
/// will be passed as command line arguments instead. When
/// is specified, the will be executed for relaunch instead of current app.
///
/// Note that this method does not quit the app when executed, you have to call or
/// after calling to make the app restart.
///
/// When is called for multiple times, multiple instances will be started after current instance
/// exited.
///
/// Options for the relaunch.
public void Relaunch(RelaunchOptions relaunchOptions)
{
this.CallMethod1(relaunchOptions);
}
///
/// On Linux, focuses on the first visible window. On macOS, makes the application the active app. On Windows, focuses
/// on the application's first window.
///
public void Focus()
{
this.CallMethod0();
}
///
/// On Linux, focuses on the first visible window. On macOS, makes the application the active app. On Windows, focuses
/// on the application's first window.
///
/// You should seek to use the option as sparingly as possible.
///
public void Focus(FocusOptions focusOptions)
{
this.CallMethod1(focusOptions);
}
///
/// Hides all application windows without minimizing them.
///
[SupportedOSPlatform("macOS")]
public void Hide()
{
this.CallMethod0();
}
///
/// Shows application windows after they were hidden. Does not automatically focus them.
///
[SupportedOSPlatform("macOS")]
public void Show()
{
this.CallMethod0();
}
///
/// The current application directory.
///
public async Task GetAppPathAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
return await this.InvokeAsync().ConfigureAwait(false);
}
///
/// Sets or creates a directory your app's logs which can then be manipulated with
/// or .
///
/// Calling without a path parameter will result in this directory being set to
/// ~/Library/Logs/YourAppName on macOS, and inside the userData directory on Linux and Windows.
///
/// A custom path for your logs. Must be absolute.
public void SetAppLogsPath(string path)
{
this.CallMethod1(path);
}
///
/// The path to a special directory. If is called without called
/// being called first, a default directory will be created equivalent
/// to calling without a path parameter.
///
/// Special directory.
/// The cancellation token.
/// A path to a special directory or file associated with name.
public async Task GetPathAsync(PathName pathName, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var taskCompletionSource = new TaskCompletionSource();
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
{
BridgeConnector.Socket.Once("appGetPathCompleted", taskCompletionSource.SetResult);
BridgeConnector.Socket.Emit("appGetPath", pathName);
return await taskCompletionSource.Task
.ConfigureAwait(false);
}
}
///
/// Overrides the path to a special directory or file associated with name. If the path specifies a directory
/// that does not exist, an Error is thrown. In that case, the directory should be created with fs.mkdirSync or similar.
///
/// You can only override paths of a name defined in .
///
/// By default, web pages' cookies and caches will be stored under the directory. If you
/// want to change this location, you have to override the path before the
/// event of the module is emitted.
/// Special directory.
/// New path to a special directory.
///
public void SetPath(PathName name, string path)
{
this.CallMethod2(name, path);
}
///
/// The version of the loaded application. If no version is found in the application’s package.json file,
/// the version of the current bundle or executable is returned.
///
/// The version of the loaded application.
public async Task GetVersionAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
return await this.InvokeAsync().ConfigureAwait(false);
}
///
/// The current application locale. Possible return values are documented here .
///
/// Note: When distributing your packaged app, you have to also ship the locales folder.
///
/// Note: On Windows, you have to call it after the events gets emitted.
///
/// The current application locale.
public async Task GetLocaleAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
return await this.InvokeAsync().ConfigureAwait(false);
}
///
/// Adds path to the recent documents list. This list is managed by the OS. On Windows you can visit the
/// list from the task bar, and on macOS you can visit it from dock menu.
///
/// Path to add.
[SupportedOSPlatform("macOS")]
[SupportedOSPlatform("Windows")]
public void AddRecentDocument(string path)
{
this.CallMethod1(path);
}
///
/// Clears the recent documents list.
///
[SupportedOSPlatform("macOS")]
[SupportedOSPlatform("Windows")]
public void ClearRecentDocuments()
{
this.CallMethod0();
}
///
/// Sets the current executable as the default handler for a protocol (aka URI scheme). It allows you to
/// integrate your app deeper into the operating system. Once registered, all links with your-protocol://
/// will be opened with the current executable. The whole link, including protocol, will be passed to your
/// application as a parameter.
///
/// Note: On macOS, you can only register protocols that have been added to your app's info.plist, which
/// cannot be modified at runtime. However, you can change the file during build time via
/// Electron Forge ,
/// Electron Packager , or by editing info.plist
/// with a text editor. Please refer to
/// Apple's documentation
/// for details.
///
/// Note: In a Windows Store environment (when packaged as an appx) this API will return true for all calls but
/// the registry key it sets won't be accessible by other applications. In order to register your Windows Store
/// application as a default protocol handler you must declare the protocol in your manifest .
///
/// The API uses the Windows Registry and LSSetDefaultHandlerForURLScheme internally.
///
///
/// The name of your protocol, without ://. For example, if you want your app to handle electron:// links,
/// call this method with electron as the parameter.
/// The cancellation token.
/// Whether the call succeeded.
public async Task SetAsDefaultProtocolClientAsync(string protocol, CancellationToken cancellationToken = default)
{
return await this.SetAsDefaultProtocolClientAsync(protocol, null, null, cancellationToken).ConfigureAwait(false);
}
///
/// Sets the current executable as the default handler for a protocol (aka URI scheme). It allows you to
/// integrate your app deeper into the operating system. Once registered, all links with your-protocol://
/// will be opened with the current executable. The whole link, including protocol, will be passed to your
/// application as a parameter.
///
/// Note: On macOS, you can only register protocols that have been added to your app's info.plist, which
/// cannot be modified at runtime. However, you can change the file during build time via
/// Electron Forge ,
/// Electron Packager , or by editing info.plist
/// with a text editor. Please refer to
/// Apple's documentation
/// for details.
///
/// Note: In a Windows Store environment (when packaged as an appx) this API will return true for all calls but
/// the registry key it sets won't be accessible by other applications. In order to register your Windows Store
/// application as a default protocol handler you must declare the protocol in your manifest .
///
/// The API uses the Windows Registry and LSSetDefaultHandlerForURLScheme internally.
///
///
/// The name of your protocol, without ://. For example, if you want your app to handle electron:// links,
/// call this method with electron as the parameter.
/// The path to the Electron executable. Defaults to process.execPath
/// The cancellation token.
/// Whether the call succeeded.
public async Task SetAsDefaultProtocolClientAsync(string protocol, string path, CancellationToken cancellationToken = default)
{
return await this.SetAsDefaultProtocolClientAsync(protocol, path, null, cancellationToken).ConfigureAwait(false);
}
///
/// Sets the current executable as the default handler for a protocol (aka URI scheme). It allows you to
/// integrate your app deeper into the operating system. Once registered, all links with your-protocol://
/// will be opened with the current executable. The whole link, including protocol, will be passed to your
/// application as a parameter.
///
/// Note: On macOS, you can only register protocols that have been added to your app's info.plist, which
/// cannot be modified at runtime. However, you can change the file during build time via
/// Electron Forge ,
/// Electron Packager , or by editing info.plist
/// with a text editor. Please refer to
/// Apple's documentation
/// for details.
///
/// Note: In a Windows Store environment (when packaged as an appx) this API will return true for all calls but
/// the registry key it sets won't be accessible by other applications. In order to register your Windows Store
/// application as a default protocol handler you must declare the protocol in your manifest .
///
/// The API uses the Windows Registry and LSSetDefaultHandlerForURLScheme internally.
///
///
/// The name of your protocol, without ://. For example, if you want your app to handle electron:// links,
/// call this method with electron as the parameter.
/// The path to the Electron executable. Defaults to process.execPath
/// Arguments passed to the executable. Defaults to an empty array.
/// The cancellation token.
/// Whether the call succeeded.
public async Task SetAsDefaultProtocolClientAsync(string protocol, string path, string[] args, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var taskCompletionSource = new TaskCompletionSource();
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
{
BridgeConnector.Socket.Once("appSetAsDefaultProtocolClientCompleted", taskCompletionSource.SetResult);
BridgeConnector.Socket.Emit("appSetAsDefaultProtocolClient", protocol, path, args);
return await taskCompletionSource.Task
.ConfigureAwait(false);
}
}
///
/// This method checks if the current executable as the default handler for a protocol (aka URI scheme).
/// If so, it will remove the app as the default handler.
///
/// The name of your protocol, without ://.
/// The cancellation token.
/// Whether the call succeeded.
[SupportedOSPlatform("macOS")]
[SupportedOSPlatform("Windows")]
public async Task RemoveAsDefaultProtocolClientAsync(string protocol, CancellationToken cancellationToken = default)
{
return await this.RemoveAsDefaultProtocolClientAsync(protocol, null, null, cancellationToken).ConfigureAwait(false);
}
///
/// This method checks if the current executable as the default handler for a protocol (aka URI scheme).
/// If so, it will remove the app as the default handler.
///
/// The name of your protocol, without ://.
/// Defaults to process.execPath.
/// The cancellation token.
/// Whether the call succeeded.
[SupportedOSPlatform("macOS")]
[SupportedOSPlatform("Windows")]
public async Task RemoveAsDefaultProtocolClientAsync(string protocol, string path, CancellationToken cancellationToken = default)
{
return await this.RemoveAsDefaultProtocolClientAsync(protocol, path, null, cancellationToken).ConfigureAwait(false);
}
///
/// This method checks if the current executable as the default handler for a protocol (aka URI scheme).
/// If so, it will remove the app as the default handler.
///
/// The name of your protocol, without ://.
/// Defaults to process.execPath.
/// Defaults to an empty array.
/// The cancellation token.
/// Whether the call succeeded.
[SupportedOSPlatform("macOS")]
[SupportedOSPlatform("Windows")]
public async Task RemoveAsDefaultProtocolClientAsync(string protocol, string path, string[] args, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var taskCompletionSource = new TaskCompletionSource();
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
{
BridgeConnector.Socket.Once("appRemoveAsDefaultProtocolClientCompleted", taskCompletionSource.SetResult);
BridgeConnector.Socket.Emit("appRemoveAsDefaultProtocolClient", protocol, path, args);
return await taskCompletionSource.Task
.ConfigureAwait(false);
}
}
///
/// This method checks if the current executable is the default handler for a protocol (aka URI scheme).
///
/// Note: On macOS, you can use this method to check if the app has been registered as the default protocol
/// handler for a protocol. You can also verify this by checking ~/Library/Preferences/com.apple.LaunchServices.plist
/// on the macOS machine. Please refer to Apple's documentation
/// for details.
///
/// The API uses the Windows Registry and LSCopyDefaultHandlerForURLScheme internally.
///
/// The name of your protocol, without ://.
/// The cancellation token.
/// Whether the current executable is the default handler for a protocol (aka URI scheme).
public async Task IsDefaultProtocolClientAsync(string protocol, CancellationToken cancellationToken = default)
{
return await this.IsDefaultProtocolClientAsync(protocol, null, null, cancellationToken).ConfigureAwait(false);
}
///
/// This method checks if the current executable is the default handler for a protocol (aka URI scheme).
///
/// Note: On macOS, you can use this method to check if the app has been registered as the default protocol
/// handler for a protocol. You can also verify this by checking ~/Library/Preferences/com.apple.LaunchServices.plist
/// on the macOS machine. Please refer to Apple's documentation
/// for details.
///
/// The API uses the Windows Registry and LSCopyDefaultHandlerForURLScheme internally.
///
/// The name of your protocol, without ://.
/// Defaults to process.execPath.
/// The cancellation token.
/// Whether the current executable is the default handler for a protocol (aka URI scheme).
public async Task IsDefaultProtocolClientAsync(string protocol, string path, CancellationToken cancellationToken = default)
{
return await this.IsDefaultProtocolClientAsync(protocol, path, null, cancellationToken).ConfigureAwait(false);
}
///
/// This method checks if the current executable is the default handler for a protocol (aka URI scheme).
///
/// Note: On macOS, you can use this method to check if the app has been registered as the default protocol
/// handler for a protocol. You can also verify this by checking ~/Library/Preferences/com.apple.LaunchServices.plist
/// on the macOS machine. Please refer to Apple's documentation
/// for details.
///
/// The API uses the Windows Registry and LSCopyDefaultHandlerForURLScheme internally.
///
/// The name of your protocol, without ://.
/// Defaults to process.execPath.
/// Defaults to an empty array.
/// The cancellation token.
/// Whether the current executable is the default handler for a protocol (aka URI scheme).
public async Task IsDefaultProtocolClientAsync(string protocol, string path, string[] args, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var taskCompletionSource = new TaskCompletionSource();
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
{
BridgeConnector.Socket.Once("appIsDefaultProtocolClientCompleted", taskCompletionSource.SetResult);
BridgeConnector.Socket.Emit("appIsDefaultProtocolClient", protocol, path, args);
return await taskCompletionSource.Task
.ConfigureAwait(false);
}
}
///
/// Adds tasks to the category of the JumpList on Windows.
///
/// Note: If you'd like to customize the Jump List even more use instead.
///
/// Array of objects.
/// The cancellation token.
/// Whether the call succeeded.
[SupportedOSPlatform("Windows")]
public async Task SetUserTasksAsync(UserTask[] userTasks, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var taskCompletionSource = new TaskCompletionSource();
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
{
BridgeConnector.Socket.Once("appSetUserTasksCompleted", taskCompletionSource.SetResult);
BridgeConnector.Socket.Emit("appSetUserTasks", userTasks);
return await taskCompletionSource.Task
.ConfigureAwait(false);
}
}
///
/// Jump List settings for the application.
///
/// The cancellation token.
/// Jump List settings.
[SupportedOSPlatform("Windows")]
public async Task GetJumpListSettingsAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
return await this.InvokeAsync().ConfigureAwait(false);
}
///
/// Sets or removes a custom Jump List for the application. If categories is null the previously set custom
/// Jump List (if any) will be replaced by the standard Jump List for the app (managed by Windows).
///
/// Note: If a object has neither the nor
/// the property set then its is assumed
/// to be . If the property is set but
/// the property is omitted then the is
/// assumed to be .
///
/// Note: Users can remove items from custom categories, and Windows will not allow a removed item to be added
/// back into a custom category until after the next successful call to . Any attempt
/// to re-add a removed item to a custom category earlier than that will result in the entire custom category being
/// omitted from the Jump List. The list of removed items can be obtained using .
///
/// Array of objects.
[SupportedOSPlatform("Windows")]
public void SetJumpList(JumpListCategory[] categories)
{
this.CallMethod1(categories);
}
///
/// The return value of this method indicates whether or not this instance of your application successfully obtained
/// the lock. If it failed to obtain the lock, you can assume that another instance of your application is already
/// running with the lock and exit immediately.
///
/// I.e.This method returns if your process is the primary instance of your application and your
/// app should continue loading. It returns if your process should immediately quit as it has
/// sent its parameters to another instance that has already acquired the lock.
///
/// On macOS, the system enforces single instance automatically when users try to open a second instance of your app
/// in Finder, and the open-file and open-url events will be emitted for that.However when users start your app in
/// command line, the system's single instance mechanism will be bypassed, and you have to use this method to ensure
/// single instance.
///
/// Lambda with an array of the second instance’s command line arguments.
/// The second parameter is the working directory path.
/// The cancellation token.
/// This method returns false if your process is the primary instance of the application and your app
/// should continue loading. And returns true if your process has sent its parameters to another instance, and
/// you should immediately quit.
///
public async Task RequestSingleInstanceLockAsync(Action newInstanceOpened, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var taskCompletionSource = new TaskCompletionSource();
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
{
BridgeConnector.Socket.Once("appRequestSingleInstanceLockCompleted", taskCompletionSource.SetResult);
BridgeConnector.Socket.Off("secondInstance");
BridgeConnector.Socket.On("secondInstance", (result) =>
{
var arr = result.EnumerateArray();
var e = arr.GetEnumerator();
e.MoveNext();
var args = e.Current.Deserialize(JsonSerializerOptions.Default);
e.MoveNext();
var workingDirectory = e.Current.GetString();
newInstanceOpened(args, workingDirectory);
});
BridgeConnector.Socket.Emit("appRequestSingleInstanceLock");
return await taskCompletionSource.Task
.ConfigureAwait(false);
}
}
///
/// Releases all locks that were created by makeSingleInstance. This will allow
/// multiple instances of the application to once again run side by side.
///
public void ReleaseSingleInstanceLock()
{
this.CallMethod0();
}
///
/// This method returns whether or not this instance of your app is currently holding the single instance lock.
/// You can request the lock with and release with
/// .
///
/// The cancellation token.
public async Task HasSingleInstanceLockAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
return await this.InvokeAsync().ConfigureAwait(false);
}
///
/// Creates an NSUserActivity and sets it as the current activity. The activity is
/// eligible for Handoff
/// to another device afterward.
///
/// Uniquely identifies the activity. Maps to NSUserActivity.activityType .
/// App-specific state to store for use by another device.
[SupportedOSPlatform("macOS")]
public void SetUserActivity(string type, object userInfo)
{
SetUserActivity(type, userInfo, null);
}
///
/// Creates an NSUserActivity and sets it as the current activity. The activity is
/// eligible for Handoff
/// to another device afterward.
///
///
/// Uniquely identifies the activity. Maps to NSUserActivity.activityType .
///
/// App-specific state to store for use by another device.
///
/// The webpage to load in a browser if no suitable app is installed on the resuming device. The scheme must be http or https.
///
[SupportedOSPlatform("macOS")]
public void SetUserActivity(string type, object userInfo, string webpageUrl)
{
this.CallMethod3(type, userInfo, webpageUrl);
}
///
/// The type of the currently running activity.
///
/// The cancellation token.
[SupportedOSPlatform("macOS")]
public async Task GetCurrentActivityTypeAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
return await this.InvokeAsync().ConfigureAwait(false);
}
///
/// Invalidates the current Handoff user activity.
///
[SupportedOSPlatform("macOS")]
public void InvalidateCurrentActivity()
{
this.CallMethod0();
}
///
/// Marks the current Handoff user activity as inactive without invalidating it.
///
[SupportedOSPlatform("macOS")]
public void ResignCurrentActivity()
{
this.CallMethod0();
}
///
/// Changes the Application User Model ID to id.
///
/// Model Id.
[SupportedOSPlatform("Windows")]
public void SetAppUserModelId(string id)
{
this.CallMethod1(id);
}
/// TODO: Check new parameter which is a function [App.ImportCertificate]
///
/// Imports the certificate in pkcs12 format into the platform certificate store.
/// callback is called with the result of import operation, a value of 0 indicates
/// success while any other value indicates failure according to chromium net_error_list.
///
///
/// The cancellation token.
/// Result of import. Value of 0 indicates success.
[SupportedOSPlatform("Linux")]
public async Task ImportCertificateAsync(ImportCertificateOptions options, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var taskCompletionSource = new TaskCompletionSource();
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
{
BridgeConnector.Socket.Once("appImportCertificateCompleted", taskCompletionSource.SetResult);
BridgeConnector.Socket.Emit("appImportCertificate", options);
return await taskCompletionSource.Task
.ConfigureAwait(false);
}
}
///
/// Memory and cpu usage statistics of all the processes associated with the app.
///
///
/// Array of ProcessMetric objects that correspond to memory and cpu usage
/// statistics of all the processes associated with the app.
/// The cancellation token.
///
public async Task GetAppMetricsAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
return await this.InvokeAsync().ConfigureAwait(false);
}
///
/// The Graphics Feature Status from chrome://gpu/.
///
/// Note: This information is only usable after the gpu-info-update event is emitted.
/// The cancellation token.
///
public async Task GetGpuFeatureStatusAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
return await this.InvokeAsync().ConfigureAwait(false);
}
///
/// Sets the counter badge for current app. Setting the count to 0 will hide the badge.
/// On macOS it shows on the dock icon. On Linux it only works for Unity launcher.
///
/// Note: Unity launcher requires the existence of a .desktop file to work, for more
/// information please read Desktop Environment Integration .
///
/// Counter badge.
/// The cancellation token.
/// Whether the call succeeded.
[SupportedOSPlatform("Linux")]
[SupportedOSPlatform("macOS")]
public async Task SetBadgeCountAsync(int count, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var taskCompletionSource = new TaskCompletionSource();
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
{
BridgeConnector.Socket.Once("appSetBadgeCountCompleted", taskCompletionSource.SetResult);
BridgeConnector.Socket.Emit("appSetBadgeCount", count);
return await taskCompletionSource.Task
.ConfigureAwait(false);
}
}
///
/// The current value displayed in the counter badge.
///
/// The cancellation token.
[SupportedOSPlatform("Linux")]
[SupportedOSPlatform("macOS")]
public async Task GetBadgeCountAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
return await this.InvokeAsync().ConfigureAwait(false);
}
///
/// A object that allows you to read and manipulate the command line arguments that Chromium uses.
///
public CommandLine CommandLine { get; internal set; }
///
/// Whether the current desktop environment is Unity launcher.
///
/// The cancellation token.
[SupportedOSPlatform("Linux")]
public async Task IsUnityRunningAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
return await this.InvokeAsync().ConfigureAwait(false);
}
///
/// If you provided path and args options to then you need to pass the same
/// arguments here for to be set correctly.
///
[SupportedOSPlatform("macOS")]
[SupportedOSPlatform("Windows")]
public async Task GetLoginItemSettingsAsync(CancellationToken cancellationToken = default)
{
return await this.GetLoginItemSettingsAsync(null, cancellationToken).ConfigureAwait(false);
}
///
/// If you provided path and args options to then you need to pass the same
/// arguments here for to be set correctly.
///
///
/// The cancellation token.
[SupportedOSPlatform("macOS")]
[SupportedOSPlatform("Windows")]
public async Task GetLoginItemSettingsAsync(LoginItemSettingsOptions options, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var taskCompletionSource = new TaskCompletionSource();
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
{
BridgeConnector.Socket.Once("appGetLoginItemSettingsCompleted", taskCompletionSource.SetResult);
if (options == null)
{
BridgeConnector.Socket.Emit("appGetLoginItemSettings");
}
else
{
BridgeConnector.Socket.Emit("appGetLoginItemSettings", options);
}
return await taskCompletionSource.Task
.ConfigureAwait(false);
}
}
///
/// Set the app's login item settings.
/// To work with Electron's autoUpdater on Windows, which uses Squirrel ,
/// you'll want to set the launch path to Update.exe, and pass arguments that specify your application name.
///
///
[SupportedOSPlatform("macOS")]
[SupportedOSPlatform("Windows")]
public void SetLoginItemSettings(LoginSettings loginSettings)
{
this.CallMethod1(loginSettings);
}
///
/// if Chrome's accessibility support is enabled, otherwise. This API will
/// return if the use of assistive technologies, such as screen readers, has been detected.
/// See Chromium's accessibility docs for more details.
///
/// if Chrome’s accessibility support is enabled, otherwise.
[SupportedOSPlatform("macOS")]
[SupportedOSPlatform("Windows")]
public async Task IsAccessibilitySupportEnabledAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
return await this.InvokeAsync().ConfigureAwait(false);
}
///
/// Manually enables Chrome's accessibility support, allowing to expose accessibility switch to users in application settings.
/// See Chromium's accessibility docs for more details.
/// Disabled ( ) by default.
///
/// This API must be called after the event is emitted.
///
/// Note: Rendering accessibility tree can significantly affect the performance of your app. It should not be enabled by default.
///
/// Enable or disable accessibility tree rendering.
[SupportedOSPlatform("macOS")]
[SupportedOSPlatform("Windows")]
public void SetAccessibilitySupportEnabled(bool enabled)
{
this.CallMethod1(enabled);
}
///
/// Show the app's about panel options. These options can be overridden with
/// .
///
public void ShowAboutPanel()
{
this.CallMethod0();
}
///
/// Set the about panel options. This will override the values defined in the app's .plist file on macOS. See the
/// Apple docs
/// for more details. On Linux, values must be set in order to be shown; there are no defaults.
///
/// If you do not set credits but still wish to surface them in your app, AppKit will look for a file named "Credits.html",
/// "Credits.rtf", and "Credits.rtfd", in that order, in the bundle returned by the NSBundle class method main. The first file
/// found is used, and if none is found, the info area is left blank. See Apple
/// documentation for more information.
///
/// About panel options.
public void SetAboutPanelOptions(AboutPanelOptions options)
{
this.CallMethod1(options);
}
///
/// A which is the user agent string Electron will use as a global fallback.
///
/// This is the user agent that will be used when no user agent is set at the webContents or
/// session level. It is useful for ensuring that your entire app has the same user agent. Set to a
/// custom value as early as possible in your app's initialization to ensure that your overridden value
/// is used.
///
public string UserAgentFallback
{
[Obsolete("Use the asynchronous version UserAgentFallbackAsync instead")]
get
{
return UserAgentFallbackAsync.Result;
}
set
{
BridgeConnector.Socket.Emit("appSetUserAgentFallback", value);
}
}
///
/// A which is the user agent string Electron will use as a global fallback.
///
/// This is the user agent that will be used when no user agent is set at the webContents or
/// session level. It is useful for ensuring that your entire app has the same user agent. Set to a
/// custom value as early as possible in your app's initialization to ensure that your overridden value
/// is used.
///
public Task UserAgentFallbackAsync
{
get
{
return Task.Run(() =>
{
var taskCompletionSource = new TaskCompletionSource();
BridgeConnector.Socket.Once("appGetUserAgentFallbackCompleted", taskCompletionSource.SetResult);
BridgeConnector.Socket.Emit("appGetUserAgentFallback");
return taskCompletionSource.Task;
});
}
}
internal void PreventQuit()
{
_preventQuit = true;
}
private bool _preventQuit = false;
private const string ModuleName = "app";
///
/// Subscribe to an unmapped event on the module.
///
/// The event name
/// The handler
public void On(string eventName, Action action)
=> Events.Instance.On(ModuleName, eventName, action);
///
/// Subscribe to an unmapped event on the module.
///
/// The event name
/// The handler
public async Task On(string eventName, Action action)
=> await Events.Instance.On(ModuleName, eventName, action).ConfigureAwait(false);
///
/// Subscribe to an unmapped event on the module once.
///
/// The event name
/// The handler
public void Once(string eventName, Action action)
=> Events.Instance.Once(ModuleName, eventName, action);
///
/// Subscribe to an unmapped event on the module once.
///
/// The event name
/// The handler
public async Task Once(string eventName, Action action)
=> await Events.Instance.Once(ModuleName, eventName, action).ConfigureAwait(false);
}
}
================================================
FILE: src/ElectronNET.API/API/AutoUpdater.cs
================================================
using ElectronNET.API.Entities;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
// ReSharper disable InconsistentNaming
namespace ElectronNET.API
{
///
/// Enable apps to automatically update themselves. Based on electron-updater.
///
public sealed class AutoUpdater : ApiBase
{
protected override SocketTaskEventNameTypes SocketTaskEventNameType => SocketTaskEventNameTypes.DashesLowerFirst;
protected override SocketTaskMessageNameTypes SocketTaskMessageNameType => SocketTaskMessageNameTypes.DashesLowerFirst;
protected override SocketEventNameTypes SocketEventNameType => SocketEventNameTypes.DashedLower;
///
/// Whether to automatically download an update when it is found. (Default is true)
///
public bool AutoDownload
{
get
{
return Task.Run(() => this.InvokeAsync()).Result;
}
set
{
BridgeConnector.Socket.Emit("autoUpdater-autoDownload-set", value);
}
}
///
/// Whether to automatically install a downloaded update on app quit (if `QuitAndInstall` was not called before).
///
/// Applicable only on Windows and Linux.
///
public bool AutoInstallOnAppQuit
{
get
{
return Task.Run(() => this.InvokeAsync()).Result;
}
set
{
BridgeConnector.Socket.Emit("autoUpdater-autoInstallOnAppQuit-set", value);
}
}
///