Dual docks in elementary OS

When using two monitors in elementary OS the dock only shows on one monitor. I prefer to have it shown on both monitors. Here’s how it’s done:

Open the terminal and enter the following commands to create a new dock definition with the same settings as your current dock

cd ~/.config/plank
mkdir dock2
cp dock1/settings dock2/
cd dock2

If you’re interested in mirroring the icons (aka launchers) between the two docks, enter the following command

ln -s ../dock1/launchers/ dock2/launchers

If you’d rather see a different set of items in the two docks enter this command

mkdir launchers

By default, both docks will show on the primary screen and in the same position. To change this we need to modify the settings file in the dock2 folder.

Open the settings file in your favorite text editor. You should see a setting for Monitor. Most likely you will need to change this to the number 1 to run it on your second monitor. Your first monitor is monitor 0. There is also a setting for Position if you’d like it to be moved to one of the sides.

You can test your settings for dock2 by entering the following command into the terminal

plank --name dock2

Press Ctrl+C to stop it.
When you’re happy with your settings you need to setup cerbere to launch dock2 at startup. You can use elementary-tweaks or dconf-editor to achieve this (or if you’re really fancy, you can use gsettings).

Here is my monitored processes before and after

['wingpanel', 'plank', 'slingshot-launcher --silent']
['wingpanel', 'plank', 'plank --name dock2', 'slingshot-launcher --silent']

This should work just as well for 3 docks, although I haven’t tested it.

Monday, August 25th, 2014 Uncategorized No Comments

Hacking a Boxee Box remote to work with a PC/Mac

I’ve had a Boxee Box for a few years and while it has served it’s purpose, it’s definitely on it’s last leg. However the remote is a work of genius and I thought I’d see if I could salvage it.

If you crack open the case, you’ll find a board holding both the WiFi card and a Nordic Semiconductor chip that receives signals from the remote.

Boxee Wireless Board

Boxee Wireless Board

Both chips connect to the motherboard over a standard USB connection.

Boxee Wireless Card Pinout

Boxee Wireless Card Pinout

You can try to solder a USB cord directly to the board, or splice into the existing cord. Since I had no use for the WiFi chip, I only spliced into the wires for the remote receiver.

Spliced USB Cable

Spliced USB Cable

WARNING

Both chips on the wireless board require 3.3V while USB supplies 5V. I haven’t looked up the datasheet for the receiver chip, but my guess is that supplying it with 5V would let the smoke out. To remedy this, I added two IN4003 diodes in series. These diodes have a 1.6V forward voltage drop bringing the 5V supply down to 3.6V — much safer.

 

 

Once you have the cable, it’s just a matter of plugging it in and begin typing on the remote. I tested on Linux (3.11 kernel), OSX Mavericks and Windows 7. I’ve read that it will also work with Android. Below are the interesting bits under Linux.

dmesg:

[206798.157442] usb 1-1.6.1: new full-speed USB device number 8 using ehci-pci
 [206798.254222] usb 1-1.6.1: New USB device found, idVendor=1915, idProduct=20d9
 [206798.254227] usb 1-1.6.1: New USB device strings: Mfr=1, Product=2, SerialNumber=0
 [206798.254230] usb 1-1.6.1: Product: D-Link Boxee Receiver d.15
 [206798.254233] usb 1-1.6.1: Manufacturer: D-Link Boxee
 [206798.256424] input: D-Link Boxee D-Link Boxee Receiver d.15 as /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.6/1-1.6.1/1-1.6.1:1.0/input/input18
 [206798.256616] hid-generic 0003:1915:20D9.0007: input,hidraw4: USB HID v1.11 Keyboard [D-Link Boxee D-Link Boxee Receiver d.15] on usb-0000:00:1a.0-1.6.1/input0
 [206798.258966] input: D-Link Boxee D-Link Boxee Receiver d.15 as /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.6/1-1.6.1/1-1.6.1:1.1/input/input19
 [206798.259300] hid-generic 0003:1915:20D9.0008: input,hidraw5: USB HID v1.11 Mouse [D-Link Boxee D-Link Boxee Receiver d.15] on usb-0000:00:1a.0-1.6.1/input1
 [206798.262802] input: D-Link Boxee D-Link Boxee Receiver d.15 as /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.6/1-1.6.1/1-1.6.1:1.2/input/input20
 [206798.263041] hid-generic 0003:1915:20D9.0009: input,hidraw6: USB HID v1.11 Keyboard [D-Link Boxee D-Link Boxee Receiver d.15] on usb-0000:00:1a.0-1.6.1/input2
 [206798.264747] hid-generic 0003:1915:20D9.000A: hiddev0,hidraw7: USB HID v1.11 Device [D-Link Boxee D-Link Boxee Receiver d.15] on usb-0000:00:1a.0-1.6.1/input3

