Skip to content

Commit

Permalink
feat: allowing additional entries in .desktop file, such as `[Desktop…
Browse files Browse the repository at this point in the history
… Actions <actionName>]` (#8572)
mmaietta authored Oct 9, 2024
1 parent a25d04d commit 0dbe357
Showing 10 changed files with 175 additions and 37 deletions.
5 changes: 5 additions & 0 deletions .changeset/sweet-masks-sparkle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"app-builder-lib": major
---

feat: allowing additional entries in .desktop file, such as `[Desktop Actions <actionName>]`. Requires changing configuration `desktop` property to object to be more extensible in the future
89 changes: 83 additions & 6 deletions packages/app-builder-lib/scheme.json
Original file line number Diff line number Diff line change
@@ -58,7 +58,15 @@
]
},
"desktop": {
"description": "The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files) entries (name to value)."
"anyOf": [
{
"$ref": "#/definitions/LinuxDesktopFile"
},
{
"type": "null"
}
],
"description": "The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files)"
},
"executableArgs": {
"anyOf": [
@@ -576,7 +584,15 @@
]
},
"desktop": {
"description": "The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files) entries (name to value)."
"anyOf": [
{
"$ref": "#/definitions/LinuxDesktopFile"
},
{
"type": "null"
}
],
"description": "The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files)"
},
"executableArgs": {
"anyOf": [
@@ -1111,7 +1127,15 @@
]
},
"desktop": {
"description": "The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files) entries (name to value)."
"anyOf": [
{
"$ref": "#/definitions/LinuxDesktopFile"
},
{
"type": "null"
}
],
"description": "The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files)"
},
"executableArgs": {
"anyOf": [
@@ -1645,7 +1669,15 @@
]
},
"desktop": {
"description": "The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files) entries (name to value)."
"anyOf": [
{
"$ref": "#/definitions/LinuxDesktopFile"
},
{
"type": "null"
}
],
"description": "The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files)"
},
"detectUpdateChannel": {
"default": true,
@@ -1941,6 +1973,35 @@
},
"type": "object"
},
"LinuxDesktopFile": {
"additionalProperties": false,
"description": "Example Spec: https://specifications.freedesktop.org/desktop-entry-spec/latest/example.html",
"properties": {
"desktopActions": {
"anyOf": [
{
"typeof": "function"
},
{
"type": "null"
}
],
"description": "`[Desktop Actions <ActionName>]` metadata entries (name to value).\n\nConfig Example:\n```js\ndesktopActions: {\n NewWindow: {\n Name: 'New Window',\n Exec: 'app --new-window',\n }\n}\n```"
},
"entry": {
"anyOf": [
{
"typeof": "function"
},
{
"type": "null"
}
],
"description": "`[Desktop Entry]` metadata entries (name to value). Overwrites default values calculated by electron-builder"
}
},
"type": "object"
},
"LinuxTargetSpecificOptions": {
"additionalProperties": false,
"properties": {
@@ -2010,7 +2071,15 @@
]
},
"desktop": {
"description": "The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files) entries (name to value)."
"anyOf": [
{
"$ref": "#/definitions/LinuxDesktopFile"
},
{
"type": "null"
}
],
"description": "The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files)"
},
"executableArgs": {
"anyOf": [
@@ -5463,7 +5532,15 @@
]
},
"desktop": {
"description": "The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files) entries (name to value)."
"anyOf": [
{
"$ref": "#/definitions/LinuxDesktopFile"
},
{
"type": "null"
}
],
"description": "The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files)"
},
"environment": {
"anyOf": [
2 changes: 1 addition & 1 deletion packages/app-builder-lib/src/index.ts
Original file line number Diff line number Diff line change
@@ -33,7 +33,7 @@ export { MsiOptions } from "./options/MsiOptions"
export { MsiWrappedOptions } from "./options/MsiWrappedOptions"
export { CommonWindowsInstallerConfiguration } from "./options/CommonWindowsInstallerConfiguration"
export { NsisOptions, NsisWebOptions, PortableOptions, CommonNsisOptions, CustomNsisBinary } from "./targets/nsis/nsisOptions"
export { LinuxConfiguration, DebOptions, CommonLinuxOptions, LinuxTargetSpecificOptions, AppImageOptions, FlatpakOptions } from "./options/linuxOptions"
export { LinuxConfiguration, DebOptions, CommonLinuxOptions, LinuxTargetSpecificOptions, AppImageOptions, FlatpakOptions, LinuxDesktopFile } from "./options/linuxOptions"
export { SnapOptions, PlugDescriptor, SlotDescriptor } from "./options/SnapOptions"
export { Metadata, AuthorMetadata, RepositoryInfo } from "./options/metadata"
export { AppInfo } from "./appInfo"
32 changes: 30 additions & 2 deletions packages/app-builder-lib/src/options/linuxOptions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
import { PlatformSpecificBuildOptions, TargetConfigType, TargetSpecificOptions } from "../index"

/**
* Example Spec: https://specifications.freedesktop.org/desktop-entry-spec/latest/example.html
*/
export interface LinuxDesktopFile {
/**
* `[Desktop Entry]` metadata entries (name to value). Overwrites default values calculated by electron-builder
*/
entry?: {
[k: string]: string
} | null
/**
* `[Desktop Actions <ActionName>]` metadata entries (name to value).
*
* Config Example:
* ```js
* desktopActions: {
* NewWindow: {
* Name: 'New Window',
* Exec: 'app --new-window',
* }
* }
* ```
*/
desktopActions?: {
[ActionName: string]: any
} | null
}

export interface LinuxConfiguration extends CommonLinuxOptions, PlatformSpecificBuildOptions {
/**
* Target package type: list of `AppImage`, `flatpak`, `snap`, `deb`, `rpm`, `freebsd`, `pacman`, `p5p`, `apk`, `7z`, `zip`, `tar.xz`, `tar.lz`, `tar.gz`, `tar.bz2`, `dir`.
@@ -56,9 +84,9 @@ export interface CommonLinuxOptions {
readonly mimeTypes?: Array<string> | null

/**
* The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files) entries (name to value).
* The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files)
*/
readonly desktop?: any | null
readonly desktop?: LinuxDesktopFile | null

/**
* The executable parameters. Pass to executableName
15 changes: 13 additions & 2 deletions packages/app-builder-lib/src/targets/LinuxTargetHelper.ts
Original file line number Diff line number Diff line change
@@ -103,7 +103,7 @@ export class LinuxTargetHelper {
throw new Error("Specified exec is empty")
}
// https://github.com/electron-userland/electron-builder/issues/3418
if (targetSpecificOptions.desktop != null && targetSpecificOptions.desktop.Exec != null) {
if (targetSpecificOptions.desktop?.entry?.Exec) {
throw new Error("Please specify executable name as linux.executableName instead of linux.desktop.Exec")
}

@@ -140,7 +140,7 @@ export class LinuxTargetHelper {
// https://github.com/electron/electron/blob/2-0-x/atom/browser/native_window_views.cc#L226
StartupWMClass: appInfo.productName,
...extra,
...targetSpecificOptions.desktop,
...(targetSpecificOptions.desktop?.entry ?? {}),
}

const description = this.getDescription(targetSpecificOptions)
@@ -194,6 +194,17 @@ export class LinuxTargetHelper {
data += `\n${name}=${desktopMeta[name]}`
}
data += "\n"
const desktopActions = targetSpecificOptions.desktop?.desktopActions ?? {}
for (const [actionName, config] of Object.entries(desktopActions)) {
if (!Object.keys(config ?? {}).length) {
continue
}
data += `\n[Desktop Action ${actionName}]`
for (const [key, value] of Object.entries(config ?? {})) {
data += `\n${key}=${value}`
}
data += "\n"
}
return Promise.resolve(data)
}
}
9 changes: 9 additions & 0 deletions test/snapshots/linux/linuxPackagerTest.js.snap
Original file line number Diff line number Diff line change
@@ -11,6 +11,15 @@ StartupWMClass=Test App ßW
X-Foo=bar
Comment=Test Application (test quite “ #378)
Categories=Development;
[Desktop Action Gallery]
Exec=fooview --gallery
Name=Browse Gallery
[Desktop Action Create]
Exec=fooview --create-new
Name=Create a new Foo!
Icon=fooview-new
"
`;

4 changes: 2 additions & 2 deletions test/src/ExtraBuildResourcesTest.ts
Original file line number Diff line number Diff line change
@@ -181,9 +181,9 @@ test.ifNotWindows(
app({
targets: linuxDirTarget,
config: {
electronDist: (_context) => {
electronDist: _context => {
return Promise.resolve(getElectronCacheDir())
}
},
},
})
)
30 changes: 10 additions & 20 deletions test/src/ignoreTest.ts
Original file line number Diff line number Diff line change
@@ -92,16 +92,14 @@ test.ifNotCiMac(
return Promise.all([
modifyPackageJson(projectDir, data => {
data.devDependencies = {
"semver": "6.3.1",
semver: "6.3.1",
...data.devDependencies,
}
}),
])
},
packed: context => {
return Promise.all([
assertThat(path.join(context.getResources(Platform.LINUX), "app", "node_modules", "semver")).doesNotExist(),
])
return Promise.all([assertThat(path.join(context.getResources(Platform.LINUX), "app", "node_modules", "semver")).doesNotExist()])
},
}
)
@@ -124,7 +122,7 @@ test.ifDevOrLinuxCi(
modifyPackageJson(projectDir, data => {
data.dependencies = {
"electron-updater": "6.3.9",
"semver":"6.3.1",
semver: "6.3.1",
...data.dependencies,
}
}),
@@ -137,9 +135,7 @@ test.ifDevOrLinuxCi(
assertThat(path.join(context.getResources(Platform.LINUX, archFromString(process.arch)), "app", "node_modules", "electron-updater", "node_modules")).isDirectory(),
assertThat(path.join(context.getResources(Platform.LINUX, archFromString(process.arch)), "app", "others", "node_modules")).doesNotExist(),
assertThat(path.join(context.getResources(Platform.LINUX, archFromString(process.arch)), "app", "submodule-1-test", "node_modules")).isDirectory(),
assertThat(
path.join(context.getResources(Platform.LINUX, archFromString(process.arch)), "app", "submodule-1-test", "node_modules", "package.json")
).isFile(),
assertThat(path.join(context.getResources(Platform.LINUX, archFromString(process.arch)), "app", "submodule-1-test", "node_modules", "package.json")).isFile(),
])
},
}
@@ -190,9 +186,9 @@ test.ifDevOrLinuxCi(
targets: Platform.LINUX.createTarget(DIR_TARGET),
config: {
asar: false,
// should use **/ instead of */,
// should use **/ instead of */,
// we use the related path to match, so the relative path is submodule-1-test/node_modules
// */ will not match submodule-1-test/node_modules
// */ will not match submodule-1-test/node_modules
files: ["**/*", "**/submodule-1-test/node_modules/**"],
},
},
@@ -211,9 +207,7 @@ test.ifDevOrLinuxCi(
packed: context => {
return Promise.all([
assertThat(path.join(context.getResources(Platform.LINUX, archFromString(process.arch)), "app", "submodule-1-test", "node_modules")).isDirectory(),
assertThat(
path.join(context.getResources(Platform.LINUX, archFromString(process.arch)), "app", "submodule-1-test", "node_modules", "package.json")
).isFile(),
assertThat(path.join(context.getResources(Platform.LINUX, archFromString(process.arch)), "app", "submodule-1-test", "node_modules", "package.json")).isFile(),
assertThat(path.join(context.getResources(Platform.LINUX, archFromString(process.arch)), "app", "submodule-2-test", "node_modules")).doesNotExist(),
])
},
@@ -243,9 +237,7 @@ test.ifDevOrLinuxCi(
])
},
packed: context => {
return Promise.all([
assertThat(path.join(context.getResources(Platform.LINUX, archFromString(process.arch)), "app", "submodule-1-test", "node_modules")).doesNotExist(),
])
return Promise.all([assertThat(path.join(context.getResources(Platform.LINUX, archFromString(process.arch)), "app", "submodule-1-test", "node_modules")).doesNotExist()])
},
}
)
@@ -273,10 +265,8 @@ test.ifDevOrLinuxCi(
])
},
packed: context => {
return Promise.all([
assertThat(path.join(context.getResources(Platform.LINUX, archFromString(process.arch)), "app", "submodule-1-test", "node_modules")).doesNotExist(),
])
return Promise.all([assertThat(path.join(context.getResources(Platform.LINUX, archFromString(process.arch)), "app", "submodule-1-test", "node_modules")).doesNotExist()])
},
}
)
)
)
24 changes: 21 additions & 3 deletions test/src/linux/linuxPackagerTest.ts
Original file line number Diff line number Diff line change
@@ -112,9 +112,25 @@ test.ifNotWindows.ifNotCiMac(
config: {
linux: {
executableName: "Foo",
// Example Spec: https://specifications.freedesktop.org/desktop-entry-spec/latest/example.html
desktop: {
"X-Foo": "bar",
Terminal: "true",
entry: {
"X-Foo": "bar",
Terminal: "true",
},
desktopActions: {
Gallery: {
Exec: "fooview --gallery",
Name: "Browse Gallery",
},
Create: {
Exec: "fooview --create-new",
Name: "Create a new Foo!",
Icon: "fooview-new",
},
EmptyEntry: {},
NullEntry: null,
},
},
},
appImage: {
@@ -293,7 +309,9 @@ test.ifNotWindows(
config: {
linux: {
desktop: {
Exec: "foo",
entry: {
Exec: "foo",
},
},
},
},
2 changes: 1 addition & 1 deletion typedoc.config.js
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ module.exports = {
excludePrivate: true,
excludeProtected: true,
excludeNotDocumented: false,
includeVersion: false,
includeVersion: true,
hideGroupHeadings: true,
hidePageTitle: true,
disableSources: true,

0 comments on commit 0dbe357

Please sign in to comment.