Showing posts with label gnome. Show all posts
Showing posts with label gnome. Show all posts

Sunday, 13 May 2012

Automatically undecorate maximised windows in GNOME Shell

One feature I really liked about the Ubuntu Netbook Remix (while it lasted) was how whenever you maximised a window, it removed the title bar. The program that did this was called Maximus.

This was really handy for saving a bit of vertical screen real estate, especially when you had a small 10" screen. Yes, it became harder to unmaximise a window/drag it around/resize it, but on a 10" screen you don't do that often anyway as it's really not big enough to bother with having multiple windows visible at once.

Anyhow, I wanted this ability in GNOME shell (undecorate maximised windows), so I wrote an extension to do this.

Maximus GNOME shell extension undecorates maximised windows Without Maximus: title bar consumes vertical space.

Before I give the link, I'll mention that you don't actually need an extension to get this behaviour; this blog post gives two ways to do it:

  1. Install Maximus: if you're lucky or have Ubuntu, you can just sudo apt-get install maximus. This will install the original Maximus program for you and then you're done. However if you have Fedora, Maximus is not in the package repositories. In that case you could try to download the source and build/install it yourself. This isn't as easy as simply installing a program or extension though.
  2. Edit your window theme: you can edit the theme GNOME-shell uses to decorate your windows to artificially remove the title bar from maximised windows. See Method 2 in this blog post for how. This is nice and easy, but you have to do it every time you change your window theme.

I'd recommend installing Maximus from your package manager if you have it (i.e. Ubuntu). Otherwise, edit your window theme (knowing that it could get written over every time you change your theme), or try my Maximus GNOME shell extension (from extensions.gnome.org).

The Maximus GNOME shell extension is just an extension that emulates the behaviour of the original Maximus program. All it does is undecorate windows that are maximised.

The homepage/repository/instructions are here. For a one-click install just visit extensions.gnome.org (or you can also download the zip file and use gnome-tweak-tool to install it ('Shell Extensions' > 'Install Extension' > select .zip file). Make sure you additionally enable it.)

Enjoy & let me know of any bugs :)


For developers: How it's done

For those who are interested, this is how I managed to get the windows undecorated.

Recall that in GNOME-shell, window objects are Meta.Windows (using the Mutter = Metacity + Clutter bindings). There are four ways I tried to undecorate/redecorate a window:

  • use the Mutter interface: try modifying the window.decorated property of a Meta.Window.
  • use the Gtk interface: try using window.set_hide_titlebar_when_maximised() or window.set_decorated(). on a Gtk.Window.
  • use the Gdk interface: try using window.set_decorations() on a Gdk.Window.
  • make an external call to xprop to set the window manager hints saying to undecorate the window.

Mutter: window.decorated = false

It would be ideal to undecorate using the Mutter bindings as GNOME-shell treats all windows as Meta.Windows anyway.

There is a window.decorated property for Meta.Windows but it's treated as read-only (as of GNOME 3.4.1); setting it to true or false has no effect. There is no way to set the window's decorations from the current (GNOME 3.4.1) Mutter gobject introspection. Not surprising; the Mutter API can't do much at the moment.

Gtk: window.set_hide_titlebar_when_maximised

Now assume that we could convert a Meta.Window into a Gtk.Window. Then we'd be able to use the function gtk_window_set_hide_titlebar_when_maximised() to get the behaviour we wanted, or if that didn't work, gtk_window_set_decorated().

However I couldn't find a way to make a GTK window from a Meta.Window, and in fact I'm not sure if it's possible/GTK works like that.

Gdk: window.set_decorations

Well, why not assume we could convert a Meta.Window into a Gdk.Window. Then we could use the function gdk_window_set_decorations(0) to remove decorations from a window.

How to create a Gdk.Window from a Meta.Window from the gobject introspection bindings? The best I could find was a function gdk_x11_window_foreign_new_for_display, which will create a Gdk.Window given the window's X ID. In the GNOME javascript bindings:

const Gdk = imports.gi.Gdk;
const GdkX11 = imports.gi.GdkX11;

let window = GdkX11.X11Window.foreign_new_for_display( Gdk.Display.get_default(), WINDOW_XID );
// remove decorations
window.set_decorations(0);
// add back decorations
window.set_decorations(Gdk.WMDecoration.ALL);

Assuming you can find the window's X ID (for example with xwininfo), this works!

However, this is another unfortunate caveat; when you call this code from within the GNOME-shell process (i.e. from a GNOME shell extension), the window gets killed! If you run the exact same code using the gjs binary, or you write the exact same code in Python with the Python bindings, it works!.

