Aug 11

GNOME 3 Plugins: Settings

This post is going to be based partially on the source of Coverflow Alt Tab, plus some of the source files that are already included in GNOME 3.

Also, a few notes here: it appears at this time that there is no way to easily import your schema into the GSettings backend.  This may be added at a future time.  So, any settings that you make will essentially be unstructured and possibly break if somebody messes around with them.

The first thing for us to do is to create a new XML schema for us. This schema holds the keys to our values, as well as default values and descriptions.  There’s some documentation on the GNOME website, so i will only go over a small portion of what is there, given that I only understand a small portion. 🙂

For our purposes, we will define two keys: one string key which will be the text to display, and a boolean value that we will mess around with later.  For now, let’s define the XML schema:

<?xml version="1.0" encoding="UTF-8"?>
  <schema id="com.rm5248.testplugin" path="/org/gnome/shell/extensions/rm5248/">
    <key type="s" name="hello-text" >
        <default>"Hello, World!"</default>
        <summary>The text to display</summary>
        <description>The text to display</description>
    <key type="b" name="wait" >
        <description>Do you wait until OK?</description>

This is pretty straightforward: you have a schema with a name(com.rm5248.testplugin) and two keys, a string value called ‘hello-text’ and a boolean vaue called ‘wait’.  Once we have written the XML file, we need to generate the binary version of it.  To do that, simply use the glib-compile-schemas tool:

glib-compile-schemas schemas/

Now that we have our compiled schema, it’s time to retrieve settings from the GSettings database.  In order to do that, we will need this lib.js file from CoverflowAltTab.  From the copyright, it appears to be from the GNOME sources, however I have been unable to find where it originally comes from.

Now that we have our schema compiled and in the schemas/ directory, it’s a very simple process to get the default settings(as we haven’t changed them at all.).  In fact, here’s the entire code:

const Gio =;
const ThisExtension = imports.misc.extensionUtils.getCurrentExtension();
const Lib = ThisExtension.imports.lib;

const RM_SCHEMA = 'com.rm5248.testplugin';
const TEXT_KEY = 'hello-text';
const BOOL_KEY = 'wait';

let settings = null;

