use format::xdg::ini; use io; use locale; use strings; // The information from a desktop entry file. // Specification: §6 export type file = struct { typ: str, version: str, name: locale::string, generic_name: locale::string, no_display: bool, comment: locale::string, icon: locale::string, hidden: bool, only_show_in: []str, not_show_in: []str, dbus_activatable: bool, try_exec: str, exec: str, path: str, terminal: bool, mime_type: []str, categories: []str, implements: []str, keywords: locale::strings, startup_notify: bool, startup_wm_class: str, url: str, prefers_non_default_gpu: bool, single_main_window: bool, actions: []action, }; // An additional application action. // Specification: §11 export type action = struct { key: str, name: locale::string, icon: locale::string, exec: str, }; // Parses a desktop entry file. Use [[file_finish]] to get rid of it. export fn parse(input: io::handle) (file | ini::error) = { let file = file { ... }; let scanne = ini::scan(input); defer ini::finish(&scanne); for (let entr => ini::next_entry(&scanne)?) { match(parse_handle_entry(&file, entr)) { case let err: ini::error => finish(&file); return err; case void => void; }; }; return file; }; // Frees resources associated with a [[file]]. export fn finish(this: *file) void = { free(this.typ); free(this.version); locale_string_finish(this.name); locale_string_finish(this.generic_name); locale_string_finish(this.comment); locale_string_finish(this.icon); strings::freeall(this.only_show_in); strings::freeall(this.not_show_in); free(this.try_exec); free(this.exec); free(this.path); strings::freeall(this.mime_type); strings::freeall(this.categories); strings::freeall(this.implements); locale_strings_finish(this.keywords); free(this.startup_wm_class); free(this.url); for (let actio .. this.actions) { free(actio.key); locale_string_finish(actio.name); locale_string_finish(actio.icon); free(actio.exec); }; free(this.actions); }; fn parse_handle_entry(this: *file, entr: ini::entry) (void | ini::error) = { const desktop_action_prefix = "Desktop Action "; if (entr.group == "Desktop Entry") { return parse_handle_ini_entry(this, entr); } else if (strings::hasprefix(entr.group, desktop_action_prefix)) { let key = strings::trimprefix(entr.group, desktop_action_prefix); return parse_handle_desktop_action_entry(this, entr, key); }; }; fn parse_handle_desktop_action_entry(this: *file, entr: ini::entry, key: str) (void | ini::error) = { let actio = match(parse_find_action(this, key)) { case let actio: *action => yield actio; case void => return void; }; if (entr.key == "Name") { parse_set_locale_string( &actio.name, entr.locale, ini::parse_localestring(entr.value)?); } else if (entr.key == "Icon") { parse_set_locale_string( &actio.icon, entr.locale, ini::parse_iconstring(entr.value)?); } else if (entr.key == "Exec") { if (parse_is_localized(entr)) return void; actio.exec = ini::parse_string(entr.value)?; }; }; fn parse_handle_ini_entry(this: *file, entr: ini::entry) (void | ini::error) = { if (entr.key == "Type") { if (parse_is_localized(entr)) return void; this.typ = ini::parse_string(entr.value)?; } else if (entr.key == "Version") { if (parse_is_localized(entr)) return void; this.version = ini::parse_string(entr.value)?; } else if (entr.key == "Name") { parse_set_locale_string( &this.name, entr.locale, ini::parse_localestring(entr.value)?); } else if (entr.key == "GenericName") { parse_set_locale_string( &this.generic_name, entr.locale, ini::parse_localestring(entr.value)?); } else if (entr.key == "NoDisplay") { if (parse_is_localized(entr)) return void; this.no_display = ini::parse_boolean(entr.value)?; } else if (entr.key == "Comment") { parse_set_locale_string( &this.comment, entr.locale, ini::parse_localestring(entr.value)?); } else if (entr.key == "Icon") { parse_set_locale_string( &this.icon, entr.locale, ini::parse_iconstring(entr.value)?); } else if (entr.key == "Hidden") { if (parse_is_localized(entr)) return void; this.hidden = ini::parse_boolean(entr.value)?; } else if (entr.key == "OnlyShowIn") { if (parse_is_localized(entr)) return void; this.only_show_in = ini::parse_strings(entr.value)?; } else if (entr.key == "NotShowIn") { if (parse_is_localized(entr)) return void; this.not_show_in = ini::parse_strings(entr.value)?; } else if (entr.key == "DBusActivatable") { if (parse_is_localized(entr)) return void; this.dbus_activatable = ini::parse_boolean(entr.value)?; } else if (entr.key == "TryExec") { if (parse_is_localized(entr)) return void; this.try_exec = ini::parse_string(entr.value)?; } else if (entr.key == "Exec") { if (parse_is_localized(entr)) return void; this.exec = ini::parse_string(entr.value)?; } else if (entr.key == "Path") { if (parse_is_localized(entr)) return void; this.path = ini::parse_string(entr.value)?; } else if (entr.key == "Terminal") { if (parse_is_localized(entr)) return void; this.terminal = ini::parse_boolean(entr.value)?; } else if (entr.key == "Actions") { if (parse_is_localized(entr)) return void; let strings =ini::parse_strings(entr.value)?; defer free(strings); this.actions = alloc([], len(strings)); for (let string .. strings) { append(this.actions, action { key = string, ... }); }; } else if (entr.key == "MimeType") { if (parse_is_localized(entr)) return void; this.mime_type = ini::parse_strings(entr.value)?; } else if (entr.key == "Categories") { if (parse_is_localized(entr)) return void; this.categories = ini::parse_strings(entr.value)?; } else if (entr.key == "Implements") { if (parse_is_localized(entr)) return void; this.implements = ini::parse_strings(entr.value)?; } else if (entr.key == "Keywords") { parse_set_locale_strings( &this.keywords, entr.locale, ini::parse_localestrings(entr.value)?); } else if (entr.key == "StartupWMClass") { if (parse_is_localized(entr)) return void; this.startup_wm_class = ini::parse_string(entr.value)?; } else if (entr.key == "URL") { if (parse_is_localized(entr)) return void; this.url = ini::parse_string(entr.value)?; } else if (entr.key == "PrefersNonDefaultGPU") { if (parse_is_localized(entr)) return void; this.prefers_non_default_gpu = ini::parse_boolean(entr.value)?; } else if (entr.key == "SingleMainWindow") { if (parse_is_localized(entr)) return void; this.single_main_window = ini::parse_boolean(entr.value)?; }; }; fn parse_is_localized(entr: ini::entry) bool = { return !locale::equal(entr.locale, locale::c); }; fn parse_set_locale_string(dest: *locale::string, local: locale::locale, value: str) void = { for (let existing &.. *dest) { if (locale::equal(existing.0, local)) { existing.1 = value; return; }; }; append(dest, (locale::dup(local), strings::dup(value))); }; fn parse_set_locale_strings(dest: *locale::strings, local: locale::locale, value: []str) void = { for (let existing &.. *dest) { if (locale::equal(existing.0, local)) { existing.1 = value; return; }; }; append(dest, (locale::dup(local), strings::dupall(value))); }; fn parse_find_action(this: *file, key: str) (*action | void) = { for (let actio &.. this.actions) { if (actio.key == key) return actio; }; }; fn locale_string_finish(string: locale::string) void = { for (let pair .. string) { locale::finish(pair.0); free(pair.1); }; free(string); }; fn locale_strings_finish(strings: locale::strings) void = { for (let pair .. strings) { locale::finish(pair.0); strings::freeall(pair.1); }; free(strings); };