lsusb

Bus 001 Device 008: ID 1915:20d9 Nordic Semiconductor ASA
Tuesday, April 15th, 2014 Electronics, Linux No Comments

New features in super-wingpanel

There have been a number of new features added to super-wingpanel recently. You’ll need to add the unstable repository to get these new features:

sudo apt-add-repository ppa:heathbar/super-wingpanel-daily
sudo apt-get update
sudo apt-get install super-wingpanel

 

Global Menus

Thanks to +Francisco Javier Delgado del Hoyo for adding global menus.

To enable global menus, you’ll need to do the following:

  1. Run these two commands from the terminal
    sudo apt-get install indicator-appmenu
    gsettings set org.pantheon.desktop.wingpanel blacklist "[]"
  2. In elementary tweaks, update cerbere from “super-wingpanel” to “env UBUNTU_MENUPROXY=0 super-wingpanel”
  3. Log out/in. (Yes this is necessary)

 

Removed Dependency on maximus

Thanks to +Sergiy Kusch for providing code to add/remove window controls without requiring maximus installed.

Move Date/Time to the Tray

Thanks to +Sergiy Kusch for adding this option which becomes useful when global menus get long

 

Images/Styling for the Applications button

See this link for styling options: http://www.gtk.org/api/2.6/pango/PangoMarkupFormat.html

super-wingpanel-image-launcher

Here are some images you might want to try: elementary-white  elementary-black

 

Friday, January 31st, 2014 elementary OS, Linux 19 Comments

Charging an iPad Air in Linux

The Backstory