function init() {
    // Create the new settings
    settings = Lib.getSettings( RM_SCHEMA );

function enable() {
    log( settings.get_string( TEXT_KEY ) );
    log( settings.get_boolean( BOOL_KEY ) );

function disable() {

For now, we’re just printing out the settings to the console, which is not very useful. We’ll expand this in a bit, but before we do that let’s make a GTK widget to set the settings.  Looking at the GNOME documentation, we see that we can change settings by having a prefs.js file with a method buildPrefsWidget.

A quick note before we get started here: when constructing a C object, the parameters to the object(in the {} syntax) are properties to set on the object.  Take for example the properties on GTKLabel: if you want to set the ‘angle’ property when constructing, you would do:

new Gtk.Label( { angle: 20 } );

Do this for all the properties that you wish to set when you create the object.

Now, moving on to the actual code of the prefs.js file.  Everything here is fairly straightforward if you’ve done GTK+ programming before.  Here’s the code that will pop up a GTK+ widget:

function buildPrefsWidget(){
    let frame = new Gtk.Box( { orientation: Gtk.Orientation.VERTICAL, border_width: 10, spacing: 10 } );

    //There are two settings, so we will have one that changes the text and one that changes
    //the boolean value.
    //Create an EntryBuffer to store the text in
    let entryBuffer = new Gtk.EntryBuffer( { text: settings.get_string( TEXT_KEY ) } );
    let textBox = new Gtk.Box( { orientation: Gtk.Orientation.HORIZONTAL, spacing: 10 } );
    let textLabel = new Gtk.Label( { label: "Hello text", xalign: 0 } );
    let textEntry = new Gtk.Entry( { buffer: entryBuffer } );
    //Gtk.Entry implements GtkEditable interface, which is where the 'changed' signal comes from
    textEntry.connect( 'changed', function( widget ){
      settings.set_string( TEXT_KEY, widget.get_text() );
     } );
    textBox.pack_start( textLabel, true, true, 0 );
    textBox.add( textEntry );

    //Let's change our boolean value now
    let boolBox = new Gtk.Box( { orientation: Gtk.Orientation.HORIZONTAL, spacing: 10 } );
    let boolLabel = new Gtk.Label( { label: "Do we wait?", xalign: 0 } );
    let boolRadio = new Gtk.Switch( { active: settings.get_boolean( BOOL_KEY ) } );
    //Gtk.Switch has an 'activate' signal, but don't connect to that directly.
    //Instead, the docs say to use the 'notify::active' signal
    boolRadio.connect( 'notify::active', function( widget ){
        settings.set_boolean( BOOL_KEY, widget.get_active() );
     } );
    boolBox.pack_start( boolLabel, true, true, 0 );
    boolBox.add( boolRadio );

    frame.add( textBox );
    frame.add( boolBox );

    return frame;

To test this out, re-load the shell (ALT+F2, r, enter), and open up gnome-tweak-tool.  Under ‘Extensions’, there should now be a small cog for the ‘settings example’ extension.  If you open that up, you should see the two settings that we made: one for the text, and one for the boolean value.  By changing these values, you can change what the extension will print out when the plugin is loaded.

You can also use dconf to see the values directly, once you have saved them first.  Change a value with gnome-tweak-tool, open up dconf and navigate to /org/gnome/shell/extensions/rm5248/, and you will see the settings that you have made.  Remember though, they only appear there if they are different from the defaults; this may not be true for all programs, but it is true for gnome-shell extensions.

You may find the complete source here.

Aug 08


So I’m working on this settings stuff for GNOME 3, and I attempted to compile a new gsettings schema.  So I make my XML schema, and run glib-compile-schemas, and it comes back at me:

schemas/com.rm5248.testplugin.gschema.xml: 0-1:unknown keyword. This entire file has been ignored.

Okay… so I look at the file, and everything looks fine.  I can’t see anything wrong with it.  I go back and forth, trying different things, until I narrow it down to this key:

<key type="s" name="hello-text" >
        <default>Hello, World!</default>
        <summary>The text to display</summary>
        <description>The text to display</description>

Commenting this out makes it work fine. So I look at the documentation for the schemas, and nothing immediately pops out at me, until I see the following:

One possible pitfall in doing schema conversion is that the default values in GSettings schemas are parsed by the GVariant parser. This means that strings need to include quotes in the XML.

So I change the XML to be the following:

<key type="s" name="hello-text" >
        <default>"Hello, World!"</default>
        <summary>The text to display</summary>
        <description>The text to display</description>

What. The. Hell.

Why do string values have to be highlighted?  That doesn’t make a lot of sense.  There shouldn’t be any reason for that.  It is still valid XML, but who does it that way? Moreover, WHY DOESN’T THE ERROR MESSAGE GIVE ME ANY USEFUL INFORMATION? That is possibly the worst error message ever. Okay, it’s been ignored, BUT WHY THE HELL HAS IT BEEN IGNORED?! Is it really that hard to provide good error messages? Also, what the hell is the “0-1”? Is it some sort of bizzare error code or what? There’s nothing to imply that the reason it failed was because it was missing a quote – at least if it gave a line number that would let me clue in to what is going on, but apparently they can’t be bothered to do that.

Aug 07

GNOME 3 Plugins: DBus

I’m currently messing around with GNOME 3 plugins, and there’s not a whole lot of information out there.  Like, almost nothing.  So, as I’m working on this stuff I will be posting up some information and some small plugins that demonstrate a certain feature.  Whenever possible, I will also link to the relevant code or library function(or at least what I think the proper library/function is).

Now, before we get started, note that this information is for GNOME shell 3.8.4, which is what Debian 8 ships with, as well as Red Hat Enterprise Linux(RHEL) 7.  Also, a note to the GNOME devs: WRITE SOME DOCUMENTATION.  Seriously.  Even a generated API document that shows what methods are on each object in JavaScript would be better than what currently is available(re: nothing).

Note that this is also going to be a bit of a crash course in JavaScript for me, so I may say some completely incorrect things about JavaScript.  If I make any errors, please feel free to leave a comment to correct me.  If you could also put a source with that, that would be very helpful so that we can all learn.

Alright, now let’s get onto the examples!

Create a new Extension

This is covered in most of the tutorials already, but we of course need an extension to edit!  So, let’s create a new one with:

gnome-shell-extension-tool --create-extension

Now give it a name, description and UUID. You can see an example here.  The new extension will be in


Once you have edited an extension, you will have to reload the gnome-shell in order for the changes to take effect.  To do this, do ALT+F2, type ‘r’, and hit enter.

To view the output of the console, you will have to use systemd’s journalctl(for Debian 8, and probably RHEL 7).  The command is:

journalctl /usr/bin/gnome-session -f -o cat

Source here.

Also, you can enable-disable extensions using gnome-tweak-tool.

Example 1: DBus Connections

If you’re unfamiliar with DBus concepts, I recommend that you take a look at my DBus tutorial.  The first thing that we are going to have to do is to define an XML interface(essentially, what the DBus Introspection method gives back).

So, using our old friend the DBus tutorial, we will create an interface that looks suspiciously similar to our first DBus example of simply echoing a string out to the console. Here’s the interface XML:

<interface name="com.rm5248.ReceiveInterface">
<method name="echoMessage">
    <arg type="s" direction="in" name="message"/>

For each method that we put on the bus, we must have a method on our javascript object that has the same name.  If we don’t, we will get an error about there not being a method to call.

Here’s the important part that actually connects to the bus(DBusIface refers to the XML that we defined above):

    this._dbusImpl = Gio.DBusExportedObject.wrapJSObject( DBusIface, this );
    this._dbusImpl.export( Gio.DBus.session, '/' );

You can check to see that this method works using qdbus:

qdbus org.gnome.Shell / com.rm5248.ReceiveInterface.echoMessage hithere

You should now see the log printed out with your message.

This is all well and good, but what if we want to have our own DBus address?  We don’t want everything to be under the org.gnome.Shell address. To do that, we simple need to acquire the well-known name on the bus that we want to use:

this._dbusId = Gio.DBus.session.own_name( 'com.rm5248', Gio.BusNameOwnerFlags.NONE, this._nameAcquired, this._nameLost );

A note on this though: your object will still be accessible from the well-known bus names of ‘com.rm5248’ and ‘org.gnome.Shell’.  Essentially, ‘com.rm5248’ becomes an alias for ‘org.gnome.Shell’, so any path that you can access using ‘org.gnome.Shell’ you can also use ‘com.rm5248’.  This perhaps not a bug, but it is contrary to how most bindings work.

The source that I used for testing can be found here. I’ve commented as much as I can, given my knowledge of how it works.

Example 2: Sending Messages

Now that we have our connection on the bus, and we can receive messages, how do we send messages out?  Well, we need to do two things: Create a proxy, and then create an instance of that proxy.  Once we have the proxy, we can call the methods asynchronously or synchronously.  Here’s the important part of the code(note that DBusIface is the same XML that we defined earlier):

    const ReceiveInterface = Gio.DBusProxy.makeProxyWrapper( DBusIface );
    var prox = new ReceiveInterface( Gio.DBus.session, 'com.rm5248', '/', 
        // I don't know what this Lang.bind does, but it's very important(the
        // plugin will load very slowly if it isn't here)
        Lang.bind( this, function( proxy, error ){
            if( error ) {
                log('error: ' + error );

        // There are two methods that are made on the javascript object: <name>Sync and <name>Remote.
        // <name>Sync calls the method synchronously, <name>Remote calls it async.  
        //  I don't know where the error message goes when you call it async; a message is printed
        // out in the console that the exception was ignored.
        prox.echoMessageSync( "hithere" );
    }catch( e ){
        // This could fail if the well-known bus name does not exist
        log( 'Error calling method: ' + e );

So, now we can call methods on other objects.  You can get the source code for this example here.  Also, a note on calling methods: there appears to be a bug when calling with the ‘sync’ method, in that it will take a long time to return.  I will do some more investigation.

Coming up in our next tutorial: settings.

Nov 01

Monitor System DBus

I’m currently writing a program which needs to listen to signals on the system DBus, and I needed a way to monitor the system bus.  I followed the directions here, but I came across a slight problem with that.  It turns out that the solution posted only allows you to listen for things that root sends out.  When I ran my program as an unprivileged user, I didn’t see any output in dbus-monitor, however I would see output if I ran the same program as root.  From what I can tell, the configuration posted will only allow you to see what root is sending on the bus, given that user=”root”.

Fortunately, there’s a simple fix for this:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE busconfig PUBLIC
  "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"

  <policy context="default">
    <!-- Allow everything to be sent -->
    <allow send_destination="*" eavesdrop="true"/>
    <!-- Allow everything to be received -->
    <allow eavesdrop="true"/>

Simply change the policy user to a policy context, and you can now see everything that’s on the bus.

As always, make sure that you remove this config once you’re done testing, otherwise it will be easy to circumvent security mechanisms that are in place.

Sep 03

Good Documentation

I just came across this post on Slashdot on how to create good documentation.  This is a good guide.  Too often, documentation that exists out there is not particularly great, or good at showing you how to do something.  This is the reason that I created my D-Bus tutorial – and I hope that it’s been useful to people.  Similar to how Log4J2 didn’t have excellent documentation – it makes sense once you know how things work, but there isn’t a nice, low-knowledge way to go about this.  In fact, right in the documentation it says:

An understanding of how loggers work in Log4j is critical before trying to configure them. Please reference the Log4j architecture if more information is required. Trying to configure Log4j without understanding those concepts will lead to frustration.

…and that’s about the point at which my eyes glaze over.

I’m not saying that the Log4J2 documentation is horrible, but it could be better and somewhat more structured as Steve Losh said.

Aug 22

Log4J2 Setup

I feel that a decent guide on how exactly Log4J2 is setup is in order, because there isn’t a real clear explanation on Apache’s site. Now Apache, I know you guys make a lot of stuff, but seriously a lot of this stuff is just over-engineered.

Alright, on to the configuration stuff!

Here’s an example config file, with an explanation as to what is going on here(at least as far as I can understand it)

<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
    <Console name="Console" target="SYSTEM_OUT">
      <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
    <File name="File" fileName="${sys:logFilename}">
      <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
    <root level="ERROR">
      <appender-ref ref="Console" />
      <appender-ref ref="File"/>
    <logger name="com.rm5248.Logtest2" level="ALL" />

Okay, let’s start at the very top with the configuration tag:

<configuration status="OFF">

If you change the ‘status’ part of this to a level such as “ALL”, you’ll notice that Log4J2 sends out A LOT of information. As far as I can tell, this is just logging information that is part of Log4J2.

Alright, let’s go on to the appenders. When you create a logger, there are several appenders that you can use to send information to various places. The two appenders that we have here are for the console(i.e. System.out) and a file.

    <Console name="Console" target="SYSTEM_OUT">
      <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
    <File name="File" fileName="${sys:logFilename}">
      <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>

The PatternLayout that they have determines how the information will be printed out. For the File appender, there is a fileName property with sys:logFilename. This means that the logger will look for the system property of logFilename. This can be set by either:

  1. passing -DlogFilename=XXX to the program
  2. System.setProperty( “logFilename”, XXX )

If you use option #2, you must also reconfigure the logger as shown in this StackOverflow post.  Fortunately, the patters are in fact well documented.  There are a lot of other appenders out there to append to different things, but we’re not worried about that at the moment.

Okay, now on to the final section: the loggers themselves.

    <root level="ERROR">
      <appender-ref ref="Console" />
      <appender-ref ref="File"/>
    <logger name="com.rm5248.Logtest2" level="ALL" />

As you can see here, we have a root logger and a specific logger.  The root logger has a level of ERROR, which means that everything descending from the root logger will print out all ERROR messages.  The root logger will also go two places: the Console logger and the File logger.

The second logger besides the root logger is logging just one specific file.  The log level for that file is ALL, which overrides the ERROR log level of the root logger.  Thus, all log messages from that file will be logged.  These messages will also be sent to both the CONSOLE log and FILE log, as those settings are inherited from the root logger.  Let’s say that we want to send those log messages to the FILE, but everything will also go to the CONSOLE. We would do something like this:

    <root level="ERROR">
      <appender-ref ref="Console" />
    <logger name="com.rm5248.Logtest2" level="ALL" >
       <appender-ref ref="File" />

Since the more specific logger inherits from the root logger, all log messages from com.rm5248.Logtest2 go to the FILE and the CONSOLE.  There’s more stuff that you can do here with that, but I haven’t really delved into it too deeply.

Anyway, here’s the code that I was using when I was playing around with this.  Hopefully this will help you to get started.

XML Config File:

<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
    <Console name="Console" target="SYSTEM_OUT">
      <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
    <File name="File" fileName="${sys:logFilename}">
      <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
    <root level="ERROR">
      <appender-ref ref="Console" />
      <appender-ref ref="File" />
    <logger name="com.rm5248.Logtest2" level="ALL" />

package com.rm5248;


import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.config.Configurator;
import org.apache.logging.log4j.core.config.XMLConfigurationFactory;

public class LogTest {

	private static Logger logger = LogManager.getLogger( LogTest.class );

	public static void main( String[] args ){
		File logConfigFile = new File( XML_CONFIG_FILE_GOES_HERE );
		System.setProperty( "logFilename", "filename.log" );		

		try {
			FileInputStream fis = new FileInputStream( logConfigFile );

			XMLConfigurationFactory fc = new XMLConfigurationFactory( );
			fc.getConfiguration(  new ConfigurationFactory.ConfigurationSource( fis ) );

			URI configuration = logConfigFile.toURI();
			Configurator.initialize("config", null, configuration);

			org.apache.logging.log4j.core.LoggerContext ctx =
					(org.apache.logging.log4j.core.LoggerContext) LogManager.getContext( true );
		} catch (FileNotFoundException e) {

		logger.debug( "hi debug" ); "hi info" );
		logger.error( "hi error" );



package com.rm5248;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Logtest2 {
	private static Logger logger = LogManager.getLogger( Logtest2.class );
	public static void doSomething(){
		logger.debug( "hello" );
		logger.error( "error?!" );
		logger.warn( "warning" ); "la la la" );
May 07

Java Scanner and Input Streams

This just took me a stupid long time to figure out, so I’m leaving it here for future reference.

In Java, the easiest way to read lines is using Scanner.  For work, I had written a quick Linux-only serial port implementation, with which I then took ideas from that for use in my cross-platform JavaSerial library.  What I was doing before was simply using a scanner to look for text coming in on the serial port, and splitting it up based on the lines using the scanner.  This worked fine.

Then I used JavaSerial.  And the Scanner wouldn’t work.


As it turns out, Scanner doesn’t like using the normal read() method found in InputStream.  It instead uses the read() method which takes in a byte[] array.

If this was documented anyplace in the Javadocs, that would be good.  But it’s not.


I’ve figured out what the problem was. According to the Javadocs for the read( byte[], off, len ) method, it will block until it reads the entire length requested, which is stupid.  My implementation reads until either the end is reached OR there is no more data available(using the available() method).  See my implementation here.

Apr 28

C Enums

Enums in C are something special.  They’re not strongly typed at all.  As it’s pointed out, sometimes enums are used for making bit values, which is rather convenient.  However, it would be very good if enums could be strongly typed.  So, here’s my thought on how to make enums in C better.

Enums should be strongly typed.  The following should throw a compile error about assigning an integer to an enum:

enum MyEnum{ Value1 = 0, Value2 = 1 };
enum MyEnum variable = 0;

However, since this is C, casting should be allowed. So you could do this:

enum MyEnum{ Value1 = 0, Value2 = 1 };
enum MyEnum variable = (enum MyEnum)0;

Now about the bitset. I think that the best way to do this is to create a new enum-like type, called bitfield. It would have a syntax nearly itentical to enum, and the compiler could auto-increment the bitfields as well, so that you can ensure bitfields are always good:

bitfield MyBitfield{ Value1 = 0, Value2 = 1, Value3 = 3, Value4 = Value2 | Value3 };

The compiler would then go through and ensure that all bitfields are valid. You could also put more than one bitfield value into a bitfield, and the compiler would ensure that they’re both of the same type. So, this would be valid:

bitfield MyBitfield{ Value1 = 0, Value2 = 1, Value3 = 3, Value4 = Value2 | Value3 };
bitfield variable = Value1;  /* Works */

/* When passing in a method */
void foo( bitfield parameter ){
  if( parameter & Value2 ){
    /* Do something */

This would create a much more type-safe form of enums for C. While not backwards-compatible, it’s not a dramatic shift in how enums are used, so it wouldn’t require huge changes.

May 09

SVN Syncing

So I just spent a good half of the day today trying to figure out how to sync two repositories together. We’re moving repositories, and so we had to import data from one repository to the other. However, this process took a while, so once we had imported the data into the new repository it was several revisions out of date. There didn’t seem to be a way to use SVN tools to do this. So, I went and wrote a script which would take all the changes from one repo and commit them to the other one with all the data. This was needed because we didn’t have shell access to the new SVN repo, and because of how that repo has to be set-up it would be somewhat infeasible to do another SVN dump.

There are a few limitations with this; there’s no way to add new files to the repo(though you could probably do an svn add right before you commit). There’s also no way to delete any files. In our case, this is not a problem, as there have been only file modifications.

Before you do this, you must checkout both repositories into different directories.

So, without further ado, here’s the code:


ORIG_DIR=&lt;original directory&gt;
NEW_DIR=&lt;new SVN directory&gt;
START_REV=&lt;start revision of original SVN&gt;
END_REV=&lt;end revision of original SVN&gt;

while [ $START_REV -ne $END_REV ]
svn update -r "$START_REV" #update to next revision of old directry
let "START_REV += 1"

echo "About to diff, this may take a while..."
diff -qr --exclude=".svn" $ORIG_DIR $NEW_DIR &gt; /tmp/svn_out_orig #figure out what files changed
cat /tmp/svn_out_orig | grep Only &gt; /tmp/svn_diff_files
cat /tmp/svn_out_orig | grep Files &gt; /tmp/svn_out
LINES=`wc -l /tmp/svn_out | cut -d' ' -f1` #how many files changed?
cat /tmp/svn_out | cut -d' ' -f2 &gt; /tmp/sources
cat /tmp/svn_out | cut -d' ' -f4 &gt; /tmp/dests
echo "LINES = $LINES"
while [ $LINES -gt 0 ]
    SOURCE=`sed -n "$LINES"p /tmp/sources`
    DEST=`sed -n "$LINES"p /tmp/dests`
    cp $SOURCE $DEST #copy each file that changed to the new directory
    let "LINES -= 1"
LOG=`svn log --xml -l 1 | grep msg | sed 's/&lt;msg&gt;//p' | sed 's/&lt;\/msg&gt;//p'` #get the log message for  that commit
svn commit -m "$LOG" #commit with message

Jun 12

Linux and Installing

So I’ve been working on a way to have C callback functions call member functions of a C++ class. To do that though, I’m upgrading to gcc 4.6, because there are some new features in c++0x that I think could help me solve this problem easily.

However, upgrading to a new version of gcc is apparently totally fucking impossible. I’m currently installing it from debian-main, because my computer runs Aurora OS, which is based off of an older version of Ubuntu. So, in order to upgrade, I first went to the gcc website and downloaded the source. However, it turns out that the source also has some dependencies. So I went and downloaded and installed those. Then I tried to actually compile the thing, and about an hour later it errored out.


For whatever inane reason, gcc just refuses to compile on my system. I tried reconfiguring, and I even tried going to an Ubuntu PPA, but it just did not like that.

This is one of the reasons that I find Linux so very annoying at times. There’s no easy way to install things. On Windows, you generally just download an installer, it does a few things, and then BAM! you can run the program. Linux is a lot more complicated. Especially because many programs you download and install the source. Seriously, would it be too hard to provide a nice simple way to do this? Moreover, why are there several locations for you to put binaries? You have /usr/local/bin/, /usr/bin, /bin, etc. There’s a bunch of random places that you could hide something. It’s very simple on Windows generally. It would be really nice if we could get something like this for Linux, like a more advanced RPM or something like that. Because I’m in about hour five of trying to upgrade gcc here, and this is just fucking bullshit.