This is the sidebar of my nautilus window. You can see two different icons - one for the USB sticks and one for drives.
How does nautilus determine which gets a hard drive icon and which gets a flash drive one?
This is the sidebar of my nautilus window. You can see two different icons - one for the USB sticks and one for drives.
How does nautilus determine which gets a hard drive icon and which gets a flash drive one?
TL;DR: Nautilus uses Gio's GDrive,GVolume, and GVolumeMonitor interfaces to obtain icons corresponding to particular device.
Gio has set of classes which allow applications to read available drives and volumes, and what icons are associated to them (and that's something API already handles). The icons are returned as Gio Icon
type, so applications don't particularly have to know where the icon is located, however requesting an icon by name or by full path is always possible. There's two types of icons you will see, "symbolic" and custom, and "symbolic" icons are sort of standard. If one looks through /usr/share/icons
they'll find there are lots of icons that end with -symbolic
in their name, stored across multiple theme folders (standard Adwaita, Humanity, and Oxygen are good examples) , typically .svg
files. In the screenshot in the question, the icon for hard drive is drive-harddisk-symbolic
and drive-removable-media-usb
. When user switches desktop theme, the application doesn't have to look up full path to the icon - so long as there is drive-harddisk-symbolic
icon in the theme folder, the Gio will find it and return to the application. How do I know all this ? I've used those same icons for my own UDisks Indicator (though my approach differs from what Nautilus does).
From reading Nautilus source code, specifically nautilusgtkplacesview.c code, essential parts are the following:
Nautilus adds drives and volumes (think partitions). There's add_drive function, which uses Gio's g_drive_get_volumes (drive)
function to get volumes on specific drive, and passes that information to Nautilus's add_volume()
function. Gio's g_volume_monitor_get_volumes()
on lines 1139 and g_volume_monitor_get_mounts() 1164 fetches volumes that may not be associated with a drive (such as ftp or network shares), but that same information goes to add_volume()
function and add_mount()
.
Icons are queried for mountpoints and volumes: Inside add_volume()
function, Gio's g_volume_get_icon()
function fetches the icon of type GIcon
. Inside add_mount()
Gio's g_mount_get_icon()
also of type GIcon
. In both cases, icon along with other information are combined into an object of type NAUTILUS_TYPE_GTK_PLACES_VIEW_ROW
and passed to insert_row()
function
Rows inserted into Places container: The Places menu that you show in the example actually is one of the basic Gtk container types - Gtk Box. Inside that box there's ListBox for subsections - that's why there's separators for all user's folders, drives and volumes, and bookmarks. Nautilus creates an object which extends from Gtk Box type, G_DEFINE_TYPE_WITH_PRIVATE (NautilusGtkPlacesView, nautilus_gtk_places_view, GTK_TYPE_BOX)
. The G_DEFINE_TYPE_WITH_PRIVATE is another standard function , and as you can see in Nautilus's definition the last item defines GTK_TYPE_BOX
- parent object. In the .c
file on line 50, NautilusGtkPlacesViewPrivate
structure is defined, which among other things has pointer to ListView widget. That's the actual contents of the object.
Now, insert_row()
function takes instance of that type (NautilusGtkPlacesView *view
), connects whole bunch of signals to the row item it received as argument, and using gtk_container_add
inserts all the information ( along with the icon ) into the ListBox widget of the Nautilus Places object.
Lengthy explanation and probably looks simpler in a diagram, but also that's all written in C. Let's try to make something ourselves in Python which is by far simpler and easier.
Here's a simple window that utilizes Gio's Drive interface to create buttons with icons corresponding to the attached drives. This is overly simplistic and needs polish, and inline-comments
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gio,Gtk
class drives_window(Gtk.Window):
def __init__(self):
super().__init__(title="Foobar")
self.volume_monitor = Gio.VolumeMonitor.get()
# We'll use Gtk.Box to hold all the buttons corresponding to each drive
# although we haven't connected the buttons to any other function
# so clicking on the does nothing
self.box = Gtk.Box()
for drive in self.volume_monitor.get_connected_drives():
button = Gtk.Button()
button.set_label(drive.get_name())
button.set_always_show_image(True)
icon = drive.get_symbolic_icon()
# buttons need Gtk.Image widget, but .get_symbolic_icon() returns Gio.ThemedIcon
# so we create new Gtk.Image using .new_From_gicon() function
# Gtk.IconSize.BUTTON is a constant
button.set_image(Gtk.Image.new_from_gicon(icon,Gtk.IconSize.BUTTON))
self.box.pack_start(button,True,True,0)
# add the box container to this window
self.add(self.box)
window = drives_window()
window.connect("destroy",Gtk.main_quit)
window.show_all()
Gtk.main()
Of course nowadays you don't have to reinvent the wheel. Gtk provides PlacesSidebar
widget.
#!/usr/bin/env python3
from gi.repository import Gtk,Gio,GLib
w = Gtk.Window()
b1 = Gtk.Box()
p = Gtk.PlacesSidebar()
b1.pack_start(p,True,True,0)
b1.pack_start(Gtk.Button("Hello World"),True,True,0)
b1.pack_start(Gtk.Button("Hello World 2"),True,True,0)
w.add(b1)
w..connect("destroy",Gtk.main_quit)
w.show_all()
Gtk.main()
Sidenote: Nautilus also has a header file nautilus-icon-names.h which defines constants with NAUTILUS_
prefix, such as
#define NAUTILUS_ICON_FILESYSTEM "drive-harddisk-symbolic"
probably for developer/code consistency, instead of having to rely on looking up actual icon names. The only place where that particular definition is used is in get_icon
function, and that function ironically is not used for Places side bar but is used inside pathbar updating function. Go figure,right ? ¯\_(ツ)_/¯