After a bit of asking around, it turns out that using the above code simply won't work as long as it's run directly from a gnome shell extension; I have to make sure that the code that undecorates the window happens from an external process. This means making an external call (for example, gjs undecorate_window.js) from my code.

Then I'll need to somehow pass the window's X ID into the script from the extension to make sure it undecorates the right window.

So yes, this option is viable, but given that I have to make an external call anyway I'd rather not have to load up the various libraries in the external script each time a window is maximised.

xprop

xprop is a command-line utility for examining/setting the X properties of a window.

Looking through the set_decorations code in GDK and the C source for Maximus, to undecorate a window one sets the _MOTIF_WM_HINTS property on the window. In the hints one specifies that the window should be undecorated. When the window manager looks at these hints it then attempts to undecorate the window according to the hints.

Translating this into using xprop to set the hints, one uses the following command (credit to this post that got me started):

xprop -id [window_XID] -f _MOTIF_WM_HINTS 32c -set _MOTIF_WM_HINTS "0x2, 0x0, 0x0, 0x0, 0x0"

To explain:

  • -id [window_XID]: this tells xprop which window we wish to modify the hints of.
  • -f _MOTIF_WM_HINTS 32c: this specifies the format of the _MOTIF_WM_HINTS property, each field being a 32bit unsigned integer.
  • -set _MOTIF_WM_HINTS 0x2, 0x0, 0x0, 0x0, 0x0: says we wish to set the value of _MOTIF_WM_HINTS to 0x2, 0x0, 0x0, 0x0, 0x0.

A quick explanation of what all the numbers in _MOTIF_WM_HINTS mean (see MwmUtil.h from the OpenMotif source for the documentation, or there is some rudimentary online documentation here).

