autorun: add an AutorunManager class
AutorunManager is a class that takes care of displaying and managing notifications and UI for storage devices. When a mount appears and a number of conditions are satisified, a transient notification will be displayed to immediately interact with the device. AutorunTransientDispatcher is the object that takes care of showing/hiding the notification sources as devices appear/disappear. Likewise, current mounts are kept in a list and presented within a list in a resident notification, handled by AutorunResidentSource. https://bugzilla.gnome.org/show_bug.cgi?id=653520
This commit is contained in:
parent
6004e3d2e1
commit
534b371d42
@ -1139,6 +1139,83 @@ StTooltip StLabel {
|
|||||||
icon-size: 36px;
|
icon-size: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hotplug-transient-box {
|
||||||
|
spacing: 6px;
|
||||||
|
padding: 2px 72px 2px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hotplug-notification-item {
|
||||||
|
background-color: #3c3c3c;
|
||||||
|
padding: 0px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #181818;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hotplug-notification-item:hover {
|
||||||
|
border: 1px solid #a1a1a1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hotplug-notification-item:focus {
|
||||||
|
background-color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hotplug-notification-item:active {
|
||||||
|
border: 1px solid #a1a1a1;
|
||||||
|
background-color: #2b2b2b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hotplug-notification-item-icon {
|
||||||
|
icon-size: 24px;
|
||||||
|
padding: 2px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hotplug-resident-box {
|
||||||
|
spacing: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hotplug-resident-mount {
|
||||||
|
spacing: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hotplug-resident-mount:hover {
|
||||||
|
background-gradient-direction: horizontal;
|
||||||
|
background-gradient-start: rgba(255, 255, 255, 0.1);
|
||||||
|
background-gradient-end: rgba(255, 255, 255, 0);
|
||||||
|
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hotplug-resident-mount-label {
|
||||||
|
color: inherit;
|
||||||
|
padding-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hotplug-resident-mount-icon {
|
||||||
|
icon-size: 24px;
|
||||||
|
padding-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hotplug-resident-eject-icon {
|
||||||
|
icon-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hotplug-resident-eject-button {
|
||||||
|
padding: 2px;
|
||||||
|
border: 1px solid #2b2b2b;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hotplug-resident-eject-button:hover {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #2b2b2b;
|
||||||
|
border: 1px solid #a1a1a1;
|
||||||
|
}
|
||||||
|
|
||||||
.chat-log-message {
|
.chat-log-message {
|
||||||
color: #888888;
|
color: #888888;
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ nobase_dist_js_DATA = \
|
|||||||
ui/altTab.js \
|
ui/altTab.js \
|
||||||
ui/appDisplay.js \
|
ui/appDisplay.js \
|
||||||
ui/appFavorites.js \
|
ui/appFavorites.js \
|
||||||
|
ui/autorunManager.js \
|
||||||
ui/boxpointer.js \
|
ui/boxpointer.js \
|
||||||
ui/calendar.js \
|
ui/calendar.js \
|
||||||
ui/chrome.js \
|
ui/chrome.js \
|
||||||
|
499
js/ui/autorunManager.js
Normal file
499
js/ui/autorunManager.js
Normal file
@ -0,0 +1,499 @@
|
|||||||
|
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||||
|
|
||||||
|
const Lang = imports.lang;
|
||||||
|
const Gio = imports.gi.Gio;
|
||||||
|
const St = imports.gi.St;
|
||||||
|
|
||||||
|
const Main = imports.ui.main;
|
||||||
|
const MessageTray = imports.ui.messageTray;
|
||||||
|
|
||||||
|
// GSettings keys
|
||||||
|
const SETTINGS_SCHEMA = 'org.gnome.desktop.media-handling';
|
||||||
|
const SETTING_DISABLE_AUTORUN = 'autorun-never';
|
||||||
|
|
||||||
|
const HOTPLUG_ICON_SIZE = 16;
|
||||||
|
|
||||||
|
// misc utils
|
||||||
|
function ignoreAutorunForMount(mount) {
|
||||||
|
let root = mount.get_root();
|
||||||
|
let volume = mount.get_volume();
|
||||||
|
|
||||||
|
if ((root.is_native() && !isMountRootHidden(root)) ||
|
||||||
|
(volume && volume.should_automount()))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMountRootHidden(root) {
|
||||||
|
let path = root.get_path();
|
||||||
|
|
||||||
|
// skip any mounts in hidden directory hierarchies
|
||||||
|
return (path.indexOf('/.') != -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startAppForMount(app, mount) {
|
||||||
|
let files = [];
|
||||||
|
let root = mount.get_root();
|
||||||
|
files.push(root);
|
||||||
|
|
||||||
|
try {
|
||||||
|
app.launch(files,
|
||||||
|
global.create_app_launch_context())
|
||||||
|
} catch (e) {
|
||||||
|
log('Unable to launch the application ' + app.get_name()
|
||||||
|
+ ': ' + e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************************/
|
||||||
|
|
||||||
|
function ContentTypeDiscoverer(callback) {
|
||||||
|
this._init(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentTypeDiscoverer.prototype = {
|
||||||
|
_init: function(callback) {
|
||||||
|
this._callback = callback;
|
||||||
|
},
|
||||||
|
|
||||||
|
guessContentTypes: function(mount) {
|
||||||
|
// guess mount's content types using GIO
|
||||||
|
mount.guess_content_type(false, null,
|
||||||
|
Lang.bind(this,
|
||||||
|
this._onContentTypeGuessed));
|
||||||
|
},
|
||||||
|
|
||||||
|
_onContentTypeGuessed: function(mount, res) {
|
||||||
|
let contentTypes = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
contentTypes = mount.guess_content_type_finish(res);
|
||||||
|
} catch (e) {
|
||||||
|
log('Unable to guess content types on added mount ' + mount.get_name()
|
||||||
|
+ ': ' + e.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// we're not interested in win32 software content types here
|
||||||
|
contentTypes = contentTypes.filter(function(type) {
|
||||||
|
return (type != 'x-content/win32-software');
|
||||||
|
});
|
||||||
|
|
||||||
|
this._callback(mount, contentTypes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function AutorunManager() {
|
||||||
|
this._init();
|
||||||
|
}
|
||||||
|
|
||||||
|
AutorunManager.prototype = {
|
||||||
|
_init: function() {
|
||||||
|
this._volumeMonitor = Gio.VolumeMonitor.get();
|
||||||
|
|
||||||
|
this._volumeMonitor.connect('mount-added',
|
||||||
|
Lang.bind(this,
|
||||||
|
this._onMountAdded));
|
||||||
|
this._volumeMonitor.connect('mount-removed',
|
||||||
|
Lang.bind(this,
|
||||||
|
this._onMountRemoved));
|
||||||
|
|
||||||
|
this._transDispatcher = new AutorunTransientDispatcher();
|
||||||
|
this._residentSource = new AutorunResidentSource();
|
||||||
|
this._residentSource.connect('destroy', Lang.bind(this,
|
||||||
|
function() {
|
||||||
|
this._residentSource = new AutorunResidentSource();
|
||||||
|
}));
|
||||||
|
|
||||||
|
let mounts = this._volumeMonitor.get_mounts();
|
||||||
|
|
||||||
|
mounts.forEach(Lang.bind(this, function (mount) {
|
||||||
|
let discoverer = new ContentTypeDiscoverer(Lang.bind (this,
|
||||||
|
function (mount, contentTypes) {
|
||||||
|
this._residentSource.addMount(mount, contentTypes);
|
||||||
|
}));
|
||||||
|
|
||||||
|
discoverer.guessContentTypes(mount);
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
_onMountAdded: function(monitor, mount) {
|
||||||
|
let discoverer = new ContentTypeDiscoverer(Lang.bind (this,
|
||||||
|
function (mount, contentTypes) {
|
||||||
|
this._transDispatcher.addMount(mount, contentTypes);
|
||||||
|
this._residentSource.addMount(mount, contentTypes);
|
||||||
|
}));
|
||||||
|
|
||||||
|
discoverer.guessContentTypes(mount);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onMountRemoved: function(monitor, mount) {
|
||||||
|
this._transDispatcher.removeMount(mount);
|
||||||
|
this._residentSource.removeMount(mount);
|
||||||
|
},
|
||||||
|
|
||||||
|
ejectMount: function(mount) {
|
||||||
|
// TODO: we need to have a StMountOperation here to e.g. trigger
|
||||||
|
// shell dialogs when applications are blocking the mount.
|
||||||
|
if (mount.can_eject())
|
||||||
|
mount.eject_with_operation(0, null, null,
|
||||||
|
Lang.bind(this, this._onMountEject));
|
||||||
|
else
|
||||||
|
mount.unmount_with_operation(0, null, null,
|
||||||
|
Lang.bind(this, this._onMountEject));
|
||||||
|
},
|
||||||
|
|
||||||
|
_onMountEject: function(mount, res) {
|
||||||
|
try {
|
||||||
|
if (mount.can_eject())
|
||||||
|
mount.eject_with_operation_finish(res);
|
||||||
|
else
|
||||||
|
mount.unmount_with_operation_finish(res);
|
||||||
|
} catch (e) {
|
||||||
|
log('Unable to eject the mount ' + mount.get_name()
|
||||||
|
+ ': ' + e.toString());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function AutorunResidentSource() {
|
||||||
|
this._init();
|
||||||
|
}
|
||||||
|
|
||||||
|
AutorunResidentSource.prototype = {
|
||||||
|
__proto__: MessageTray.Source.prototype,
|
||||||
|
|
||||||
|
_init: function() {
|
||||||
|
MessageTray.Source.prototype._init.call(this, _('Removable Devices'));
|
||||||
|
|
||||||
|
this._mounts = [];
|
||||||
|
|
||||||
|
this._notification = new AutorunResidentNotification(this);
|
||||||
|
this._setSummaryIcon(this.createNotificationIcon(HOTPLUG_ICON_SIZE));
|
||||||
|
},
|
||||||
|
|
||||||
|
addMount: function(mount, contentTypes) {
|
||||||
|
if (ignoreAutorunForMount(mount))
|
||||||
|
return;
|
||||||
|
|
||||||
|
let filtered = this._mounts.filter(function (element) {
|
||||||
|
return (element.mount == mount);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (filtered.length != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let element = { mount: mount, contentTypes: contentTypes };
|
||||||
|
this._mounts.push(element);
|
||||||
|
this._redisplay();
|
||||||
|
},
|
||||||
|
|
||||||
|
removeMount: function(mount) {
|
||||||
|
this._mounts =
|
||||||
|
this._mounts.filter(function (element) {
|
||||||
|
return (element.mount != mount);
|
||||||
|
});
|
||||||
|
|
||||||
|
this._redisplay();
|
||||||
|
},
|
||||||
|
|
||||||
|
_redisplay: function() {
|
||||||
|
if (this._mounts.length == 0) {
|
||||||
|
this._notification.destroy();
|
||||||
|
this.destroy();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._notification.updateForMounts(this._mounts);
|
||||||
|
|
||||||
|
// add ourselves as a source, and push the notification
|
||||||
|
if (!Main.messageTray.contains(this)) {
|
||||||
|
Main.messageTray.add(this);
|
||||||
|
this.pushNotification(this._notification);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
createNotificationIcon: function(iconSize) {
|
||||||
|
return new St.Icon ({ icon_name: 'drive-harddisk',
|
||||||
|
icon_size: iconSize ? iconSize : this.ICON_SIZE });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function AutorunResidentNotification(source) {
|
||||||
|
this._init(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
AutorunResidentNotification.prototype = {
|
||||||
|
__proto__: MessageTray.Notification.prototype,
|
||||||
|
|
||||||
|
_init: function(source) {
|
||||||
|
MessageTray.Notification.prototype._init.call(this, source,
|
||||||
|
source.title, null,
|
||||||
|
{ customContent: true });
|
||||||
|
|
||||||
|
// set the notification as resident
|
||||||
|
this.setResident(true);
|
||||||
|
|
||||||
|
this._layout = new St.BoxLayout ({ style_class: 'hotplug-resident-box',
|
||||||
|
vertical: true });
|
||||||
|
|
||||||
|
this.addActor(this._layout,
|
||||||
|
{ x_expand: true,
|
||||||
|
x_fill: true });
|
||||||
|
},
|
||||||
|
|
||||||
|
updateForMounts: function(mounts) {
|
||||||
|
// remove all the layout content
|
||||||
|
this._layout.destroy_children();
|
||||||
|
|
||||||
|
for (let idx = 0; idx < mounts.length; idx++) {
|
||||||
|
let element = mounts[idx];
|
||||||
|
|
||||||
|
let actor = this._itemForMount(element.mount, element.contentTypes);
|
||||||
|
this._layout.add(actor, { x_fill: true,
|
||||||
|
expand: true });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_itemForMount: function(mount, contentTypes) {
|
||||||
|
let item = new St.BoxLayout();
|
||||||
|
|
||||||
|
// prepare the mount button content
|
||||||
|
let mountLayout = new St.BoxLayout();
|
||||||
|
|
||||||
|
let mountIcon = new St.Icon({ gicon: mount.get_icon(),
|
||||||
|
style_class: 'hotplug-resident-mount-icon' });
|
||||||
|
mountLayout.add_actor(mountIcon);
|
||||||
|
|
||||||
|
let labelBin = new St.Bin({ y_align: St.Align.MIDDLE });
|
||||||
|
let mountLabel =
|
||||||
|
new St.Label({ text: mount.get_name(),
|
||||||
|
style_class: 'hotplug-resident-mount-label',
|
||||||
|
track_hover: true,
|
||||||
|
reactive: true });
|
||||||
|
labelBin.add_actor(mountLabel);
|
||||||
|
mountLayout.add_actor(labelBin);
|
||||||
|
|
||||||
|
let mountButton = new St.Button({ child: mountLayout,
|
||||||
|
x_align: St.Align.START,
|
||||||
|
x_fill: true,
|
||||||
|
style_class: 'hotplug-resident-mount',
|
||||||
|
button_mask: St.ButtonMask.ONE });
|
||||||
|
item.add(mountButton, { x_align: St.Align.START,
|
||||||
|
expand: true });
|
||||||
|
|
||||||
|
let ejectIcon =
|
||||||
|
new St.Icon({ icon_name: 'media-eject',
|
||||||
|
style_class: 'hotplug-resident-eject-icon' });
|
||||||
|
|
||||||
|
let ejectButton =
|
||||||
|
new St.Button({ style_class: 'hotplug-resident-eject-button',
|
||||||
|
button_mask: St.ButtonMask.ONE,
|
||||||
|
child: ejectIcon });
|
||||||
|
item.add(ejectButton, { x_align: St.Align.END });
|
||||||
|
|
||||||
|
// TODO: need to do something better here...
|
||||||
|
if (!contentTypes.length)
|
||||||
|
contentTypes.push('inode/directory');
|
||||||
|
|
||||||
|
// now connect signals
|
||||||
|
mountButton.connect('clicked', Lang.bind(this, function(actor, event) {
|
||||||
|
let app = Gio.app_info_get_default_for_type(contentTypes[0], false);
|
||||||
|
|
||||||
|
if (app)
|
||||||
|
startAppForMount(app, mount);
|
||||||
|
}));
|
||||||
|
|
||||||
|
ejectButton.connect('clicked', Lang.bind(this, function() {
|
||||||
|
Main.autorunManager.ejectMount(mount);
|
||||||
|
}));
|
||||||
|
|
||||||
|
return item;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function AutorunTransientDispatcher() {
|
||||||
|
this._init();
|
||||||
|
}
|
||||||
|
|
||||||
|
AutorunTransientDispatcher.prototype = {
|
||||||
|
_init: function() {
|
||||||
|
this._sources = [];
|
||||||
|
this._settings = new Gio.Settings({ schema: SETTINGS_SCHEMA });
|
||||||
|
},
|
||||||
|
|
||||||
|
_getSourceForMount: function(mount) {
|
||||||
|
let filtered =
|
||||||
|
this._sources.filter(function (source) {
|
||||||
|
return (source.mount == mount);
|
||||||
|
});
|
||||||
|
|
||||||
|
// we always make sure not to add two sources for the same
|
||||||
|
// mount in addMount(), so it's safe to assume filtered.length
|
||||||
|
// is always either 1 or 0.
|
||||||
|
if (filtered.length == 1)
|
||||||
|
return filtered[0];
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
addMount: function(mount, contentTypes) {
|
||||||
|
// if autorun is disabled globally, return
|
||||||
|
if (this._settings.get_boolean(SETTING_DISABLE_AUTORUN))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// if the mount doesn't want to be autorun, return
|
||||||
|
if (ignoreAutorunForMount(mount))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// finally, if we already have a source showing for this
|
||||||
|
// mount, return
|
||||||
|
if (this._getSourceForMount(mount))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// add a new source
|
||||||
|
this._sources.push(new AutorunTransientSource(mount, contentTypes));
|
||||||
|
},
|
||||||
|
|
||||||
|
removeMount: function(mount) {
|
||||||
|
let source = this._getSourceForMount(mount);
|
||||||
|
|
||||||
|
// if we aren't tracking this mount, don't do anything
|
||||||
|
if (!source)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// destroy the notification source
|
||||||
|
source.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function AutorunTransientSource(mount, contentTypes) {
|
||||||
|
this._init(mount, contentTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
AutorunTransientSource.prototype = {
|
||||||
|
__proto__: MessageTray.Source.prototype,
|
||||||
|
|
||||||
|
_init: function(mount, contentTypes) {
|
||||||
|
MessageTray.Source.prototype._init.call(this, mount.get_name());
|
||||||
|
|
||||||
|
this.mount = mount;
|
||||||
|
this.contentTypes = contentTypes;
|
||||||
|
|
||||||
|
this._notification = new AutorunTransientNotification(this);
|
||||||
|
this._setSummaryIcon(this.createNotificationIcon(this.ICON_SIZE));
|
||||||
|
|
||||||
|
// add ourselves as a source, and popup the notification
|
||||||
|
Main.messageTray.add(this);
|
||||||
|
this.notify(this._notification);
|
||||||
|
},
|
||||||
|
|
||||||
|
createNotificationIcon: function(iconSize) {
|
||||||
|
return new St.Icon({ gicon: this.mount.get_icon(),
|
||||||
|
icon_size: iconSize ? iconSize : this.ICON_SIZE });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function AutorunTransientNotification(source) {
|
||||||
|
this._init(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
AutorunTransientNotification.prototype = {
|
||||||
|
__proto__: MessageTray.Notification.prototype,
|
||||||
|
|
||||||
|
_init: function(source) {
|
||||||
|
MessageTray.Notification.prototype._init.call(this, source,
|
||||||
|
source.title, null,
|
||||||
|
{ customContent: true });
|
||||||
|
|
||||||
|
this._box = new St.BoxLayout({ style_class: 'hotplug-transient-box',
|
||||||
|
vertical: true });
|
||||||
|
this.addActor(this._box);
|
||||||
|
|
||||||
|
this._mount = source.mount;
|
||||||
|
|
||||||
|
source.contentTypes.forEach(Lang.bind(this, function (type) {
|
||||||
|
let actor = this._buttonForContentType(type);
|
||||||
|
|
||||||
|
if (actor)
|
||||||
|
this._box.add(actor, { x_fill: true,
|
||||||
|
x_align: St.Align.START });
|
||||||
|
}));
|
||||||
|
|
||||||
|
// TODO: ideally we never want to show the file manager entry here,
|
||||||
|
// but we want to detect which kind of files are present on the device,
|
||||||
|
// and use those to present a more meaningful choice.
|
||||||
|
if (this._contentTypes.length == 0) {
|
||||||
|
let button = this._buttonForContentType('inode/directory');
|
||||||
|
|
||||||
|
if (button)
|
||||||
|
this._box.add (button, { x_fill: true,
|
||||||
|
x_align: St.Align.START });
|
||||||
|
}
|
||||||
|
|
||||||
|
this._box.add(this._buttonForEject(), { x_fill: true,
|
||||||
|
x_align: St.Align.START });
|
||||||
|
|
||||||
|
// set the notification to transient and urgent, so that it
|
||||||
|
// expands out
|
||||||
|
this.setTransient(true);
|
||||||
|
this.setUrgency(MessageTray.Urgency.CRITICAL);
|
||||||
|
},
|
||||||
|
|
||||||
|
_buttonForContentType: function(type) {
|
||||||
|
let app = Gio.app_info_get_default_for_type(type, false);
|
||||||
|
|
||||||
|
if (!app)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
let box = new St.BoxLayout();
|
||||||
|
|
||||||
|
let icon = new St.Icon({ gicon: app.get_icon(),
|
||||||
|
style_class: 'hotplug-notification-item-icon' });
|
||||||
|
box.add(icon);
|
||||||
|
|
||||||
|
let label = new St.Bin({ y_align: St.Align.MIDDLE,
|
||||||
|
child: new St.Label
|
||||||
|
({ text: _("Open with %s").format(app.get_display_name()) })
|
||||||
|
});
|
||||||
|
box.add(label);
|
||||||
|
|
||||||
|
let button = new St.Button({ child: box,
|
||||||
|
button_mask: St.ButtonMask.ONE,
|
||||||
|
style_class: 'hotplug-notification-item' });
|
||||||
|
|
||||||
|
button.connect('clicked', Lang.bind(this, function() {
|
||||||
|
startAppForMount(app, this._mount);
|
||||||
|
this.destroy();
|
||||||
|
}));
|
||||||
|
|
||||||
|
return button;
|
||||||
|
},
|
||||||
|
|
||||||
|
_buttonForEject: function() {
|
||||||
|
let box = new St.BoxLayout();
|
||||||
|
let icon = new St.Icon({ icon_name: 'media-eject',
|
||||||
|
style_class: 'hotplug-notification-item-icon' });
|
||||||
|
box.add(icon);
|
||||||
|
|
||||||
|
let label = new St.Bin({ y_align: St.Align.MIDDLE,
|
||||||
|
child: new St.Label
|
||||||
|
({ text: _("Eject") })
|
||||||
|
});
|
||||||
|
box.add(label);
|
||||||
|
|
||||||
|
let button = new St.Button({ child: box,
|
||||||
|
x_fill: true,
|
||||||
|
x_align: St.Align.START,
|
||||||
|
button_mask: St.ButtonMask.ONE,
|
||||||
|
style_class: 'hotplug-notification-item' });
|
||||||
|
|
||||||
|
button.connect('clicked', Lang.bind(this, function() {
|
||||||
|
Main.autorunManager.ejectMount(this._mount);
|
||||||
|
}));
|
||||||
|
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@ const Meta = imports.gi.Meta;
|
|||||||
const Shell = imports.gi.Shell;
|
const Shell = imports.gi.Shell;
|
||||||
const St = imports.gi.St;
|
const St = imports.gi.St;
|
||||||
|
|
||||||
|
const AutorunManager = imports.ui.autorunManager;
|
||||||
const Chrome = imports.ui.chrome;
|
const Chrome = imports.ui.chrome;
|
||||||
const CtrlAltTab = imports.ui.ctrlAltTab;
|
const CtrlAltTab = imports.ui.ctrlAltTab;
|
||||||
const EndSessionDialog = imports.ui.endSessionDialog;
|
const EndSessionDialog = imports.ui.endSessionDialog;
|
||||||
@ -39,6 +40,7 @@ const Util = imports.misc.util;
|
|||||||
const DEFAULT_BACKGROUND_COLOR = new Clutter.Color();
|
const DEFAULT_BACKGROUND_COLOR = new Clutter.Color();
|
||||||
DEFAULT_BACKGROUND_COLOR.from_pixel(0x2266bbff);
|
DEFAULT_BACKGROUND_COLOR.from_pixel(0x2266bbff);
|
||||||
|
|
||||||
|
let autorunManager = null;
|
||||||
let chrome = null;
|
let chrome = null;
|
||||||
let panel = null;
|
let panel = null;
|
||||||
let hotCorners = [];
|
let hotCorners = [];
|
||||||
@ -142,6 +144,7 @@ function start() {
|
|||||||
notificationDaemon = new NotificationDaemon.NotificationDaemon();
|
notificationDaemon = new NotificationDaemon.NotificationDaemon();
|
||||||
windowAttentionHandler = new WindowAttentionHandler.WindowAttentionHandler();
|
windowAttentionHandler = new WindowAttentionHandler.WindowAttentionHandler();
|
||||||
telepathyClient = new TelepathyClient.Client();
|
telepathyClient = new TelepathyClient.Client();
|
||||||
|
autorunManager = new AutorunManager.AutorunManager();
|
||||||
|
|
||||||
layoutManager.init();
|
layoutManager.init();
|
||||||
overview.init();
|
overview.init();
|
||||||
|
Loading…
Reference in New Issue
Block a user