[SOLVED] Re: List of installed applications from prefs.js



(sorry if you get this email twice - the first time I sent I attached
a .js file so the mail got rejected, so I've changed the file
extension to .txt)

On 25 August 2012 01:24, Florian Müllner <fmuellner gnome org> wrote:
> On Fri, Aug 24, 2012 at 2:31 PM, Amy C <mathematical coffee gmail com> wrote:
>> So, how can I get a list of installed applications from a process
>> outside of the gnome-shell one (no access to Shell, etc)?
>
> Use the GnomeMenu-3.0 API - ShellAppSystem is a rather thin wrapper
> around that nowadays.

Hi all,

Thanks for pointing me to GnomeMenu and shell-app-system.c.

Based on these I managed to get a list of installed applications. I
attach a code sample I wrote to do so in case anyone wishes to use it.
(I changed the filename to .txt because the email got rejected the
first time - can't send .js as an attachment).

It is very basic, feel free to tweak as you need - it is basically a
very simple version of shell-app-system.c (with none of the search
features, just the ability to list all the applications). The code
snippet shows how to instantiate an object { app_desktop_id:
GMenuTree.Entry } and keep that in sync with the apps list.

The file can be run via ` ./gjs appsystem.js` and will print a list of
installed apps to your terminal. (if you don't want it to run just
comment out the `main();` at the very buttom of the file). ***USUAL
DISCLAIMER about not running a script file blindly; view the code
before running/don't run at all if you're concerned etc***

I am unsure of the etiquette concerning sending scripts around a
mailing list so I'll outline the main points here in case someone in
the future visits the archive and wants to know:

// import GMenu
const GMenu = imports.gi.GMenu;

// make a GMenu.Tree containing the applications menu tree structure
(Applictions -> {Accessories, Games, Graphics, ...} -> individual
application)
let appsTree = new GMenu.Tree({
    menu_basename: 'applications.menu',
    flags: GMenu.TreeFlags.INCLUDE_NODISPLAY
});

// you must do this on the tree before anything else, returns a
boolean whether it
// succeeded or not
appsTree.load_sync();

// if you want to connect to changes in this (user installs/removes a program)
appsTree.connect('changed', callback);

Then to get the actual apps as a flat list, you create a TreeIter on
appsTree and iterate through each item in it. If it is an entry, it is
an app - add it to the list. If it is a directory, recurse into it
until you extract the entries. This is the getNodes function in the
attached file.

let dir = appsTree.get_root_directory();
let iter = dir.iter();
// now just call iter.next() until it returns GMenu.TreeItemType.INVALID.
// use iter.get_entry() to get the entry or iter.get_directory() to
get the directory.
// iter.next() returns the type of the current entry (ENTRY, DIRECTORY, ...)

Hope that helps someone in the future, and thanks for the pointers
everyone! (Florian, Yaa101, Milan Bouchet-Valat).
const GMenu = imports.gi.GMenu;

/*******
 * Code
 * Example use. Getting a list of applications.
 * The GMenu.Tree is 'applications.menu', and the top-level children of it
 * are the application subclasses (like Accessories, Games, ...)
 * The children of these are applications.
 *
 * This piece of code simply loads the apps menu into a GMenu.Tree object
 * `appsTree`, and also populates an object `appsList` where the keys are the
 * application's desktop id (e.g. gnome-terminal.desktop) and the entries are
 * the GMenu.TreeEntry corresponding to the application.
 *
 * The code keeps `appsList` in sync with `appsTree` by monitoring changes
 * in `appsTree` and mirroring them to `appsList`.
 *
 * NOTE: shell-app-system.c has much more - for example, it deals with vendor
 * prefixes on desktop ids (e.g. gnome-xxx.desktop, fedora-yyy.desktop) and lets
 * you search with these prefixes removed.
 *******/
let appsTree = null,
    appsList = {};

function main() {
    // create a GMenu.Tree holding the applications menu.
    // (If you want the GNOME controle centre, use 'gnomecc.menu': see
    //  /etc/xdg/menus)
    // This copies off shell-app-system.c shell_app_system_init
    appsTree = new GMenu.Tree({
        menu_basename: 'applications.menu',
        flags: GMenu.TreeFlags.INCLUDE_NODISPLAY
    });

    // monitor changes in the tree so we can update appsList (i.e. when this
    // gets called the installed apps have changed)
    appsTree.connect('changed', onTreeChanged);

    // update appsList
    onTreeChanged(appsTree);

    // now, appsList should be always up to date with the current application
    // list.
    for (let appid in appsList) {
        if (appsList.hasOwnProperty(appid)) {
            print(appid);
        }
    }
}

// callback: for example here we update our appsList dictionary to remain in
// sync with the appsTree.
function onTreeChanged(tree) {
    // first make sure that tree is appsTree (it should be, that's all we
    // connected this function to...)
    if (tree !== appsTree) {
        throw new Error("`tree` is not the apps tree!");
    }

    // initialise appsTree
    if (!appsTree.load_sync()) {
        throw new Error("Failed to load apps...");
    }

    // new apps tree
    let newApps = getTreeNodes(tree);

    // look for apps in appsList that are not in newApps and remove them
    for (let id in appsList) {
        if (!appsList.hasOwnProperty(id)) {
            continue;
        }
        if (!newApps[id]) {
            delete appsList[id];
        }
    }

    // add/update appsList from newApps
    for (let id in newApps) {
        if (!newApps.hasOwnProperty(id)) {
            continue;
        }
        let entry = newApps[id];
        //    prefix = getPrefixForEntry(entry); // TODO <-- ??

        if (appsList[id] !== entry) {
            // add the entry to appsList
            appsList[id] = entry;
        }
    }
}

/***************
 * Helper functions.
 *
 * Note:
 * GMenu.Tree  --> load_sync() to init, get_root_directory().iter() to iterate.
 * | Contains:
 * |- GMenu.TreeDirectory --> use .iter() to iterate through
 * |  |- .iter() to iterate through
 * |  |- .get_menu_id()
 * |  |- .get_name()
 * |  |- and so on...
 * |- GMenu.TreeEntry
 * |  |- .get_app_info()
 * |  |- .get_desktop_file_id()
 * |  |- and so on...
 ***************/

/* Get all nodes of a directory 
 * (shell-app-system.c: get_flattened_entires_recurse)
 * @dir: a GMenu.TreeDirectory (example, .get_root_directory() of a GMenu.Tree)
 *
 * returns: an object with key `id` and value GMenu.TreeEntry, where the id
 * is the entry's .get_desktop_file_id().
 */
function getNodes(dir) {
    let entry,
        nodeType,
        entries,
        returnDict = {}, // dictionary. id: entry.
        iter = dir.iter();

    // loop through the directory recursing down directories until we reach
    // items.
    while ((nodeType = iter.next()) !== GMenu.TreeItemType.INVALID) {
        switch (nodeType) {
        case GMenu.TreeItemType.ENTRY:
            // add it to the return object, overwriting duplicates (if any)
            entry = iter.get_entry();
            print('adding entry: ' + entry.get_desktop_file_id());
            returnDict[entry.get_desktop_file_id()] = entry;
            break;
        case GMenu.TreeItemType.DIRECTORY:
            // recurse into the directory, adding these results to our own
            // return object (any duplicates are overwritten):
            entries = getNodes(iter.get_directory());
            for (entry in entries) {
                if (entries.hasOwnProperty(entry)) {
                    returnDict[entry] = entries[entry];
                }
            }
            break;
        default: // SEPARATOR, HEADER, ALIAS. skip for now.
            break;
        }
    }
    return returnDict;
}

/* get all nodes from a tree */
function getTreeNodes(tree) {
    return getNodes(tree.get_root_directory());
}



/*****************************************************/
// run code sample
main();


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]