The hints structure has 5 fields, and (in order) they are:

  • what fields we're specifying with the hints: the window functions, decorations, input mode and/or status.
  • the hints for the window functions. That is, which buttons to display on the window's title bar: maximise, minimise, close, ...
  • the decorations to be drawn on the window: title bar, border, all decorations, no decorations, ...
  • the input mode of the window: modeless (can't click on it), application modal, sytem modal, ...
  • the status of the window: whether it's a tearoff window or not.

So in the above, 0x2, 0x0, 0x0, 0x0, 0x0 says that:

  • we want to specify the decorations of the window (0x2); ignore all the other fields
  • we want there to be no decorations on the window (the third number is 0x0).

If we wanted to decorate the window we'd set the window hints to 0x2, 0x0, 0x1, 0x0, 0x0.

Summary

Out of all of the above, only xprop and the GDK version were viable. Since I have to make an external call to undecorate the window anyway, I'd rather have my external call be xprop -id ... than gjs undecorate_window.js because in the latter gjs has to load all the relevant modules to execute the code, whereas the former is a simple command-line call that does the undecorating directly.

So the only question left is how to get the X ID of a window in order to pass it in to xprop (we'd have the same problem with Gdk too).

Getting the X ID of a window

There is currently (GNOME 3.4.1) no official way to get the X ID from a Meta.Window. If I listen to maximise events, I get a Meta.Window specifying the window that was maximised. There is no method in the API to extract this window's X ID. (aaargh!)

So how can I find the Meta.Window's X ID? I could try convert it into a Wnck window which has a get_xid method, but there's no way to do this directly other than looping through a list of Wnck windows and (say) comparing titles to determine whether the Wnck window is the same as the Meta.Window. This is prone to error - what happens when you have multiple windows with the same title? How will you work out which Wnck window is your window?

After a bit of digging around in the source of Mutter, I found two (somewhat hacky) ways to grab a Meta.Window's X ID.

Method 1: Using the window's description

It turns out that the window.get_description() of a Meta.Window consists of its X ID followed by its name (truncated to 10 characters) in brackets. For example, 0x26005b (Google Chr).

So, method 1 is to extract the X ID from the description of the window. However, it isn't guaranteed that the window's description will have the X ID in it in future releases of GNOME/Mutter; it's just lucky that in 3.2 to 3.4 this is what the description is set to.

Method 2: Using the window frame's X ID

If one has the actor for a Meta.Window, then the property actor['x-window'] is the X ID of the window's frame.

By the frame of the window, I mean that every window has a frame created for it by the window manager, actor['x-window'] is the X ID for that frame.

However, to perform undecoration/decoration of windows, one must use the X ID for the client window, being the window that the frame contains.

The client window is actually the child of the frame window. By using xwininfo -children -id [frame_XID] we can get a list of all the X IDs for the frame window's children, namely the one client window.

So method 2 is to make an external call to xwininfo and parse the output the extract the child's X ID. Again, it is not guaranteed that actor['x-window'] will still be there in future releases of GNOME.

Failing that

If the above two methods fail to locate a window's X ID we can use its title as a last resort. This is because xprop -id [window's XID] can be replaced with xprop -name [window's title] to identify the window in question. However if you have multiple windows of the same title, you aren't guaranteed that xprop will identify the particular window you're after.

Putting it all together.

Putting it all together, this is what happens:

  1. Create a function onMaximise that fires whenever a window is maximised. One of its arguments is the window (that was maximised)'s actor.
  2. Get the window's X ID, first by trying to parse the window's description and failing that by calling xwininfo on actor['x-window'] to get the child's X ID.
  3. If we found the X ID, then call xprop -id [XI D] -f _MOTIF_WM_HINTS 32c -set _MOTIF_WM_HINTS "0x2, 0x0, 0x0, 0x0, 0x0". If we didn't, then call xprop -name [window.get_title()] ....

If you find a less cludgy way to do this (i.e. doing the external system call) please let me know!

Thursday, 12 April 2012

Reduce horizontal spacing between icons in GNOME 3.2

One thing that annoys me about the default GNOME shell is the amount of horizontal space it wastes in between icons/indicators in the status area (top right on the panel).

Urgh look how much space is between all the icons!

I wrote a very simple GNOME shell extension to fix this. (My first ever! More an exercise in how to do it than anything else).

It's available for one-click install from extensions.gnome.org. The repository (for anyone interested in the code) is here.

If for some reason you don't want to/can't install from extensions.gnome.org, you can go to the 'Downloads' page and download the .zip file. Then start gnome-tweak-tool, select "Shell Extensions", "Install Shell Extensions", and choose the .zip file. Restart the gnome-shell, enable the extension, and you're all done!

The result?

Nice and slim

Voila! Enjoy :)

What it Does

It basically just modifies these lines from the file /usr/share/gnome-shell/theme/gnome-shell.css:

.panel-button {
    -natural-hpadding: 12px;
    -minimum-hpadding: 6px;
    /* ... and so on */
}

The attribute -natural-hpadding is set by default to 12 pixels wide. My extension sets this to 6 pixels wide (you can modify this if you want; just change the relevant lines in stylesheet.css that comes with the extensions).

If you set -naturial-hpadding below 6 pixels you will have to adjust -minimum-hpadding to match it too.

Shell Extensions in Gnome 3.2/Fedora 16

I just upgraded my work computer from Fedora 15 to Fedora 16, which uses Gnome 3.2. This is my first real experience with the GNOME shell in Fedora, because I got rid of it straight away (fallback mode) in F15 since I couldn't stand it so haven't used it since.

Anyhow, I thought I'd give it a shot this time as the GNOME team have apparently been doing good things with the newer versions of GNOME shell.

My first impression - danger Will Robinson! Switch back to fallback mode!

  • poor support for dual monitor which I use at work: top panel is only on one screen
  • to get to the Overview to switch/view windows I have to hover over the top left corner of the left-most screen, rather than just clicking a window from the top panel
  • the clock is in the middle of my top panel, meaning I wouldn't be able to see my current window title, except that
  • the window title of the current window I'm in doesn't show in the top panel. Just the application name.

I was just about to switch to fallback mode again, when I discovered GNOME shell extensions. These are little plugins (think of them like browser plugins) that add functionality to the GNOME shell interface.

Below I'll summarise some of the shell extensions that I found at the GNOME shell extensions website that rectified my problems. You generally install them from that website, and then use gnome-tweak-tool (or "Advanced Settings" in Fedora) to toggle them on and off.

You'll need to log out and in again or restart the shell (Alt+F2, type 'r' for restart, press Enter) to have them take effect.

Top Panel

Frippery Move Clock moves the clock to the left

Frippery Move Clock This moves the clock from the centre of the panel to the left of the status menu button (over the right of the screen). This frees up space for extensions that show icons for open windows, etc. Essential.

Extend Left Box This allows extensions using the top panel (e.g. window list extensions) to use the whole of the top panel instead of just the left half. Essential.

Status Title Bar This shows the entire title (e.g. "Blogger: Mathematical Coffee - Edit post - Google chrome") of your current window in the top panel, rather than just the application name ("Chrome"). Whether to use this or not depends on your top panel real-estate.< /p>

Status Title Bar with Extend Left Box show the full window title

Notification Icons

The following extensions affect the notification icons up the top-right of the screen (accessibility, volume, ...) and the messaging tray at the bottom of the screen.

Evil Status Icon Forever There are many extensions that hide icons (e.g. accessibility) or move icons from the messaging tray to the notification bar (dropbox, ...), but this one seems to cover the functionality of all of them. This allows you to set which notification icons to move from the messaging tray to the top panel (I added dropbox and guake up there) and which to hide (the accessibility icon). For instructions see here.

Evil Status Icon Forever lets me put dropbox & other icons up the top;
notice also the workspace indicator.
Places menu

Places Status Indicator adds a folder to the notification tray that, when clicked, shows a list of folders to open in nautilus.

If you want to remove an individual icon (accessibility, bluetooth, volume, ..) from the notification bar, use Evil Status Icon Forever. If for some reason you don't want to do that, you can install the volume icon remover, bluetooth icon remover, or gnome-shell-extension-remove-accessibility-icon from the fedora repositories.

Workspace indicator adds a little drop-down menu from the notification area letting you switch between workspaces (The '1' in the box in the above picture). Since I added this functionality on my docklet (later in this post), I didn't need this any more).

Status Icon

The "Status Icon" is the menu at the top-right of your panel with a speech bubble next to your user name. The speech bubble is your chat availability - you can control it from there. The menu also contains the "System Settings", "Log out", etc options.
Chat, Hibernate, Power Off options;
No name on the button

Alternative Status Menu By default the status menu only gives a "Suspend" option for closing your computer, and you have to hold down Alt to see "Hibernate" or "Power Off". This extension adds the "Hibernate" and "Power Off" buttons permanently to the menu. Very useful (for me anyway), because I want to power off my computer much more often than suspend it.

Status Only icon removes your username from next to the speech bubble, leaving just the speech bubble. I know my name already so there's no use having it there taking up space!

Empathy Menu You can set your availability for chat with the status menu, but you can't actually start a chat from it. What a pain! This extension adds an option "Chat" to the menu that launches empathy (there are similar ones for Pidgin and Gwibber). I still think I'd like to be able to click on a contact's name from that menu though, like Unity.

Notifications Alert this paints the speech bubble on the status menu red when you have a new notification (e.g. IM messages).

Window list

My biggest gripe is not having a one-click window list. I just want something like the good old gnome-panel. There are a number of extensions providing this functionality. These all work best with Frippery Move Clock extension and the Extend Left extension.

Dock this puts a permanent dock on the left or right side of the screen, the same dock that appears in the overview. You can configure it to autohide or not, and which side of the screen to put it on (not top and bottom though, and doesn't support one per monitor). Quite slick, although not very customisable.

Window List does what it says on the can. Adds window buttons (icon and text) to the top panel. Like the Windows XP task bar.

Window list

Window Icon List Adds icons for open windows to the top panel, like Windows 7.

Panel Docklet S I've saved the best til last! This is the extension I ended up going with - sleek, infinitely customisable. It lets me create either a panel or docklet (my chose) on any side of the screen I like. I can show one icon per group of icons (text too if I want), or one icon per application, and best of all, I can put one docklet per monitor, and I'll only get icons for applications on that monitor! I can also add a workspace switcher (depreciating the need for a separate workspace switcher app), add a button to go to the desktop, add my 'favourites' (on the side dock) as icon shortcuts, and much, much more. I strongly recommend this. The only downfall is that I find that the panel on the second monitor sometimes shows icons of applications even though they've already been closed, and if I right click on them gnome-shell crashes :(.

Panel Docklet S in 'docklet' mode
Panel Docklet S in 'panel-docklet' mode over my top panel

Other

User Themes: this allows you to install custom skins for the GNOME shell and switch between them from gnome-tweak-tool.

Lame Extensions Manager while you're playing around with all the shiny extensions on extensions.gnome.org, this provides a button in the status bar allowing you to toggle them on and off quickly.\

Summary

In the end, I managed to fix most my gripes - removed notification icons I don't use, moved the clock to the left, removed the name from the status bar, and these freed up a lot of space in the top panel.

With that free space I dumped a Panel Docklet S there (in the 'docklet panel' mode) which allows me to quickly see what windows I have open on a particular monitor and go to them (as well as letting me switch between workspaces), and changed the title bar to show the full name of the current window rather than the application name.

All in all, that covers most the gripes I started with! The fact that I can make one docklet per monitor soothes the gnome-shell dual-monitor pain, and the docklet really lets me do most things I wanted (workspace and window switching, shortcuts).