menu

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 7, 2026 License: BSD-3-Clause Imports: 8 Imported by: 0

README

menu — go-freedesktop

ci Go Reference License Go Coverage

The freedesktop Desktop Menu Specification for a launcher — the piece that turns the installed applications into the categorized category menu a launcher shows (Accessories, Graphics, System, Games, …). It reads the applications.menu XML file and produces a resolved tree of menus, each with its display name, icon, sub-menus and application entries. Pure Go, CGO-free.

Scope — what this adds, and what it reuses

This is Wave 2 of go-freedesktop; it stands on Wave 1 rather than reimplementing it:

On top of those it implements the menu-spec algorithm:

  • parse the <Menu> tree of an applications.menu file: <Name>, <Directory>, <Include>/<Exclude> with the <And>/<Or>/<Not>/<Category>/<Filename>/<All> match rules, <AppDir>/<DefaultAppDirs>, <DirectoryDir>/<DefaultDirectoryDirs>, <MergeFile>/<MergeDir>/<DefaultMergeDirs>, <OnlyUnallocated>, <Deleted>, <Move> and <Layout>/<DefaultLayout>;
  • merge referenced menu files (with cycle protection), consolidate menus that share a <Name>, apply <Move> rules and drop <Deleted> menus, per the spec;
  • resolve the include/exclude rule expressions against the entries scanned by desktopentry.ScanDirs, honoring the two-phase <OnlyUnallocated> allocation (an unallocated "Other" menu gets exactly what no normal menu claimed);
  • resolve each menu's <Directory> .directory file for its display name, icon and comment;
  • order each menu's contents with its <Layout>/<DefaultLayout> (<Menuname>, <Filename>, <Merge type="menus|files|all">, <Separator>), defaulting to alphabetical sub-menus then applications.

Install

go get github.com/go-freedesktop/menu

Quickstart

package main

import (
	"fmt"

	"github.com/go-freedesktop/menu"
)

func main() {
	tree, err := menu.Load() // reads the standard XDG applications.menu
	if err != nil {
		panic(err)
	}
	walk(tree.Root, 0)
}

func walk(m *menu.Menu, depth int) {
	fmt.Printf("%*s%s\n", depth*2, "", m.DirectoryName)
	for _, a := range m.Apps {
		fmt.Printf("%*s- %s  (%s)\n", depth*2+2, "", a.Name, a.Icon)
	}
	for _, sub := range m.Submenus {
		walk(sub, depth+1)
	}
}

Public API

Symbol Purpose
Load() (*Tree, error) build the menu from the standard XDG locations
LoadWithDirs(menuFile string, appDirs, dirDirs, mergeDirs []string, current string) (*Tree, error) directory-injectable form (tests / custom search paths)
Tree the resolved menu; Tree.Root is the top-level *Menu
Menu one node: Name, DirectoryName, Icon, Comment, Submenus []*Menu, Apps []*desktopentry.Entry

current is the desktop-environment name (as in XDG_CURRENT_DESKTOP) used for OnlyShowIn/NotShowIn filtering; the empty string shows every entry.

wasmdesk integration

This is the application-menu layer of the wasmdesk launcher:

  • Load() → the whole category menu (Accessories / Graphics / System / …), ready to render as a nested pop-up or a sidebar tree.
  • each Menu.Apps entry is a *desktopentry.Entry, so a click flows straight into desktopentry.ExpandExec to launch it.
  • each Menu.Icon and each entry's Icon feed go-freedesktop/icontheme to resolve a concrete image path.

Together with desktopentry (what to launch) and icontheme (how to draw it), this completes the three freedesktop layers a launcher needs.

Tests & coverage

CGO_ENABLED=0 go test ./...100% statement coverage, including every error branch, driven by a synthetic applications.menu plus an app/directory fixture tree under testdata/ that exercises include/exclude, And/Or/Not, merge (file + dir + default), same-name consolidation, <Move>, <Deleted>, <Layout> and <OnlyUnallocated> allocation. CI additionally cross-builds and runs the suite on the six supported 64-bit targets (amd64/arm64 natively, riscv64/loong64/ppc64le/s390x under qemu-user).

License

BSD-3-Clause. Copyright (c) the go-freedesktop/menu authors.

Documentation

Overview