Today I plugged my iPad Air into my MacBook Pro (running Linux of course), but was promptly disappointed to notice the “Not Charging” message on the iPad. A friend suggested that the iPad would charge if the screen was turned off–and indeed it did–albeit very slowly. This behavior is a result of the USB spec’s 500mA limit on standard USB ports. However, in 2007 the USB Battery Charging Specification defined new types of USB ports that could potentially deliver up to 5A (see http://en.wikipedia.org/wiki/USB#Power for more information).

A quick reboot to OSX proved that my MacBook Pro was capable of charging the iPad Air much faster, even with the screen turned on. After a reboot back to Linux a search provided this app, but it couldn’t find any iPads on my system. Digging into the code I found that it was looking for the specific USB Device ID of either an iPad 1 or 2. A little code tweaking and I was able to print out the device IDs for all Apple devices on my system. Since I’m on a MacBook Pro, there were several so I had to run the list twice–with and without the iPad plugged in to determine which device ID corresponded to the iPad. Adding the device ID to the app was pretty straightforward and like magic my iPad was charging full steam ahead.

The Solution

Add the PPA and install the package by running these commands

sudo apt-add-repository ppa:heathbar/ipad-charge && sudo apt-get update
sudo apt-get install ipad-charge

or if you’re the DIY type

bzr branch lp:~heathbar/+junk/ipad-charge && cd ipad-charge
make && sudo make install

Your iPad should be automatically detected and charging enabled when you plug it in. However, if it’s not you may have to troubleshoot

Troubleshooting

If the udev rules are not automatically enabling charging when you plug in your device, you can run the program manually.

ipad-charge

If your iPad is still not charging you can find the device IDs of all Apple products with this command.

ipad-charge -l

If more than one is listed, you may have to run it with your iPad unplugged, then plugged in to find the correct ID. Once you determine the ID, send me a comment and I’ll add it to the app.

 

 

Monday, January 6th, 2014 Linux 4 Comments

super-wingpanel

I’m a big fan of elementary OS. Paraphrasing from their design philosophy, elementary OS offers a concise, simple interface that avoids configuration in an effort to get out of your way so that you can get things done. This is great, except when you want to configure something a little differently. Enter super-wingpanel. Super-wingpanel is a drop-in replacement for elementary’s standard top panel (called wingpanel). It adds a number of features that build on my previous work with wingpanel-slim. If you’re not convinced yet, I’ve included a list of the specific features super-wingpanel adds at the bottom of this post.

If you’re new to linux, installing and configuring super-wingpanel is a little bit more technical than some of the other modifications you can make to elementary OS. That said, I’ll do my best to walk you through it.

 

Installation

First we need to add the super-wingpanel PPA. PPA stands for Personal Packaged Archive. It allows people who are not a part of the elementary project to publish software in the software center. One of the major benefits of adding a PPA versus simply downloading an app from a website is that PPAs are integrated into elementary’s software update system. This makes it easy to ensure you always get the latest features. The super-wingpanel PPA can be added to your system by opening the Terminal application and entering the following command

sudo apt-add-repository ppa:heathbar/super-wingpanel

Next we need to install the app. This can be done through the software center, or since you already have the terminal open, the following commands will do the same thing.

sudo apt-get update
sudo apt-get install super-wingpanel

Now that super-wingpanel is installed, we need to modify our system to use it instead of the standard wingpanel. The easiest way is by using elementary tweaks. If you haven’t installed this piece of software already, you definitely should. Installation instructions can be found here. Tweaks can be opened by launching the System Settings app and selecting Tweaks.

elementary-tweaks-cerbere-super-wingpanel

Once you have Tweaks open, click on the Cerbere tab on the left. Cerbere is the part of elementary OS that makes sure some of the important parts of the operating system are running. It watches the processes (programs) in the list and if for any reason they quit working, Cerbere will re-start them. You’ll notice wingpanel is listed. Double click to edit it and change it to super-wingpanel.

Finally, log out and log back in and you will be using super-wingpanel. Alternatively, if you’ve still got the Terminal app open, you can simply type:

killall wingpanel && super-wingpanel &

 

Configuration

Unlike wingpanel-slim, super-wingpanel doesn’t have a convenient interface to modify its settings. At the time of this writing, if you want to modify super-wingpanel’s settings (which is why you installed it, right?), you’ll need to use dconf-editor. You can install dconf-tools from the software center or by entering the following command in the Termianal

sudo apt-get install dconf-tools

Once dconf-editor is installed and open, navigate to org.pantheon.desktop.super-wingpanel by clicking the little triangles to expand each section

elementary-dconf-editor-super-wingpanel

Features

  • launcher-text-override – don’t like the “Applications” nomenclature? This setting allows you to over write the text of the Applications button.
  • indicator-order – want the sound menu on the far right? This setting allows you to modify how indicators are ordered.
    elementary-super-wingpanel-order-text-overrides
  • show-window-controls – when windows are maximized, the close and maximize button are displayed in the panel. This feature works best in conjunction with maximus.

    elementary-super-wingpanel-window-controls

  • hide-mode – allows you to have the panel hide in various ways, including my favorite Intellislim: slim when you need it, not when you don’t.
  • slim mode – This is the same as wingpanel-slim, but with more features. For the uninitiated, this setting allows the panel to shrink to the smallest possible width which allows windows to be displayed on the topmost part of your screen, ultimately making more room for your stuffelementary-super-wingpanel-slim-mode
  • slim-panel-edge – allows you to select the edge shape of the slim panel
  • slim-panel-position – allows you to specify where you want the slim panel to be positioned
  • slim-panel-margin – allows you to specify the number of pixels the panel stays from the edge of the screen in Elementary Left and Elementary Right modes to allow for non-standard window control configurations
  • slim-panel-separate-launcher – allows you to choose whether the Applications button is a separate panel on the left, or if it is placed in the slim panel next to the date/time.

 

Saturday, December 14th, 2013 elementary OS 11 Comments

Keeping Your Files When Distro Hopping

I currently quad-boot my laptop and each of those OSes occasionally gets replaced. It can be messy to keep track of your documents, music, pictures, etc. when distro-hopping. In the past I’ve tried retaining a separate /home partition. This partially works, but can go bad quickly if you’re running different versions of the same software.  I’ve worked out a slightly different system that I’ve been pretty happy with for the last couple of years.

Using rEFIt to quad boot (without GRUB).

Using rEFIt to quad boot (without GRUB).

The Setup

The first step is to create a separate partition for your media. If your only jumping between Linux distros then you can pick your favorite file system. If you’re looking to share with OSX or Windows you’ll need to compromise with a file system like FAT32 that is supported by these operating systems.

Create a partition for your personal media

Create a partition for your personal media

In the photo above you can see I have set aside space for two Linux distros at the front of my SSD. The remainder I have allocated to my media.

Media Folder

Media Folder

I have moved all of my media files to the media partition and mounted it at /mnt/home with the following entry int /etc/fstab

UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx /mnt/home ext4 defaults 0 2

The magic sauce

Use mount –bind to mount each of these folders in their standard location under your home folder.

The bind option works much like a symlink, with the distinct difference that it’s not a link. Instead, the same data is accessible in two places within the directory tree. By explicitly linking only the folders you want shared, the remaining configuration files can remain different between OSes eliminating any conflicts.

The /etc/fstab entries look like this

/mnt/home/hp/Documents /home/hp/Documents bind defaults,bind 0 0
/mnt/home/hp/Music     /home/hp/Music     bind defaults,bind 0 0
/mnt/home/hp/Pictures  /home/hp/Pictures  bind defaults,bind 0 0

Bonus Tip

If any of your folders have spaces in their name, use \040 as a substitue in /etc/fstab. For example

/mnt/home/hp/VirtualBox\040\VMs  /home/hp/VirtualBox\040VMs  bind defaults,bind 0 0

 

Wednesday, November 13th, 2013 Linux 8 Comments

Transparent Panel in elementary OS

I’ve seen this tweak requested a few times in the G+ community so here is the answer. This modification will affect both the standard wingpanel as well as wingpanel-slim.

Disclaimer: This method will alter the theme for all users of your computer.

First we need to launch Files in administrator mode. If Files is in your dock, you can simply right-click it and select “New Window as Administrator”.

pantheon-files as admin

pantheon-files as admin

In the left-hand pane, click on File System then navigate to the folder /usr/share/themes/elementary/gtk-3.0/

Locate apps.css

Locate /usr/share/themes/elementary/gtk-3.0/apps.css

Click on the file named apps.css. This should launch your text editor.

Scroll down to the section that looks like this:

/*********
* Panel *
********/

Edit apps.css

Edit apps.css

 

The line we are interested in is just below that where it says something like “background-color: alpha(#333, 0.7).

  1. The first value inside the parenthesis represents the hexidecimal color of the panel. If you are not familiar with how hexidecimal colors work, you can use http://www.colorpicker.com/ to select the color.
  2. The second value represents how transparent the panel is on a scale from 0.0 (transparent) to 1.0 (opaque).

IT’S NOT WORKING!

The changes will not take effect immediately; you’ll have to restart the panel. You can do so in one of two ways:

  1. Log out and log back in.
  2. Open the terminal application and type one of the following (depending on which you are currently using):
killall wingpanel
killall wingpanel-slim

The panel will automatically restart with your new settings.

 

Bonus: You’ll notice the shadow is unaffected by the changes we’ve made above. Hopefully you also noticed the next section in the file starts with “.panel-shadow”. The shadow is slightly more complicated as it is a gradient and as such, has two separate alpha colors to specify. By default, the shadow starts at 30% opaque and goes to 0% (completely transparent).

 

 

Tags:

Wednesday, September 18th, 2013 elementary OS 4 Comments

Word Clock: Switching Lights

Progress is coming slowly on the word clocks. I’ve assembled the LED driver boards. These boards host a series of standard serial-in parallel-out 595 shift registers that pump into ULN2003A NPN darlington transistor arrays. The schematic is pretty straightforward.

Simple Schematic

The arduino tells the shift registers which pins to turn on/off. When a pin goes high on the 595, the corresponding channel in the ULN2003A is connected to ground and the LED illuminates. I have to use the transistor for two reasons. First, the control/logic circuitry runs on 5V while the LEDs are running on 12V. Second, since there are multiple LEDs wired to each channel, if they were all wired to a single channel, the shift register would likely not be able to sink that much current and let the smoke out.

While the SN74HC595 shift registers I’m using have 8 outputs per chip, the transistor arrays only hold 7 transistors per chip. The word clocks have 21 switchable channels, so this worked out nicely. I simply left the first output on each of the 595 chips unconnected; it was in a bad location anyway. As they say in Hollywood, “We’ll fix it in post.” (Translation: we’ll fix it in software)

First LED Driver Board

Testing the LED driver board

Testing the LED driver board

Pictured above is the first LED board. It is the simpler of the two as this clock will not have any RGB LEDs.

Second LED Driver Board

Second LED Driver Board

The second LED driver board required a 4th transistor array to switch the RGB LEDs. I’m running straight from the arduino’s PWM pins into the transistors. The are able to keep up with the Arduino’s PWM duty cycle. As a result of the extra IC, I had to rearrange the layout to fit everything all on the circuit board. The shift registers are in the center, while the transistors are on the outside. On the breadboard is an Arduino (or at least the important parts of one).

I didn’t snap any pictures, but the lights are all working on both clocks.

Still on the checklist:

  • Hook up the RTC so that we know what time it is
  • Move the Arduino off the breadboard and onto something more permanent.
  • Write some code to make the magic happen.
Sunday, September 8th, 2013 Word Clock No Comments

Consuming Twitter API 1.1 with Application-Only Auth in Android

Preamble

Recently twitter turned off API 1.0 requiring applications to move to 1.1 and begin using OAuth for all API calls. For those of us who simply wanted to display a user’s timeline in our app, that means things just got a little more complicated.

There are essentially three ways to authenticate with Twitter

  1. Standard OAuth is what you’ll need to use if you want your users to be able to sign in with their own twitter credentials.
  2. Single user mode is great if you are the only person using your application. For instance if your washing machine tweets updates. It’s much simpler to setup than Standard OAuth, but all actions will be performed in the context of the single user you specify in the code. (more info here)
  3. Application Only is a little different in that instead of authenticating a user, you authenticate the application. Again, this type of authentication is a little easier to setup, but it comes with the limitation that you cannot perform any actions in the context of a user.

For my application I only needed to display the timelines from a handful of users. Since searching/reading tweets is public information I can use Application Only authentication. This process is documented already here, but this post will supplement some Android-specific code.

Prerequisites

The first thing you will need to do is sign into https://dev.twitter.com. Go to the “My Applications” section and create a new application for yourself.  Twitter will assign you a Consumer Key and a Consumer Secret.

Getting Down to Business

We need to obtain a “bearer token” which will serve as the login credentials for our application. To obtain the bearer token we submit a specially formatted request to twitter. The request should look like this (without the line break on the Authorization line):

POST /oauth2/token HTTP/1.1
Host: api.twitter.com
User-Agent: My Twitter App v1.0.23
Authorization: Basic eHZ6MWV2RlM0d0VFUFRHRUZQSEJvZzpMOHFxOVBaeVJnNmllS0dFS2hab2xHQzB2SldMdzhpRUo4OERSZHlPZw==

Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Content-Length: 29
Accept-Encoding: gzip

grant_type=client_credentials

We construct this request in Android as follows:

DefaultHttpClient httpclient = new DefaultHttpClient(new BasicHttpParams());
HttpPost httppost = new HttpPost("https://api.twitter.com/oauth2/token");

final String APIKEY = "<Your Consumer Key>";
final String APISECRET = "<Your Consumer Secret>";

String apiString = APIKEY + ":" + APISECRET;
String authorization = "Basic " + Base64.encodeToString(apiString.getBytes(), Base64.NO_WRAP);

httppost.setHeader("Authorization", authorization);
httppost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
httppost.setEntity(new StringEntity("grant_type=client_credentials"));

InputStream inputStream = null;
HttpResponse response = httpclient.execute(httppost);
HttpEntity entity = response.getEntity();

inputStream = entity.getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"), 8);
StringBuilder sb = new StringBuilder();

String line = null;
while ((line = reader.readLine()) != null)
{
    sb.append(line + "\n");
}

The StringBuilder sb will now contain a JSON formatted response containing your bearer token. It will look something like this

{“token_type”:”bearer”,”access_token”:”AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%2FAAAAAAAAAAAAAAAAAAAA%3DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA”}

We will include the bearer token in every call we make to the API. It acts as our authentication. Before we jump into using the bearer token there are some things you should know about it.

  • It uniquely identifies your application to twitter. Do not post it publicly.
  • It can be revoked by twitter. If this happens, you will need to generate a new token using the process above.
  • It can be revoked by you. You would want to do this if someone else obtained the token and was using it without your permission

So while theoretically you could hard code the bearer token into your app, it’s better to save it to your application’s database or as a preference. If the token is revoked, your application should run the code above to get a new token.

Making the Call

Calling the API with the bearer token is very similar to what we did before. We simply add the bearer token as a header in the HTTP request like this

GET /1.1/statuses/user_timeline.json?count=100&screen_name=twitterapi
HTTP/1.1 Host: api.twitter.com User-Agent: My Twitter App v1.0.23
Authorization: Bearer AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%2FAAAAAAAAAAAAAAAAAAAA%3DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Accept-Encoding: gzip

Which is created by the following Android code. The variable url specifies the API you are attempting to access. For instance https://api.twitter.com/1.1/statuses/user_timeline.json?count=100&screen_name=twitterapi

DefaultHttpClient httpclient = new DefaultHttpClient(new BasicHttpParams());
HttpGet httpget = new HttpGet(url);
httpget.setHeader("Authorization", "Bearer AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%2FAAAAAAAAAAAAAAAAAAAA%3DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
httpget.setHeader("Content-type", "application/json");

InputStream inputStream = null;
HttpResponse response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();

inputStream = entity.getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"), 8);
StringBuilder sb = new StringBuilder();

String line = null;
while ((line = reader.readLine()) != null)
{
 sb.append(line + "\n");
}

Once again, the StringBuilder sb will contain the JSON encoded response from the API.

 

Here is an example eclipse project. Simply provide your API Key and API Secret in MainActivity.java.

TwitterApiExample

Saturday, June 15th, 2013 Android 15 Comments

Custom Keybindings in elementary OS

The keyboard-settings pane in Ubuntu allows custom keyboard combinations to launch applications or scripts. This feature is currently missing from elementary OS’s keyboard plug. However, with a little command line foo we can setup the keybindings without the GUI.

First we need to install gconf2. It may be installed already, but in case it’s not:

sudo apt-get install gconf2

Next we use gconf tool to query which keybindings are already out there:

gconftool-2 --all-dirs "/desktop/gnome/keybindings"

On my machine, this returns 1 entry: /desktop/gnome/keybindings/custom0 We can dig deeper by executing:

gconftool-2 --all-entries "/desktop/gnome/keybindings/custom0"

name = Launch Terminal
action = x-terminal-emulator
binding = <Control><Alt>t

We can see that by default there is only 1 keybinding that launches a terminal. Now we can create our own with the following commands:

gconftool-2 --set "/desktop/gnome/keybindings/custom1/name" --type string "Launch Switchboard"
gconftool-2 --set "/desktop/gnome/keybindings/custom1/action" --type string "switchboard"
gconftool-2 --set "/desktop/gnome/keybindings/custom1/binding" --type string "<Control><Alt><Shift>s"

Each keybinding needs it’s own folder. In this case, I used “custom1″ since “custom0″ was already in use. The rest should be pretty self-explanatory: give it a name, an action (command or script to run) and the keybinding you’d like to use.

 

Monday, May 20th, 2013 elementary OS 5 Comments