Package menu implements the freedesktop.org Desktop Menu Specification: it reads the applications.menu XML file and turns it into a categorized tree of installed applications — the "category menu" an application launcher shows (Accessories, Graphics, System, ...).

It deliberately does not reinvent the layers below it. The individual .desktop entries are enumerated and parsed by github.com/go-freedesktop/desktopentry (its desktopentry.ScanDirs and desktopentry.Entry), and the XDG base-directory resolution is delegated to github.com/adrg/xdg. On top of those this package adds the menu-spec gap:

  • parse the <Menu> tree of an applications.menu file, including <Include>/<Exclude> match rules (<And>/<Or>/<Not>/<Category>/ <Filename>/<All>), <AppDir>/<DefaultAppDirs>, <DirectoryDir>/ <DefaultDirectoryDirs>, <MergeFile>/<MergeDir>/<DefaultMergeDirs>, <Directory>, <OnlyUnallocated>, <Deleted>, <Move> and <Layout>;
  • merge referenced menu files, consolidate menus sharing a <Name>, apply <Move> rules and drop <Deleted> menus, per the spec;
  • resolve the include/exclude rule expressions against the scanned desktop entries, honoring the two-phase <OnlyUnallocated> allocation;
  • resolve each menu's <Directory> .directory file for its display name and icon;
  • order the result with the menu's <Layout>/<DefaultLayout>.

The result is a Tree of Menu nodes, each exposing its display Name, Icon and the ordered lists of Submenus and Apps.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Menu struct {
	// Name is the menu's <Name>, the identifier used by <Move>, <Layout>
	// and same-name consolidation. It is never localized.
	Name string
	// DirectoryName is the user-visible label: the Name key of the menu's
	// resolved <Directory> .directory file, or Name when there is none.
	DirectoryName string
	// Icon is the icon name/path from the .directory file (feeds an
	// icon-theme lookup), empty when unset.
	Icon string
	// Comment is the localized description from the .directory file.
	Comment string
	// Submenus are the child menus, ordered per the menu's layout.
	Submenus []*Menu
	// Apps are the application entries allocated to this menu, ordered per
	// the menu's layout.
	Apps []*desktopentry.Entry
}

Menu is one node of the resolved application menu.

type Tree

type Tree struct {
	// Root is the top-level menu (typically named "Applications").
	Root *Menu
}

Tree is a fully resolved application menu.

func Load

func Load() (*Tree, error)

Load builds the application menu from the standard locations: the applications.menu file resolved through the XDG config directories (with the $XDG_CONFIG_HOME copy overriding the system one), the applications/ directories, the desktop-directories/ directories and the menus/applications-merged/ merge directories, all provided by github.com/adrg/xdg. Entry visibility honors $XDG_CURRENT_DESKTOP.

func LoadWithDirs

func LoadWithDirs(menuFile string, appDirs, dirDirs, mergeDirs []string, current string) (*Tree, error)

LoadWithDirs is the directory-injectable form of Load, intended for tests and for embedders that manage their own search paths. menuFile is the applications.menu file to read; appDirs, dirDirs and mergeDirs supply the concrete directories that the XML's <DefaultAppDirs>, <DefaultDirectoryDirs> and <DefaultMergeDirs> elements expand to; current is the desktop environment name used for OnlyShowIn/NotShowIn filtering (empty means "show everything").

Example

ExampleLoadWithDirs builds the categorized application menu from an injected set of directories (the same shape menu.Load uses with the standard XDG paths) and prints each top-level category with its resolved display name and application count.

package main

import (
	"fmt"

	"github.com/go-freedesktop/menu"
)

func main() {
	tree, err := menu.LoadWithDirs(
		"testdata/menus/applications.menu",
		[]string{"testdata/applications"},
		[]string{"testdata/desktop-directories"},
		[]string{"testdata/menus/applications-merged"},
		"", // show entries for every desktop environment
	)
	if err != nil {
		panic(err)
	}

	fmt.Println(tree.Root.DirectoryName)
	for _, m := range tree.Root.Submenus {
		fmt.Printf("  %-12s %d app(s)\n", m.DirectoryName, len(m.Apps))
	}

}
Output:
All Applications
  Accessories  2 app(s)
  Games        2 app(s)
  Graphics     1 app(s)
  Network      1 app(s)
  Other        1 app(s)
  System       1 app(s)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL