diff --git a/docs/reference/cinnamon/meson.build b/docs/reference/cinnamon/meson.build
index 4514ff5d2c..eaa011ce79 100644
--- a/docs/reference/cinnamon/meson.build
+++ b/docs/reference/cinnamon/meson.build
@@ -1,6 +1,7 @@
ignore = [
'cinnamon-recorder-src.h',
'cinnamon-recorder.h',
+ 'cinnamon-mount-operation.h',
st_headers,
st_private_headers,
tray_headers,
diff --git a/js/ui/automountManager.js b/js/ui/automountManager.js
new file mode 100644
index 0000000000..a8b93c1e45
--- /dev/null
+++ b/js/ui/automountManager.js
@@ -0,0 +1,272 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+/* exported Component */
+
+const { Gio, GLib } = imports.gi;
+const Params = imports.misc.params;
+
+const GnomeSession = imports.misc.gnomeSession;
+const Main = imports.ui.main;
+const CinnamonMountOperation = imports.ui.cinnamonMountOperation;
+
+var GNOME_SESSION_AUTOMOUNT_INHIBIT = 16;
+
+// GSettings keys
+const SETTINGS_SCHEMA = 'org.cinnamon.desktop.media-handling';
+const SETTING_ENABLE_AUTOMOUNT = 'automount';
+
+var AUTORUN_EXPIRE_TIMEOUT_SECS = 10;
+
+var AutomountManager = class {
+ constructor() {
+ this._settings = new Gio.Settings({ schema_id: SETTINGS_SCHEMA });
+ this._activeOperations = new Map();
+
+ GnomeSession.SessionManager((proxy, error) => {
+ if (error)
+ return;
+
+ this._session = proxy;
+ this.actor.show();
+ this.updateStatus();
+
+ this._session.connectSignal(
+ "InhibitorAdded",
+ this._InhibitorsChanged.bind(this)
+ );
+
+ this._session.connectSignal(
+ "InhibitorRemoved",
+ this._InhibitorsChanged.bind(this)
+ );
+ });
+
+ this._inhibited = false;
+
+ this._volumeMonitor = Gio.VolumeMonitor.get();
+ this.enable();
+ }
+
+ enable() {
+ this._volumeMonitor.connectObject(
+ 'volume-added', this._onVolumeAdded.bind(this),
+ 'volume-removed', this._onVolumeRemoved.bind(this),
+ 'drive-connected', this._onDriveConnected.bind(this),
+ 'drive-disconnected', this._onDriveDisconnected.bind(this),
+ 'drive-eject-button', this._onDriveEjectButton.bind(this), this);
+
+ this._mountAllId = GLib.idle_add(GLib.PRIORITY_DEFAULT, this._startupMountAll.bind(this));
+ GLib.Source.set_name_by_id(this._mountAllId, '[cinnamon] this._startupMountAll');
+ }
+
+ disable() {
+ this._volumeMonitor.disconnectObject(this);
+
+ if (this._mountAllId > 0) {
+ GLib.source_remove(this._mountAllId);
+ this._mountAllId = 0;
+ }
+ }
+
+ async _InhibitorsChanged(_object, _senderName, [_inhibitor]) {
+ try {
+ const [inhibited] =
+ await this._session.IsInhibitedAsync(GNOME_SESSION_AUTOMOUNT_INHIBIT);
+ this._inhibited = inhibited;
+ } catch (e) {}
+ }
+
+ _startupMountAll() {
+ let volumes = this._volumeMonitor.get_volumes();
+ volumes.forEach(volume => {
+ this._checkAndMountVolume(volume, {
+ checkSession: false,
+ useMountOp: false,
+ allowAutorun: false,
+ });
+ });
+
+ this._mountAllId = 0;
+ return GLib.SOURCE_REMOVE;
+ }
+
+ _onDriveConnected() {
+ // if we're not in the current ConsoleKit session,
+ // or screensaver is active, don't play sounds
+ // if (!this._session.SessionIsActive)
+ // return;
+
+ let player = global.display.get_sound_player();
+ player.play_from_theme('device-added-media',
+ _("External drive connected"),
+ null);
+ }
+
+ _onDriveDisconnected() {
+ // if we're not in the current ConsoleKit session,
+ // or screensaver is active, don't play sounds
+ // if (!this._session.SessionIsActive)
+ // return;
+
+ let player = global.display.get_sound_player();
+ player.play_from_theme('device-removed-media',
+ _("External drive disconnected"),
+ null);
+ }
+
+ _onDriveEjectButton(monitor, drive) {
+ // TODO: this code path is not tested, as the GVfs volume monitor
+ // doesn't emit this signal just yet.
+ // if (!this._session.SessionIsActive)
+ // return;
+
+ // we force stop/eject in this case, so we don't have to pass a
+ // mount operation object
+ if (drive.can_stop()) {
+ drive.stop(Gio.MountUnmountFlags.FORCE, null, null,
+ (o, res) => {
+ try {
+ drive.stop_finish(res);
+ } catch (e) {
+ log(`Unable to stop the drive after drive-eject-button ${e.toString()}`);
+ }
+ });
+ } else if (drive.can_eject()) {
+ drive.eject_with_operation(Gio.MountUnmountFlags.FORCE, null, null,
+ (o, res) => {
+ try {
+ drive.eject_with_operation_finish(res);
+ } catch (e) {
+ log(`Unable to eject the drive after drive-eject-button ${e.toString()}`);
+ }
+ });
+ }
+ }
+
+ _onVolumeAdded(monitor, volume) {
+ this._checkAndMountVolume(volume);
+ }
+
+ _checkAndMountVolume(volume, params) {
+ global.log("check and mount");
+ params = Params.parse(params, {
+ checkSession: true,
+ useMountOp: true,
+ allowAutorun: true,
+ });
+
+ if (params.checkSession) {
+ // if we're not in the current ConsoleKit session,
+ // don't attempt automount
+ // if (!this._session.SessionIsActive)
+ // return;
+ }
+
+ if (this._inhibited)
+ return;
+
+ // Volume is already mounted, don't bother.
+ if (volume.get_mount())
+ return;
+
+ if (!this._settings.get_boolean(SETTING_ENABLE_AUTOMOUNT) ||
+ !volume.should_automount() ||
+ !volume.can_mount()) {
+ // allow the autorun to run anyway; this can happen if the
+ // mount gets added programmatically later, even if
+ // should_automount() or can_mount() are false, like for
+ // blank optical media.
+ this._allowAutorun(volume);
+ this._allowAutorunExpire(volume);
+
+ return;
+ }
+
+ if (params.useMountOp) {
+ let operation = new CinnamonMountOperation.CinnamonMountOperation(volume);
+ this._mountVolume(volume, operation, params.allowAutorun);
+ } else {
+ this._mountVolume(volume, null, params.allowAutorun);
+ }
+ }
+
+ _mountVolume(volume, operation, allowAutorun) {
+ if (allowAutorun)
+ this._allowAutorun(volume);
+
+ const mountOp = operation?.mountOp ?? null;
+ this._activeOperations.set(volume, operation);
+
+ volume.mount(0, mountOp, null,
+ this._onVolumeMounted.bind(this));
+ }
+
+ _onVolumeMounted(volume, res) {
+ global.log("on volume mounted");
+ this._allowAutorunExpire(volume);
+
+ try {
+ volume.mount_finish(res);
+ this._closeOperation(volume);
+ } catch (e) {
+ // FIXME: we will always get G_IO_ERROR_FAILED from the gvfs udisks
+ // backend, see https://bugs.freedesktop.org/show_bug.cgi?id=51271
+ // To reask the password if the user input was empty or wrong, we
+ // will check for corresponding error messages. However, these
+ // error strings are not unique for the cases in the comments below.
+ if (e.message.includes('No key available with this passphrase') || // cryptsetup
+ e.message.includes('No key available to unlock device') || // udisks (no password)
+ // libblockdev wrong password opening LUKS device
+ e.message.includes('Failed to activate device: Incorrect passphrase') ||
+ // cryptsetup returns EINVAL in many cases, including wrong TCRYPT password/parameters
+ e.message.includes('Failed to load device\'s parameters: Invalid argument')) {
+ this._reaskPassword(volume);
+ } else {
+ if (e.message.includes('Compiled against a version of libcryptsetup that does not support the VeraCrypt PIM setting')) {
+ Main.notifyError(_("Unable to unlock volume"),
+ _("The installed udisks version does not support the PIM setting"));
+ }
+
+ if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED_HANDLED))
+ log(`Unable to mount volume ${volume.get_name()}: ${e.toString()}`);
+ this._closeOperation(volume);
+ }
+ }
+ }
+
+ _onVolumeRemoved(monitor, volume) {
+ if (volume._allowAutorunExpireId && volume._allowAutorunExpireId > 0) {
+ GLib.source_remove(volume._allowAutorunExpireId);
+ delete volume._allowAutorunExpireId;
+ }
+ }
+
+ _reaskPassword(volume) {
+ let prevOperation = this._activeOperations.get(volume);
+ const existingDialog = prevOperation?.borrowDialog();
+ let operation =
+ new CinnamonMountOperation.CinnamonMountOperation(volume, { existingDialog });
+ this._mountVolume(volume, operation);
+ }
+
+ _closeOperation(volume) {
+ let operation = this._activeOperations.get(volume);
+ if (!operation)
+ return;
+ operation.close();
+ this._activeOperations.delete(volume);
+ }
+
+ _allowAutorun(volume) {
+ volume.allowAutorun = true;
+ }
+
+ _allowAutorunExpire(volume) {
+ let id = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, AUTORUN_EXPIRE_TIMEOUT_SECS, () => {
+ volume.allowAutorun = false;
+ delete volume._allowAutorunExpireId;
+ return GLib.SOURCE_REMOVE;
+ });
+ volume._allowAutorunExpireId = id;
+ GLib.Source.set_name_by_id(id, '[cinnamon] volume.allowAutorun');
+ }
+};
diff --git a/js/ui/autorunManager.js b/js/ui/autorunManager.js
new file mode 100644
index 0000000000..d35a212596
--- /dev/null
+++ b/js/ui/autorunManager.js
@@ -0,0 +1,313 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+/* exported Component */
+
+const { Clutter, Gio, GObject, St } = imports.gi;
+
+const GnomeSession = imports.misc.gnomeSession;
+const Main = imports.ui.main;
+const MessageTray = imports.ui.messageTray;
+
+Gio._promisify(Gio.Mount.prototype, 'guess_content_type');
+
+const hotplugSnifferIface =
+' \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+';
+
+// GSettings keys
+const SETTINGS_SCHEMA = 'org.cinnamon.desktop.media-handling';
+const SETTING_DISABLE_AUTORUN = 'autorun-never';
+const SETTING_START_APP = 'autorun-x-content-start-app';
+const SETTING_IGNORE = 'autorun-x-content-ignore';
+const SETTING_OPEN_FOLDER = 'autorun-x-content-open-folder';
+
+var AutorunSetting = {
+ RUN: 0,
+ IGNORE: 1,
+ FILES: 2,
+ ASK: 3,
+};
+
+// misc utils
+function shouldAutorunMount(mount) {
+ let root = mount.get_root();
+ let volume = mount.get_volume();
+
+ if (!volume || !volume.allowAutorun)
+ return false;
+
+ if (root.is_native() && isMountRootHidden(root))
+ return false;
+
+ return true;
+}
+
+function isMountRootHidden(root) {
+ let path = root.get_path();
+
+ // skip any mounts in hidden directory hierarchies
+ return path.includes('/.');
+}
+
+function isMountNonLocal(mount) {
+ // If the mount doesn't have an associated volume, that means it's
+ // an uninteresting filesystem. Most devices that we care about will
+ // have a mount, like media players and USB sticks.
+ let volume = mount.get_volume();
+ if (volume == null)
+ return true;
+
+ return volume.get_identifier("class") == "network";
+}
+
+function startAppForMount(app, mount) {
+ let files = [];
+ let root = mount.get_root();
+ let retval = false;
+
+ files.push(root);
+
+ try {
+ retval = app.launch(files, global.create_app_launch_context());
+ } catch (e) {
+ log(`Unable to launch the app ${app.get_name()}: ${e}`);
+ }
+
+ return retval;
+}
+
+const HotplugSnifferProxy = Gio.DBusProxy.makeProxyWrapper(hotplugSnifferIface);
+function HotplugSniffer() {
+ return new HotplugSnifferProxy(Gio.DBus.session,
+ 'org.Cinnamon.HotplugSniffer',
+ '/org/Cinnamon/HotplugSniffer');
+}
+
+var ContentTypeDiscoverer = class {
+ constructor() {
+ this._settings = new Gio.Settings({ schema_id: SETTINGS_SCHEMA });
+ }
+
+ async guessContentTypes(mount) {
+ let autorunEnabled = !this._settings.get_boolean(SETTING_DISABLE_AUTORUN);
+ let shouldScan = autorunEnabled && !isMountNonLocal(mount);
+
+ let contentTypes = [];
+ if (shouldScan) {
+ try {
+ contentTypes = await mount.guess_content_type(false, null);
+ } catch (e) {
+ log(`Unable to guess content types on added mount ${mount.get_name()}: ${e}`);
+ }
+
+ if (contentTypes.length === 0) {
+ const root = mount.get_root();
+ const hotplugSniffer = new HotplugSniffer();
+ [contentTypes] = await hotplugSniffer.SniffURIAsync(root.get_uri());
+ }
+ }
+
+ // we're not interested in win32 software content types here
+ contentTypes = contentTypes.filter(
+ type => type !== 'x-content/win32-software');
+
+ const apps = [];
+ contentTypes.forEach(type => {
+ const app = Gio.app_info_get_default_for_type(type, false);
+
+ if (app)
+ apps.push(app);
+ });
+
+ if (apps.length === 0)
+ apps.push(Gio.app_info_get_default_for_type('inode/directory', false));
+
+ return [apps, contentTypes];
+ }
+};
+
+var AutorunManager = class {
+ constructor() {
+ // this._session = new GnomeSession.SessionManager();
+ this._volumeMonitor = Gio.VolumeMonitor.get();
+
+ this._dispatcher = new AutorunDispatcher(this);
+ this.enable();
+ }
+
+ enable() {
+ this._volumeMonitor.connectObject(
+ 'mount-added', this._onMountAdded.bind(this),
+ 'mount-removed', this._onMountRemoved.bind(this), this);
+ }
+
+ disable() {
+ this._volumeMonitor.disconnectObject(this);
+ }
+
+ async _onMountAdded(monitor, mount) {
+ // don't do anything if our session is not the currently
+ // active one
+ // if (!this._session.SessionIsActive)
+ // return;
+
+ const discoverer = new ContentTypeDiscoverer();
+ const [apps, contentTypes] = await discoverer.guessContentTypes(mount);
+ this._dispatcher.addMount(mount, apps, contentTypes);
+ }
+
+ _onMountRemoved(monitor, mount) {
+ this._dispatcher.removeMount(mount);
+ }
+};
+
+var AutorunDispatcher = class {
+ constructor(manager) {
+ this._manager = manager;
+ this._sources = [];
+ this._settings = new Gio.Settings({ schema_id: SETTINGS_SCHEMA });
+ }
+
+ _getAutorunSettingForType(contentType) {
+ let runApp = this._settings.get_strv(SETTING_START_APP);
+ if (runApp.includes(contentType))
+ return AutorunSetting.RUN;
+
+ let ignore = this._settings.get_strv(SETTING_IGNORE);
+ if (ignore.includes(contentType))
+ return AutorunSetting.IGNORE;
+
+ let openFiles = this._settings.get_strv(SETTING_OPEN_FOLDER);
+ if (openFiles.includes(contentType))
+ return AutorunSetting.FILES;
+
+ return AutorunSetting.ASK;
+ }
+
+ _getSourceForMount(mount) {
+ let filtered = this._sources.filter(source => 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;
+ }
+
+ _addSource(mount, apps) {
+ // if we already have a source showing for this
+ // mount, return
+ if (this._getSourceForMount(mount))
+ return;
+
+ // add a new source
+ this._sources.push(new AutorunSource(this._manager, mount, apps));
+ }
+
+ addMount(mount, apps, 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 (!shouldAutorunMount(mount))
+ return;
+
+ let setting;
+ if (contentTypes.length > 0)
+ setting = this._getAutorunSettingForType(contentTypes[0]);
+ else
+ setting = AutorunSetting.ASK;
+
+ // check at the settings for the first content type
+ // to see whether we should ask
+ if (setting == AutorunSetting.IGNORE)
+ return; // return right away
+
+ let success = false;
+ let app = null;
+
+ if (setting == AutorunSetting.RUN)
+ app = Gio.app_info_get_default_for_type(contentTypes[0], false);
+ else if (setting == AutorunSetting.FILES)
+ app = Gio.app_info_get_default_for_type('inode/directory', false);
+
+ if (app)
+ success = startAppForMount(app, mount);
+
+ // we fallback here also in case the settings did not specify 'ask',
+ // but we failed launching the default app or the default file manager
+ if (!success)
+ this._addSource(mount, apps);
+ }
+
+ removeMount(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();
+ }
+};
+
+var AutorunSource = class extends MessageTray.Source {
+ constructor(manager, mount, apps) {
+ super(mount.get_name());
+
+ this._manager = manager;
+ this.mount = mount;
+ this.apps = apps;
+
+ this._notification = new AutorunNotification(this._manager, this);
+
+ // add ourselves as a source, and popup the notification
+ Main.messageTray.add(this);
+ this.notify(this._notification);
+ }
+
+ createNotificationIcon () {
+ return new St.Icon({
+ gicon: this.mount.get_symbolic_icon(),
+ icon_type: St.IconType.SYMBOLIC,
+ icon_size: this.ICON_SIZE
+ });
+ }
+};
+
+var AutorunNotification = class extends MessageTray.Notification {
+ constructor(manager, source) {
+ super(source, source.title);
+
+ this._manager = manager;
+ this._mount = source.mount;
+
+ this.source.apps.forEach(app => {
+ this.addButton(app.get_id(), _("Open with %s").format(app.get_name()));
+ });
+
+ this.connect("action-invoked", (notification, id) => {
+ let app = this.source.apps.find(a => a.get_id() == id);
+ if (app)
+ startAppForMount(app, this._mount);
+ });
+ }
+
+ activate() {
+ super.activate();
+
+ let app = Gio.app_info_get_default_for_type('inode/directory', false);
+ startAppForMount(app, this._mount);
+ }
+};
diff --git a/js/ui/cinnamonMountOperation.js b/js/ui/cinnamonMountOperation.js
new file mode 100644
index 0000000000..98c01504e3
--- /dev/null
+++ b/js/ui/cinnamonMountOperation.js
@@ -0,0 +1,795 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+/* exported CinnamonMountOperation, CinnamonMountOpHandler */
+
+const { Clutter, Gio, GLib, GObject, Pango, Cinnamon, St } = imports.gi;
+
+// const Animation = imports.ui.animation;
+const CheckBox = imports.ui.checkBox;
+const Dialog = imports.ui.dialog;
+const Main = imports.ui.main;
+const MessageTray = imports.ui.messageTray;
+const ModalDialog = imports.ui.modalDialog;
+const Params = imports.misc.params;
+const CinnamonEntry = imports.ui.cinnamonEntry;
+
+const Util = imports.misc.util;
+
+var LIST_ITEM_ICON_SIZE = 48;
+var WORK_SPINNER_ICON_SIZE = 16;
+
+const REMEMBER_MOUNT_PASSWORD_KEY = 'remember-mount-password';
+
+const MountOperationHandlerIface =
+' \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+';
+
+/* ------ Common Utils ------- */
+function _setButtonsForChoices(dialog, oldChoices, choices) {
+ let buttons = [];
+ let buttonsChanged = oldChoices.length !== choices.length;
+
+ for (let idx = 0; idx < choices.length; idx++) {
+ let button = idx;
+
+ buttonsChanged ||= oldChoices[idx] !== choices[idx];
+
+ buttons.unshift({
+ label: choices[idx],
+ action: () => dialog.emit('response', button),
+ });
+ }
+
+ if (buttonsChanged)
+ dialog.setButtons(buttons);
+}
+
+function _setLabelsForMessage(content, message) {
+ let labels = message.split('\n');
+
+ content.title = labels.shift();
+ content.description = labels.join('\n');
+}
+
+/* -------------------------------------------------------- */
+
+var CinnamonMountOperation = class {
+ constructor(source, params) {
+ params = Params.parse(params, { existingDialog: null });
+
+ this._dialog = null;
+ this._existingDialog = params.existingDialog;
+ this._processesDialog = null;
+
+ this.mountOp = new Cinnamon.MountOperation();
+
+ this.mountOp.connect('ask-question',
+ this._onAskQuestion.bind(this));
+ this.mountOp.connect('ask-password',
+ this._onAskPassword.bind(this));
+ this.mountOp.connect('show-processes-2',
+ this._onShowProcesses2.bind(this));
+ this.mountOp.connect('aborted',
+ this.close.bind(this));
+ this.mountOp.connect('show-unmount-progress',
+ this._onShowUnmountProgress.bind(this));
+ }
+
+ _closeExistingDialog() {
+ if (!this._existingDialog)
+ return;
+
+ this._existingDialog.close();
+ this._existingDialog = null;
+ }
+
+ _onAskQuestion(op, message, choices) {
+ global.log("askQuestion", message, choices);
+ this._closeExistingDialog();
+ this._dialog = new CinnamonMountQuestionDialog();
+
+ this._dialog.connectObject('response',
+ (object, choice) => {
+ this.mountOp.set_choice(choice);
+ this.mountOp.reply(Gio.MountOperationResult.HANDLED);
+
+ this.close();
+ }, this);
+
+ this._dialog.update(message, choices);
+ this._dialog.open();
+ }
+
+ _onAskPassword(op, message, defaultUser, defaultDomain, flags) {
+ global.log("askPassword", message, defaultUser, defaultDomain, flags);
+ if (this._existingDialog) {
+ this._dialog = this._existingDialog;
+ this._dialog.reaskPassword();
+ } else {
+ this._dialog = new CinnamonMountPasswordDialog(message, flags);
+ }
+
+ this._dialog.connectObject('response',
+ (object, choice, password, remember, hiddenVolume, systemVolume, pim) => {
+ if (choice == -1) {
+ this.mountOp.reply(Gio.MountOperationResult.ABORTED);
+ } else {
+ if (remember)
+ this.mountOp.set_password_save(Gio.PasswordSave.PERMANENTLY);
+ else
+ this.mountOp.set_password_save(Gio.PasswordSave.NEVER);
+
+ this.mountOp.set_password(password);
+ this.mountOp.set_is_tcrypt_hidden_volume(hiddenVolume);
+ this.mountOp.set_is_tcrypt_system_volume(systemVolume);
+ this.mountOp.set_pim(pim);
+ this.mountOp.reply(Gio.MountOperationResult.HANDLED);
+ }
+ }, this);
+ this._dialog.open();
+ }
+
+ close(_op) {
+ this._closeExistingDialog();
+ this._processesDialog = null;
+
+ if (this._dialog) {
+ this._dialog.close();
+ this._dialog = null;
+ }
+
+ if (this._notifier) {
+ this._notifier.done();
+ this._notifier = null;
+ }
+ }
+
+ _onShowProcesses2(op) {
+ this._closeExistingDialog();
+ global.log("showProcesses");
+ let processes = op.get_show_processes_pids();
+ let choices = op.get_show_processes_choices();
+ let message = op.get_show_processes_message();
+
+ if (!this._processesDialog) {
+ this._processesDialog = new CinnamonProcessesDialog();
+ this._dialog = this._processesDialog;
+
+ this._processesDialog.connectObject('response',
+ (object, choice) => {
+ if (choice == -1) {
+ this.mountOp.reply(Gio.MountOperationResult.ABORTED);
+ } else {
+ this.mountOp.set_choice(choice);
+ this.mountOp.reply(Gio.MountOperationResult.HANDLED);
+ }
+
+ this.close();
+ }, this);
+ this._processesDialog.open();
+ }
+
+ this._processesDialog.update(message, processes, choices);
+ }
+
+ _onShowUnmountProgress(op, message, timeLeft, bytesLeft) {
+ global.log("show unmount prog", message, timeLeft, bytesLeft);
+ if (!this._notifier)
+ this._notifier = new CinnamonUnmountNotifier();
+
+ if (bytesLeft == 0)
+ this._notifier.done(message);
+ else
+ this._notifier.show(message);
+ }
+
+ borrowDialog() {
+ this._dialog?.disconnectObject(this);
+ return this._dialog;
+ }
+};
+
+var CinnamonUnmountNotifier = class extends MessageTray.Source {
+ constructor() {
+ super('unmount-notifier');
+
+ this._notification = null;
+ Main.messageTray.add(this);
+ }
+
+ show(message) {
+ let [header, text] = message.split('\n', 2);
+
+ if (!this._notification) {
+ this._notification = new MessageTray.Notification(this, header, text);
+ this._notification.setTransient(true);
+ this._notification.setUrgency(MessageTray.Urgency.CRITICAL);
+ } else {
+ this._notification.update(header, text);
+ }
+
+ this.notify(this._notification);
+ }
+
+ done(message) {
+ if (this._notification) {
+ this._notification.destroy();
+ this._notification = null;
+ }
+
+ if (message) {
+ let [header, text] = message.split('\n', 2);
+ let notification = new MessageTray.Notification(this, header, text);
+ notification.setTransient(true);
+
+ this.notify(notification);
+ }
+ }
+
+ createNotificationIcon () {
+ return new St.Icon({
+ icon_name: 'xapp-media-removable',
+ icon_type: St.IconType.SYMBOLIC,
+ icon_size: this.ICON_SIZE
+ });
+ }
+};
+
+var CinnamonMountQuestionDialog = GObject.registerClass({
+ Signals: { 'response': { param_types: [GObject.TYPE_INT] } },
+}, class CinnamonMountQuestionDialog extends ModalDialog.ModalDialog {
+ _init() {
+ super._init({ styleClass: 'mount-question-dialog' });
+
+ this._oldChoices = [];
+
+ this._content = new Dialog.MessageDialogContent();
+ this.contentLayout.add_child(this._content);
+ }
+
+ vfunc_key_release_event(event) {
+ if (event.keyval === Clutter.KEY_Escape) {
+ this.emit('response', -1);
+ return Clutter.EVENT_STOP;
+ }
+
+ return Clutter.EVENT_PROPAGATE;
+ }
+
+ update(message, choices) {
+ _setLabelsForMessage(this._content, message);
+ _setButtonsForChoices(this, this._oldChoices, choices);
+ this._oldChoices = choices;
+ }
+});
+
+var CinnamonMountPasswordDialog = GObject.registerClass({
+ Signals: {
+ 'response': {
+ param_types: [
+ GObject.TYPE_INT,
+ GObject.TYPE_STRING,
+ GObject.TYPE_BOOLEAN,
+ GObject.TYPE_BOOLEAN,
+ GObject.TYPE_BOOLEAN,
+ GObject.TYPE_UINT,
+ ],
+ },
+ },
+}, class CinnamonMountPasswordDialog extends ModalDialog.ModalDialog {
+ _init(message, flags) {
+ let strings = message.split('\n');
+ let title = strings.shift() || null;
+ let description = strings.shift() || null;
+ super._init({ styleClass: 'prompt-dialog' });
+
+ let disksApp = Cinnamon.AppSystem.get_default().lookup_app('org.gnome.DiskUtility.desktop');
+
+ let content = new Dialog.MessageDialogContent({ title, description });
+
+ let passwordGridLayout = new Clutter.GridLayout({ orientation: Clutter.Orientation.VERTICAL });
+ let passwordGrid = new St.Widget({
+ style_class: 'prompt-dialog-password-grid',
+ layout_manager: passwordGridLayout,
+ });
+ passwordGridLayout.hookup_style(passwordGrid);
+
+ let rtl = passwordGrid.get_text_direction() === Clutter.TextDirection.RTL;
+ let curGridRow = 0;
+
+ if (flags & Gio.AskPasswordFlags.TCRYPT) {
+ this._hiddenVolume = new CheckBox.CheckBox(_("Hidden Volume"));
+ content.add_child(this._hiddenVolume);
+
+ this._systemVolume = new CheckBox.CheckBox(_("Windows System Volume"));
+ content.add_child(this._systemVolume);
+
+ this._keyfilesCheckbox = new CheckBox.CheckBox(_("Uses Keyfiles"));
+ this._keyfilesCheckbox.connect("clicked", this._onKeyfilesCheckboxClicked.bind(this));
+ content.add_child(this._keyfilesCheckbox);
+
+ this._keyfilesLabel = new St.Label({ visible: false });
+ if (disksApp) {
+ this._keyfilesLabel.clutter_text.set_markup(
+ /* Translators: %s is the Disks application */
+ _('To unlock a volume that uses keyfiles, use the %s utility instead.')
+ .format(disksApp.get_name()));
+ } else {
+ this._keyfilesLabel.clutter_text.set_markup(
+ _('You need an external utility like Disks to unlock a volume that uses keyfiles.'));
+ }
+ this._keyfilesLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
+ this._keyfilesLabel.clutter_text.line_wrap = true;
+ content.add_child(this._keyfilesLabel);
+
+ this._pimEntry = new St.PasswordEntry({
+ style_class: 'prompt-dialog-password-entry',
+ hint_text: _('PIM Number'),
+ can_focus: true,
+ x_expand: true,
+ });
+ this._pimEntry.clutter_text.connect('activate', this._onEntryActivate.bind(this));
+ CinnamonEntry.addContextMenu(this._pimEntry);
+
+ if (rtl)
+ passwordGridLayout.attach(this._pimEntry, 1, curGridRow, 1, 1);
+ else
+ passwordGridLayout.attach(this._pimEntry, 0, curGridRow, 1, 1);
+ curGridRow += 1;
+ } else {
+ this._hiddenVolume = null;
+ this._systemVolume = null;
+ this._pimEntry = null;
+ }
+
+ this._passwordEntry = new St.PasswordEntry({
+ style_class: 'prompt-dialog-password-entry',
+ hint_text: _('Password'),
+ can_focus: true,
+ x_expand: true,
+ });
+ this._passwordEntry.clutter_text.connect('activate', this._onEntryActivate.bind(this));
+ this.setInitialKeyFocus(this._passwordEntry);
+ CinnamonEntry.addContextMenu(this._passwordEntry);
+
+ // this._workSpinner = new Animation.Spinner(WORK_SPINNER_ICON_SIZE, {
+ // animate: true,
+ // });
+
+ if (rtl) {
+ // passwordGridLayout.attach(this._workSpinner, 0, curGridRow, 1, 1);
+ passwordGridLayout.attach(this._passwordEntry, 1, curGridRow, 1, 1);
+ } else {
+ passwordGridLayout.attach(this._passwordEntry, 0, curGridRow, 1, 1);
+ // passwordGridLayout.attach(this._workSpinner, 1, curGridRow, 1, 1);
+ }
+ curGridRow += 1;
+
+ let warningBox = new St.BoxLayout({ vertical: true });
+
+ let capsLockWarning = new CinnamonEntry.CapsLockWarning();
+ warningBox.add_child(capsLockWarning);
+
+ this._errorMessageLabel = new St.Label({
+ style_class: 'prompt-dialog-error-label',
+ opacity: 0,
+ });
+ this._errorMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
+ this._errorMessageLabel.clutter_text.line_wrap = true;
+ warningBox.add_child(this._errorMessageLabel);
+
+ passwordGridLayout.attach(warningBox, 0, curGridRow, 2, 1);
+
+ content.add_child(passwordGrid);
+
+ // if (flags & Gio.AskPasswordFlags.SAVING_SUPPORTED) {
+ // this._rememberChoice = new CheckBox.CheckBox(_("Remember Password"));
+ // // this._rememberChoice.checked =
+ // // global.settings.get_boolean(REMEMBER_MOUNT_PASSWORD_KEY);
+ // content.add_child(this._rememberChoice);
+ // } else {
+ // this._rememberChoice = null;
+ // }
+
+ this.contentLayout.add_child(content);
+
+ this._defaultButtons = [{
+ label: _("Cancel"),
+ action: this._onCancelButton.bind(this),
+ key: Clutter.KEY_Escape,
+ }, {
+ label: _("Unlock"),
+ action: this._onUnlockButton.bind(this),
+ default: true,
+ }];
+
+ this._usesKeyfilesButtons = [{
+ label: _("Cancel"),
+ action: this._onCancelButton.bind(this),
+ key: Clutter.KEY_Escape,
+ }];
+
+ if (disksApp) {
+ this._usesKeyfilesButtons.push({
+ /* Translators: %s is the Disks application */
+ label: _('Open %s').format(disksApp.get_name()),
+ action: () => {
+ disksApp.activate();
+ this._onCancelButton();
+ },
+ default: true,
+ });
+ }
+
+ this.setButtons(this._defaultButtons);
+ }
+
+ reaskPassword() {
+ // this._workSpinner.stop();
+ this._passwordEntry.set_text('');
+ this._errorMessageLabel.text = _('Sorry, that didn’t work. Please try again.');
+ this._errorMessageLabel.opacity = 255;
+
+ Util.wiggle(this._passwordEntry);
+ }
+
+ _onCancelButton() {
+ this.emit('response', -1, '', false, false, false, 0);
+ }
+
+ _onUnlockButton() {
+ this._onEntryActivate();
+ }
+
+ _onEntryActivate() {
+ let pim = 0;
+ if (this._pimEntry !== null) {
+ pim = this._pimEntry.get_text();
+
+ if (isNaN(pim)) {
+ this._pimEntry.set_text('');
+ this._errorMessageLabel.text = _('The PIM must be a number or empty.');
+ this._errorMessageLabel.opacity = 255;
+ return;
+ }
+
+ this._errorMessageLabel.opacity = 0;
+ }
+
+ // global.settings.set_boolean(REMEMBER_MOUNT_PASSWORD_KEY,
+ // this._rememberChoice && this._rememberChoice.checked);
+
+ // this._workSpinner.play();
+ this.emit('response', 1,
+ this._passwordEntry.get_text(),
+ this._rememberChoice &&
+ this._rememberChoice.checked,
+ this._hiddenVolume &&
+ this._hiddenVolume.checked,
+ this._systemVolume &&
+ this._systemVolume.checked,
+ parseInt(pim));
+ }
+
+ _onKeyfilesCheckboxClicked() {
+ let useKeyfiles = this._keyfilesCheckbox.checked;
+ this._passwordEntry.reactive = !useKeyfiles;
+ this._passwordEntry.can_focus = !useKeyfiles;
+ this._pimEntry.reactive = !useKeyfiles;
+ this._pimEntry.can_focus = !useKeyfiles;
+ this._rememberChoice.reactive = !useKeyfiles;
+ this._rememberChoice.can_focus = !useKeyfiles;
+ this._keyfilesLabel.visible = useKeyfiles;
+ this.setButtons(useKeyfiles ? this._usesKeyfilesButtons : this._defaultButtons);
+ }
+});
+
+var CinnamonProcessesDialog = GObject.registerClass({
+ Signals: { 'response': { param_types: [GObject.TYPE_INT] } },
+}, class CinnamonProcessesDialog extends ModalDialog.ModalDialog {
+ _init() {
+ super._init({ styleClass: 'processes-dialog' });
+
+ this._oldChoices = [];
+
+ this._content = new Dialog.MessageDialogContent();
+ this.contentLayout.add_child(this._content);
+
+ this._applicationSection = new Dialog.ListSection();
+ this._applicationSection.hide();
+ this.contentLayout.add_child(this._applicationSection);
+ }
+
+ vfunc_key_release_event(event) {
+ if (event.keyval === Clutter.KEY_Escape) {
+ this.emit('response', -1);
+ return Clutter.EVENT_STOP;
+ }
+
+ return Clutter.EVENT_PROPAGATE;
+ }
+
+ _setAppsForPids(pids) {
+ // remove all the items
+ this._applicationSection.list.destroy_all_children();
+ global.log(pids);
+ pids.forEach(pid => {
+ let tracker = Cinnamon.WindowTracker.get_default();
+ let app = tracker.get_app_from_pid(pid);
+
+ if (!app)
+ return;
+
+ let listItem = new Dialog.ListSectionItem({
+ icon_actor: app.create_icon_texture(LIST_ITEM_ICON_SIZE),
+ title: app.get_name(),
+ });
+ this._applicationSection.list.add_child(listItem);
+ });
+
+ this._applicationSection.visible =
+ this._applicationSection.list.get_n_children() > 0;
+ }
+
+ update(message, processes, choices) {
+ this._setAppsForPids(processes);
+ _setLabelsForMessage(this._content, message);
+ _setButtonsForChoices(this, this._oldChoices, choices);
+ this._oldChoices = choices;
+ }
+});
+
+var CinnamonMountOperationType = {
+ NONE: 0,
+ ASK_PASSWORD: 1,
+ ASK_QUESTION: 2,
+ SHOW_PROCESSES: 3,
+};
+
+var CinnamonMountOpHandler = class {
+ constructor() {
+ this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(MountOperationHandlerIface, this);
+ this._dbusImpl.export(Gio.DBus.session, '/org/gtk/MountOperationHandler');
+ Gio.bus_own_name_on_connection(Gio.DBus.session, 'org.gtk.MountOperationHandler',
+ Gio.BusNameOwnerFlags.REPLACE, null, null);
+
+ this._dialog = null;
+
+ this._ensureEmptyRequest();
+ }
+
+ _ensureEmptyRequest() {
+ this._currentId = null;
+ this._currentInvocation = null;
+ this._currentType = CinnamonMountOperationType.NONE;
+ }
+
+ _clearCurrentRequest(response, details) {
+ if (this._currentInvocation) {
+ this._currentInvocation.return_value(
+ GLib.Variant.new('(ua{sv})', [response, details]));
+ }
+
+ this._ensureEmptyRequest();
+ }
+
+ _setCurrentRequest(invocation, id, type) {
+ let oldId = this._currentId;
+ let oldType = this._currentType;
+ let requestId = `${id}@${invocation.get_sender()}`;
+
+ this._clearCurrentRequest(Gio.MountOperationResult.UNHANDLED, {});
+
+ this._currentInvocation = invocation;
+ this._currentId = requestId;
+ this._currentType = type;
+
+ if (this._dialog && (oldId == requestId) && (oldType == type))
+ return true;
+
+ return false;
+ }
+
+ _closeDialog() {
+ if (this._dialog) {
+ this._dialog.close();
+ this._dialog = null;
+ }
+ }
+
+ /**
+ * AskPassword:
+ * @param {Array} params
+ * {string} id: an opaque ID identifying the object for which
+ * the operation is requested
+ * {string} message: the message to display
+ * {string} icon_name: the name of an icon to display
+ * {string} default_user: the default username for display
+ * {string} default_domain: the default domain for display
+ * {Gio.AskPasswordFlags} flags: a set of GAskPasswordFlags
+ * {Gio.MountOperationResults} response: a GMountOperationResult
+ * {Object} response_details: a dictionary containing response details as
+ * entered by the user. The dictionary MAY contain the following
+ * properties:
+ * - "password" -> (s): a password to be used to complete the mount operation
+ * - "password_save" -> (u): a GPasswordSave
+ * @param {Gio.DBusMethodInvocation} invocation
+ * The ID must be unique in the context of the calling process.
+ *
+ * The dialog will stay visible until clients call the Close() method, or
+ * another dialog becomes visible.
+ * Calling AskPassword again for the same id will have the effect to clear
+ * the existing dialog and update it with a message indicating the previous
+ * attempt went wrong.
+ */
+ AskPasswordAsync(params, invocation) {
+ let [id, message, iconName_, defaultUser_, defaultDomain_, flags] = params;
+ global.log("ask pass", message);
+ if (this._setCurrentRequest(invocation, id, CinnamonMountOperationType.ASK_PASSWORD)) {
+ this._dialog.reaskPassword();
+ return;
+ }
+
+ this._closeDialog();
+
+ this._dialog = new CinnamonMountPasswordDialog(message, flags);
+ this._dialog.connect('response',
+ (object, choice, password, remember, hiddenVolume, systemVolume, pim) => {
+ let details = {};
+ let response;
+
+ if (choice == -1) {
+ response = Gio.MountOperationResult.ABORTED;
+ } else {
+ response = Gio.MountOperationResult.HANDLED;
+
+ let passSave = remember ? Gio.PasswordSave.PERMANENTLY : Gio.PasswordSave.NEVER;
+ details['password_save'] = GLib.Variant.new('u', passSave);
+ details['password'] = GLib.Variant.new('s', password);
+ details['hidden_volume'] = GLib.Variant.new('b', hiddenVolume);
+ details['system_volume'] = GLib.Variant.new('b', systemVolume);
+ details['pim'] = GLib.Variant.new('u', pim);
+ }
+
+ this._clearCurrentRequest(response, details);
+ });
+ this._dialog.open();
+ }
+
+ /**
+ * AskQuestion:
+ * @param {Array} params - params
+ * {string} id: an opaque ID identifying the object for which
+ * the operation is requested
+ * The ID must be unique in the context of the calling process.
+ * {string} message: the message to display
+ * {string} icon_name: the name of an icon to display
+ * {string[]} choices: an array of choice strings
+ * @param {Gio.DBusMethodInvocation} invocation - invocation
+ *
+ * The dialog will stay visible until clients call the Close() method, or
+ * another dialog becomes visible.
+ * Calling AskQuestion again for the same id will have the effect to clear
+ * update the dialog with the new question.
+ */
+ AskQuestionAsync(params, invocation) {
+ let [id, message, iconName_, choices] = params;
+ global.log("ask question", message);
+
+ if (this._setCurrentRequest(invocation, id, CinnamonMountOperationType.ASK_QUESTION)) {
+ this._dialog.update(message, choices);
+ return;
+ }
+
+ this._closeDialog();
+
+ this._dialog = new CinnamonMountQuestionDialog(message);
+ this._dialog.connect('response', (object, choice) => {
+ let response;
+ let details = {};
+
+ if (choice == -1) {
+ response = Gio.MountOperationResult.ABORTED;
+ } else {
+ response = Gio.MountOperationResult.HANDLED;
+ details['choice'] = GLib.Variant.new('i', choice);
+ }
+
+ this._clearCurrentRequest(response, details);
+ });
+
+ this._dialog.update(message, choices);
+ this._dialog.open();
+ }
+
+ /**
+ * ShowProcesses:
+ * @param {Array} params - params
+ * {string} id: an opaque ID identifying the object for which
+ * the operation is requested
+ * The ID must be unique in the context of the calling process.
+ * {string} message: the message to display
+ * {string} icon_name: the name of an icon to display
+ * {number[]} application_pids: the PIDs of the applications to display
+ * {string[]} choices: an array of choice strings
+ * @param {Gio.DBusMethodInvocation} invocation - invocation
+ *
+ * The dialog will stay visible until clients call the Close() method, or
+ * another dialog becomes visible.
+ * Calling ShowProcesses again for the same id will have the effect to clear
+ * the existing dialog and update it with the new message and the new list
+ * of processes.
+ */
+ ShowProcessesAsync(params, invocation) {
+ let [id, message, iconName_, applicationPids, choices] = params;
+
+ if (this._setCurrentRequest(invocation, id, CinnamonMountOperationType.SHOW_PROCESSES)) {
+ this._dialog.update(message, applicationPids, choices);
+ return;
+ }
+
+ this._closeDialog();
+ global.log("show processes ");
+ this._dialog = new CinnamonProcessesDialog();
+ this._dialog.connect('response', (object, choice) => {
+ let response;
+ let details = {};
+
+ if (choice == -1) {
+ response = Gio.MountOperationResult.ABORTED;
+ } else {
+ response = Gio.MountOperationResult.HANDLED;
+ details['choice'] = GLib.Variant.new('i', choice);
+ }
+
+ this._clearCurrentRequest(response, details);
+ });
+
+ this._dialog.update(message, applicationPids, choices);
+ this._dialog.open();
+ }
+
+ /**
+ * Close:
+ * @param {Array} _params - params
+ * @param {Gio.DBusMethodInvocation} _invocation - invocation
+ *
+ * Closes a dialog previously opened by AskPassword, AskQuestion or ShowProcesses.
+ * If no dialog is open, does nothing.
+ */
+ Close(_params, _invocation) {
+ this._clearCurrentRequest(Gio.MountOperationResult.UNHANDLED, {});
+ this._closeDialog();
+ }
+};
diff --git a/js/ui/main.js b/js/ui/main.js
index 18f74fa092..1e7cd8e738 100644
--- a/js/ui/main.js
+++ b/js/ui/main.js
@@ -91,6 +91,8 @@ const GObject = imports.gi.GObject;
const XApp = imports.gi.XApp;
const PointerTracker = imports.misc.pointerTracker;
+const AutomountManager = imports.ui.automountManager;
+const AutorunManager = imports.ui.autorunManager;
const AudioDeviceSelection = imports.ui.audioDeviceSelection;
const SoundManager = imports.ui.soundManager;
const BackgroundManager = imports.ui.backgroundManager;
@@ -116,6 +118,7 @@ const NetworkAgent = imports.ui.networkAgent;
const NotificationDaemon = imports.ui.notificationDaemon;
const WindowAttentionHandler = imports.ui.windowAttentionHandler;
const CinnamonDBus = imports.ui.cinnamonDBus;
+const CinnamonMountOperation = imports.ui.cinnamonMountOperation;
const Screenshot = imports.ui.screenshot;
const ThemeManager = imports.ui.themeManager;
const Magnifier = imports.ui.magnifier;
@@ -162,6 +165,9 @@ var windowAttentionHandler = null;
var screenRecorder = null;
var cinnamonAudioSelectionDBusService = null;
var cinnamonDBusService = null;
+var cinnamonMountOpDBusService = null;
+var automountManager = null;
+var autorunManager = null;
var screenshotService = null;
var modalCount = 0;
var modalActorFocusStack = [];
@@ -331,6 +337,10 @@ function start() {
new CinnamonPortalHandler();
cinnamonAudioSelectionDBusService = new AudioDeviceSelection.AudioDeviceSelectionDBus();
cinnamonDBusService = new CinnamonDBus.CinnamonDBus();
+ cinnamonMountOpDBusService = new CinnamonMountOperation.CinnamonMountOpHandler();
+ automountManager = new AutomountManager.AutomountManager();
+ autorunManager = new AutorunManager.AutorunManager();
+
setRunState(RunState.STARTUP);
screenshotService = new Screenshot.ScreenshotService();
diff --git a/js/ui/placesManager.js b/js/ui/placesManager.js
index f96a95c648..f8740dc48d 100644
--- a/js/ui/placesManager.js
+++ b/js/ui/placesManager.js
@@ -8,6 +8,7 @@ const Mainloop = imports.mainloop;
const Signals = imports.signals;
const St = imports.gi.St;
+const CinnamonMountOperation = imports.ui.cinnamonMountOperation;
const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;
const Params = imports.misc.params;
@@ -125,172 +126,23 @@ PlaceDeviceInfo.prototype = {
if (!this.isRemovable())
return;
- let mountOp = new Gio.MountOperation();
+ let mountOp = new CinnamonMountOperation.CinnamonMountOperation();
let drive = this._mount.get_drive();
let volume = this._mount.get_volume();
if (drive &&
drive.get_start_stop_type() == Gio.DriveStartStopType.SHUTDOWN &&
drive.can_stop()) {
- drive.stop(0, mountOp, null, Lang.bind(this, this._stopFinish));
+ drive.stop(0, mountOp.mountOp, null, null);
} else {
- if (drive && drive.can_eject())
- drive.eject_with_operation(0, mountOp, null, Lang.bind(this, this._ejectFinish, true));
- else if (volume && volume.can_eject())
- volume.eject_with_operation(0, mountOp, null, Lang.bind(this, this._ejectFinish, false));
- else if (this._mount.can_eject())
- this._mount.eject_with_operation(0, mountOp, null, Lang.bind(this, this._ejectFinish, false));
+ if ((drive && drive.can_eject()) || (volume && volume.can_eject()) || this._mount.can_eject())
+ drive.eject_with_operation(0, mountOp.mountOp, null, null);
else if (this._mount.can_unmount())
- this._mount.unmount_with_operation(0, mountOp, null, Lang.bind(this, this._removeFinish));
+ this._mount.unmount_with_operation(0, mountOp.mountOp, null, null);
}
this.busyWaitId = 0;
return false;
- },
-
- _sendNotification: function(msg1, msg2 = null, withButton = false, persistent = false) {
- if (Main.messageTray) {
- if (persistent && this.busyNotification != null) {
- return;
- }
-
- if (!persistent && this.busyNotification) {
- this.busyNotification.destroy();
- this.busyNotification = null;
- }
-
- let source = new MessageTray.SystemNotificationSource();
- Main.messageTray.add(source);
- let notification = new MessageTray.Notification(source, msg1, msg2);
- notification.setTransient(true);
- notification.setUrgency(persistent ? MessageTray.Urgency.CRITICAL : MessageTray.Urgency.NORMAL);
- if (withButton) {
- notification.addButton('system-undo', _("Retry"));
- notification.connect('action-invoked', Lang.bind(this, this.remove));
- }
- source.notify(notification);
- if (persistent) {
- this.busyNotification = notification;
- this.destroySignalId = notification.connect("destroy", () => {
- this.busyNotification.disconnect(this.destroySignalId);
- this.busyNotification = null;
- this.destroySignalId = 0;
- })
- }
- } else {
- if (msg2)
- global.log(msg1 + ': ' + msg2);
- else
- global.log(msg1);
- }
- },
-
- _stopFinish: function(drive, res) {
- if (DEBUG) global.log("PlacesManager: **_stopFinish**");
- let driveName = drive.get_name(); // Ex: USB Flash Drive
- let unixDevice = drive.get_identifier('unix-device'); // Ex: /dev/sdc
- let msg1 = _("%s (%s) has just been stopped.").format(driveName, this.name);
- let msg2 = _("Device %s can be turned off, if necessary.").format(unixDevice);
- let btn = false; // Show the 'Retry' button?
- try {
- drive.stop_finish(res);
- } catch(e) {
- if (e.code == Gio.IOErrorEnum.BUSY) {
- msg1 = _("Device %s is busy, please wait.".format(drive.get_name()));
- msg2 = _("Do not disconnect or data loss may occur.");
-
- this._sendNotification(msg1, msg2, false, true);
- this.busyWaitId = Mainloop.timeout_add_seconds(2, ()=>this._tryRemove());
- return;
- }
- btn = true;
- msg1 = _("Unable to stop the drive %s (%s)").format(drive.get_name(), this.name);
- msg2 = e.message;
- }
- if (DEBUG) global.log(msg1 + ": " + msg2);
- this._sendNotification(msg1, msg2, btn);
- },
-
- _ejectFinish: function(source, res, is_drive) {
- if (DEBUG) global.log("PlacesManager: **_ejectFinish**");
- let msg1;
- let msg2 = null;
- let btn = false;
-
- if (is_drive) {
- let driveName = source.get_name(); // Ex: USB Flash Drive
- let unixDevice = source.get_identifier('unix-device'); // Ex: /dev/sdc
- msg1 = _("%s (%s) can be safely unplugged.").format(driveName, this.name);
- msg2 = _("Device %s can be removed.").format(unixDevice);
- } else {
- msg1 = _("%s (%s) has just been ejected.").format(source.get_name(), this.name);
- }
- try {
- source.eject_with_operation_finish(res);
- } catch(e) {
- if (e.code == Gio.IOErrorEnum.BUSY) {
- msg1 = _("Device %s is busy, please wait.".format(source.get_name()));
- msg2 = _("Do not remove or data loss may occur.");
-
- this._sendNotification(msg1, msg2, false, true);
- this.busyWaitId = Mainloop.timeout_add_seconds(2, ()=>this._tryRemove());
- return;
- }
- btn = true;
- msg1 = _("Unable to eject the drive %s (%s)").format(source.get_name(), this.name);
- msg2 = e.message;
- }
- if (DEBUG) global.log(msg1 + ": " + msg2);
- this._sendNotification(msg1, msg2, btn);
- },
-
- _removeFinish: function(o, res, data) {
- if (DEBUG) global.log("PlacesManager: **_removeFinish**");
- let msg1 = _("Successfully unmounted %s (%s)").format(o.get_name(), this.name);
- let msg2 = null;
- let btn = false;
-
- // 'this._mount.can_eject()' seems to be ever false. Thus, only the 'else' part will be used.
- // If no issues are reported, these 19 lines of code commented below can be deleted.
- //~ if (this._mount.can_eject()) {
- //~ msg1 = _("%s (%s) can be safely unplugged").format(o.get_name(), this.name);
- //~ msg2 = _("Device can be removed");
- //~ try {
- //~ this._mount.eject_with_operation_finish(res);
- //~ } catch(e) {
- //~ btn = true;
- //~ msg1 = _("Failed to eject %s (%s)").format(o.get_name(), this.name);
- //~ msg2 = e.message;
- //~ }
- //~ } else {
- //~ try {
- //~ this._mount.unmount_with_operation_finish(res);
- //~ } catch(e) {
- //~ btn = true;
- //~ msg1 = _("Failed to unmount %s (%s)").format(o.get_name(), this.name);
- //~ msg2 = e.message;
- //~ }
- //~ }
- // <--Beginning of the code replacing the 19 lines above:
- try {
- this._mount.unmount_with_operation_finish(res);
- } catch(e) {
- if (e.code == Gio.IOErrorEnum.BUSY) {
- msg1 = _("Device %s is busy, please wait.".format(o.get_name()));
- msg2 = _("Do not disconnect or data loss may occur.");
-
- this._sendNotification(msg1, msg2, false, true);
- this.busyWaitId = Mainloop.timeout_add_seconds(2, ()=>this._tryRemove());
- return;
- }
- btn = true;
- msg1 = _("Failed to unmount %s (%s)").format(o.get_name(), this.name);
- msg2 = e.message;
- }
- // End of this code.-->
-
- if (DEBUG) global.log(msg1 + ": " + msg2);
- this._sendNotification(msg1, msg2, btn);
}
};
diff --git a/src/cinnamon-mount-operation.c b/src/cinnamon-mount-operation.c
new file mode 100644
index 0000000000..d7096886eb
--- /dev/null
+++ b/src/cinnamon-mount-operation.c
@@ -0,0 +1,189 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see .
+ *
+ * Author: Cosimo Cecchi
+ *
+ */
+
+#include "cinnamon-mount-operation.h"
+
+/* This is a dummy class; we would like to be able to subclass the
+ * object from JS but we can't yet; the default GMountOperation impl
+ * automatically calls g_mount_operation_reply(UNHANDLED) after an idle,
+ * in interactive methods. We want to handle the reply ourselves
+ * instead, so we just override the default methods with empty ones,
+ * except for ask-password, as we don't want to handle that.
+ *
+ * Also, we need to workaround the fact that gjs doesn't support type
+ * annotations for signals yet (so we can't effectively forward e.g.
+ * the GPid array to JS).
+ * See https://bugzilla.gnome.org/show_bug.cgi?id=645978
+ */
+
+enum {
+ SHOW_PROCESSES_2,
+ NUM_SIGNALS
+};
+
+static guint signals[NUM_SIGNALS] = { 0, };
+
+typedef struct _CinnamonMountOperationPrivate CinnamonMountOperationPrivate;
+
+struct _CinnamonMountOperation
+{
+ GMountOperation parent_instance;
+
+ CinnamonMountOperationPrivate *priv;
+};
+
+struct _CinnamonMountOperationPrivate {
+ GArray *pids;
+ gchar **choices;
+ gchar *message;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (CinnamonMountOperation, cinnamon_mount_operation, G_TYPE_MOUNT_OPERATION);
+
+static void
+cinnamon_mount_operation_init (CinnamonMountOperation *self)
+{
+ self->priv = cinnamon_mount_operation_get_instance_private (self);
+}
+
+static void
+cinnamon_mount_operation_ask_password (GMountOperation *op,
+ const char *message,
+ const char *default_user,
+ const char *default_domain,
+ GAskPasswordFlags flags)
+{
+ /* do nothing */
+}
+
+static void
+cinnamon_mount_operation_ask_question (GMountOperation *op,
+ const char *message,
+ const char *choices[])
+{
+ /* do nothing */
+}
+
+static void
+cinnamon_mount_operation_show_processes (GMountOperation *operation,
+ const gchar *message,
+ GArray *processes,
+ const gchar *choices[])
+{
+ CinnamonMountOperation *self = CINNAMON_MOUNT_OPERATION (operation);
+
+ if (self->priv->pids != NULL)
+ {
+ g_array_unref (self->priv->pids);
+ self->priv->pids = NULL;
+ }
+
+ g_free (self->priv->message);
+ g_strfreev (self->priv->choices);
+
+ /* save the parameters */
+ self->priv->pids = g_array_ref (processes);
+ self->priv->choices = g_strdupv ((gchar **) choices);
+ self->priv->message = g_strdup (message);
+
+ g_signal_emit (self, signals[SHOW_PROCESSES_2], 0);
+}
+
+static void
+cinnamon_mount_operation_finalize (GObject *obj)
+{
+ CinnamonMountOperation *self = CINNAMON_MOUNT_OPERATION (obj);
+
+ g_strfreev (self->priv->choices);
+ g_free (self->priv->message);
+
+ if (self->priv->pids != NULL)
+ {
+ g_array_unref (self->priv->pids);
+ self->priv->pids = NULL;
+ }
+
+ G_OBJECT_CLASS (cinnamon_mount_operation_parent_class)->finalize (obj);
+}
+
+static void
+cinnamon_mount_operation_class_init (CinnamonMountOperationClass *klass)
+{
+ GMountOperationClass *mclass;
+ GObjectClass *oclass;
+
+ mclass = G_MOUNT_OPERATION_CLASS (klass);
+ mclass->show_processes = cinnamon_mount_operation_show_processes;
+ mclass->ask_question = cinnamon_mount_operation_ask_question;
+ mclass->ask_password = cinnamon_mount_operation_ask_password;
+
+ oclass = G_OBJECT_CLASS (klass);
+ oclass->finalize = cinnamon_mount_operation_finalize;
+
+ signals[SHOW_PROCESSES_2] =
+ g_signal_new ("show-processes-2",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+GMountOperation *
+cinnamon_mount_operation_new (void)
+{
+ return g_object_new (CINNAMON_TYPE_MOUNT_OPERATION, NULL);
+}
+
+/**
+ * cinnamon_mount_operation_get_show_processes_pids:
+ * @self: a #CinnamonMountOperation
+ *
+ * Returns: (transfer full) (element-type GPid): a #GArray
+ */
+GArray *
+cinnamon_mount_operation_get_show_processes_pids (CinnamonMountOperation *self)
+{
+ return g_array_ref (self->priv->pids);
+}
+
+/**
+ * cinnamon_mount_operation_get_show_processes_choices:
+ * @self: a #CinnamonMountOperation
+ *
+ * Returns: (transfer full):
+ */
+gchar **
+cinnamon_mount_operation_get_show_processes_choices (CinnamonMountOperation *self)
+{
+ return g_strdupv (self->priv->choices);
+}
+
+/**
+ * cinnamon_mount_operation_get_show_processes_message:
+ * @self: a #CinnamonMountOperation
+ *
+ * Returns: (transfer full):
+ */
+gchar *
+cinnamon_mount_operation_get_show_processes_message (CinnamonMountOperation *self)
+{
+ return g_strdup (self->priv->message);
+}
diff --git a/src/cinnamon-mount-operation.h b/src/cinnamon-mount-operation.h
new file mode 100644
index 0000000000..ed79929cd6
--- /dev/null
+++ b/src/cinnamon-mount-operation.h
@@ -0,0 +1,41 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see .
+ *
+ * Author: Cosimo Cecchi
+ *
+ */
+
+#ifndef __CINNAMON_MOUNT_OPERATION_H__
+#define __CINNAMON_MOUNT_OPERATION_H__
+
+#include
+
+G_BEGIN_DECLS
+
+#define CINNAMON_TYPE_MOUNT_OPERATION (cinnamon_mount_operation_get_type ())
+G_DECLARE_FINAL_TYPE (CinnamonMountOperation, cinnamon_mount_operation,
+ CINNAMON, MOUNT_OPERATION, GMountOperation)
+
+GMountOperation *cinnamon_mount_operation_new (void);
+
+GArray * cinnamon_mount_operation_get_show_processes_pids (CinnamonMountOperation *self);
+gchar ** cinnamon_mount_operation_get_show_processes_choices (CinnamonMountOperation *self);
+gchar * cinnamon_mount_operation_get_show_processes_message (CinnamonMountOperation *self);
+
+G_END_DECLS
+
+#endif /* __CINNAMON_MOUNT_OPERATION_H__ */
diff --git a/src/hotplug-sniffer/cinnamon-mime-sniffer.c b/src/hotplug-sniffer/cinnamon-mime-sniffer.c
index aaa50066c8..c17ca210eb 100644
--- a/src/hotplug-sniffer/cinnamon-mime-sniffer.c
+++ b/src/hotplug-sniffer/cinnamon-mime-sniffer.c
@@ -14,9 +14,7 @@
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street - Suite 500, Boston, MA
- * 02110-1335, USA.
+ * along with this program; if not, see .
*
* Author: Cosimo Cecchi
*
@@ -45,8 +43,6 @@
#define DIRECTORY_LOAD_ITEMS_PER_CALLBACK 100
#define HIGH_SCORE_RATIO 0.10
-G_DEFINE_TYPE (CinnamonMimeSniffer, cinnamon_mime_sniffer, G_TYPE_OBJECT);
-
enum {
PROP_FILE = 1,
NUM_PROPERTIES
@@ -74,16 +70,26 @@ typedef struct {
gint total_items;
} DeepCountState;
+typedef struct _CinnamonMimeSnifferPrivate CinnamonMimeSnifferPrivate;
+
+struct _CinnamonMimeSniffer
+{
+ GObject parent_instance;
+
+ CinnamonMimeSnifferPrivate *priv;
+};
+
struct _CinnamonMimeSnifferPrivate {
GFile *file;
GCancellable *cancellable;
guint watchdog_id;
- GSimpleAsyncResult *async_result;
- gchar **sniffed_mime;
+ GTask *task;
};
+G_DEFINE_TYPE_WITH_PRIVATE (CinnamonMimeSniffer, cinnamon_mime_sniffer, G_TYPE_OBJECT);
+
static void deep_count_load (DeepCountState *state,
GFile *file);
@@ -181,6 +187,7 @@ prepare_async_result (DeepCountState *state)
GArray *results;
GPtrArray *sniffed_mime;
SniffedResult result;
+ char **mimes;
sniffed_mime = g_ptr_array_new ();
results = g_array_new (TRUE, TRUE, sizeof (SniffedResult));
@@ -222,16 +229,16 @@ prepare_async_result (DeepCountState *state)
out:
g_ptr_array_add (sniffed_mime, NULL);
- self->priv->sniffed_mime = (gchar **) g_ptr_array_free (sniffed_mime, FALSE);
+ mimes = (gchar **) g_ptr_array_free (sniffed_mime, FALSE);
g_array_free (results, TRUE);
- g_simple_async_result_complete_in_idle (self->priv->async_result);
+ g_task_return_pointer (self->priv->task, mimes, (GDestroyNotify)g_strfreev);
}
/* adapted from nautilus/libnautilus-private/nautilus-directory-async.c */
static void
deep_count_one (DeepCountState *state,
- GFileInfo *info)
+ GFileInfo *info)
{
GFile *subdir;
const char *content_type;
@@ -242,11 +249,13 @@ deep_count_one (DeepCountState *state,
subdir = g_file_get_child (state->file, g_file_info_get_name (info));
state->deep_count_subdirectories =
g_list_append (state->deep_count_subdirectories, subdir);
- }
+ }
else
{
content_type = g_file_info_get_content_type (info);
- add_content_type_to_cache (state, content_type);
+
+ if (content_type)
+ add_content_type_to_cache (state, content_type);
}
}
@@ -297,8 +306,8 @@ deep_count_next_dir (DeepCountState *state)
static void
deep_count_more_files_callback (GObject *source_object,
- GAsyncResult *res,
- gpointer user_data)
+ GAsyncResult *res,
+ gpointer user_data)
{
DeepCountState *state;
GList *files, *l;
@@ -311,10 +320,10 @@ deep_count_more_files_callback (GObject *source_object,
deep_count_finish (state);
return;
}
-
+
files = g_file_enumerator_next_files_finish (state->enumerator,
res, NULL);
-
+
for (l = files; l != NULL; l = l->next)
{
info = l->data;
@@ -345,8 +354,8 @@ deep_count_more_files_callback (GObject *source_object,
static void
deep_count_callback (GObject *source_object,
- GAsyncResult *res,
- gpointer user_data)
+ GAsyncResult *res,
+ gpointer user_data)
{
DeepCountState *state;
GFileEnumerator *enumerator;
@@ -361,7 +370,7 @@ deep_count_callback (GObject *source_object,
enumerator = g_file_enumerate_children_finish (G_FILE (source_object),
res, NULL);
-
+
if (enumerator == NULL)
{
deep_count_next_dir (state);
@@ -418,21 +427,18 @@ query_info_async_ready_cb (GObject *source,
if (error != NULL)
{
- g_simple_async_result_take_error (self->priv->async_result,
- error);
- g_simple_async_result_complete_in_idle (self->priv->async_result);
+ g_task_return_error (self->priv->task, error);
return;
}
if (g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY)
{
- g_simple_async_result_set_error (self->priv->async_result,
- G_IO_ERROR,
- G_IO_ERROR_NOT_DIRECTORY,
- "Not a directory");
- g_simple_async_result_complete_in_idle (self->priv->async_result);
- g_object_unref(info);
+ g_task_return_new_error (self->priv->task,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_DIRECTORY,
+ "Not a directory");
+
return;
}
@@ -477,27 +483,13 @@ cinnamon_mime_sniffer_dispose (GObject *object)
g_clear_object (&self->priv->file);
g_clear_object (&self->priv->cancellable);
- g_clear_object (&self->priv->async_result);
+ g_clear_object (&self->priv->task);
- if (self->priv->watchdog_id != 0)
- {
- g_source_remove (self->priv->watchdog_id);
- self->priv->watchdog_id = 0;
- }
+ g_clear_handle_id (&self->priv->watchdog_id, g_source_remove);
G_OBJECT_CLASS (cinnamon_mime_sniffer_parent_class)->dispose (object);
}
-static void
-cinnamon_mime_sniffer_finalize (GObject *object)
-{
- CinnamonMimeSniffer *self = CINNAMON_MIME_SNIFFER (object);
-
- g_strfreev (self->priv->sniffed_mime);
-
- G_OBJECT_CLASS (cinnamon_mime_sniffer_parent_class)->finalize (object);
-}
-
static void
cinnamon_mime_sniffer_get_property (GObject *object,
guint prop_id,
@@ -541,7 +533,6 @@ cinnamon_mime_sniffer_class_init (CinnamonMimeSnifferClass *klass)
oclass = G_OBJECT_CLASS (klass);
oclass->dispose = cinnamon_mime_sniffer_dispose;
- oclass->finalize = cinnamon_mime_sniffer_finalize;
oclass->get_property = cinnamon_mime_sniffer_get_property;
oclass->set_property = cinnamon_mime_sniffer_set_property;
@@ -550,19 +541,15 @@ cinnamon_mime_sniffer_class_init (CinnamonMimeSnifferClass *klass)
"File",
"The loaded file",
G_TYPE_FILE,
- G_PARAM_READWRITE);
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_type_class_add_private (klass, sizeof (CinnamonMimeSnifferPrivate));
g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
}
static void
cinnamon_mime_sniffer_init (CinnamonMimeSniffer *self)
{
- self->priv =
- G_TYPE_INSTANCE_GET_PRIVATE (self,
- CINNAMON_TYPE_MIME_SNIFFER,
- CinnamonMimeSnifferPrivate);
+ self->priv = cinnamon_mime_sniffer_get_instance_private (self);
init_mimetypes ();
}
@@ -580,18 +567,16 @@ cinnamon_mime_sniffer_sniff_async (CinnamonMimeSniffer *self,
gpointer user_data)
{
g_assert (self->priv->watchdog_id == 0);
- g_assert (self->priv->async_result == NULL);
-
- self->priv->async_result =
- g_simple_async_result_new (G_OBJECT (self),
- callback, user_data,
- cinnamon_mime_sniffer_sniff_finish);
+ g_assert (self->priv->task == NULL);
self->priv->cancellable = g_cancellable_new ();
+ self->priv->task = g_task_new (self, self->priv->cancellable,
+ callback, user_data);
self->priv->watchdog_id =
g_timeout_add (WATCHDOG_TIMEOUT,
watchdog_timeout_reached_cb, self);
+ g_source_set_name_by_id (self->priv->watchdog_id, "[gnome-shell] watchdog_timeout_reached_cb");
start_loading_file (self);
}
@@ -601,8 +586,5 @@ cinnamon_mime_sniffer_sniff_finish (CinnamonMimeSniffer *self,
GAsyncResult *res,
GError **error)
{
- if (g_simple_async_result_propagate_error (self->priv->async_result, error))
- return NULL;
-
- return g_strdupv (self->priv->sniffed_mime);
+ return g_task_propagate_pointer (self->priv->task, error);
}
diff --git a/src/hotplug-sniffer/cinnamon-mime-sniffer.h b/src/hotplug-sniffer/cinnamon-mime-sniffer.h
index d6e47cd074..dcfcf934a1 100644
--- a/src/hotplug-sniffer/cinnamon-mime-sniffer.h
+++ b/src/hotplug-sniffer/cinnamon-mime-sniffer.h
@@ -13,9 +13,7 @@
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street - Suite 500, Boston, MA
- * 02110-1335, USA.
+ * along with this program; if not, see .
*
* Author: Cosimo Cecchi
*
@@ -29,30 +27,9 @@
G_BEGIN_DECLS
-#define CINNAMON_TYPE_MIME_SNIFFER (cinnamon_mime_sniffer_get_type ())
-#define CINNAMON_MIME_SNIFFER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CINNAMON_TYPE_MIME_SNIFFER, CinnamonMimeSniffer))
-#define CINNAMON_IS_MIME_SNIFFER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CINNAMON_TYPE_MIME_SNIFFER))
-#define CINNAMON_MIME_SNIFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CINNAMON_TYPE_MIME_SNIFFER, CinnamonMimeSnifferClass))
-#define CINNAMON_IS_MIME_SNIFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CINNAMON_TYPE_MIME_SNIFFER))
-#define CINNAMON_MIME_SNIFFER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CINNAMON_TYPE_MIME_SNIFFER, CinnamonMimeSnifferClass))
-
-typedef struct _CinnamonMimeSniffer CinnamonMimeSniffer;
-typedef struct _CinnamonMimeSnifferPrivate CinnamonMimeSnifferPrivate;
-typedef struct _CinnamonMimeSnifferClass CinnamonMimeSnifferClass;
-
-struct _CinnamonMimeSniffer
-{
- GObject parent_instance;
-
- CinnamonMimeSnifferPrivate *priv;
-};
-
-struct _CinnamonMimeSnifferClass
-{
- GObjectClass parent_class;
-};
-
-GType cinnamon_mime_sniffer_get_type (void) G_GNUC_CONST;
+#define CINNAMON_TYPE_MIME_SNIFFER (cinnamon_mime_sniffer_get_type ())
+G_DECLARE_FINAL_TYPE (CinnamonMimeSniffer, cinnamon_mime_sniffer,
+ CINNAMON, MIME_SNIFFER, GObject)
CinnamonMimeSniffer *cinnamon_mime_sniffer_new (GFile *file);
diff --git a/src/hotplug-sniffer/hotplug-sniffer.c b/src/hotplug-sniffer/hotplug-sniffer.c
index 343d2e9ca4..6682db84fc 100644
--- a/src/hotplug-sniffer/hotplug-sniffer.c
+++ b/src/hotplug-sniffer/hotplug-sniffer.c
@@ -13,9 +13,7 @@
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street - Suite 500, Boston, MA
- * 02110-1335, USA.
+ * along with this program; if not, see .
*
* Authors: David Zeuthen
* Cosimo Cecchi
@@ -62,11 +60,7 @@ ensure_autoquit_off (void)
if (g_getenv ("HOTPLUG_SNIFFER_PERSIST") != NULL)
return;
- if (autoquit_id != 0)
- {
- g_source_remove (autoquit_id);
- autoquit_id = 0;
- }
+ g_clear_handle_id (&autoquit_id, g_source_remove);
}
static void
@@ -78,6 +72,7 @@ ensure_autoquit_on (void)
autoquit_id =
g_timeout_add_seconds (AUTOQUIT_TIMEOUT,
autoquit_timeout_cb, NULL);
+ g_source_set_name_by_id (autoquit_id, "[cinnamon] autoquit_timeout_cb");
}
typedef struct {
@@ -91,7 +86,7 @@ invocation_data_new (GVariant *params,
{
InvocationData *ret;
- ret = g_slice_new0 (InvocationData);
+ ret = g_new0 (InvocationData, 1);
ret->parameters = g_variant_ref (params);
ret->invocation = g_object_ref (invocation);
@@ -104,7 +99,7 @@ invocation_data_free (InvocationData *data)
g_variant_unref (data->parameters);
g_clear_object (&data->invocation);
- g_slice_free (InvocationData, data);
+ g_free (data);
}
static void
@@ -128,9 +123,9 @@ sniff_async_ready_cb (GObject *source,
g_dbus_method_invocation_return_value (data->invocation,
g_variant_new ("(^as)", types));
+ g_strfreev (types);
out:
- g_strfreev (types);
invocation_data_free (data);
ensure_autoquit_on ();
}
@@ -267,7 +262,7 @@ main (int argc,
/* ---------------------------------------------------------------------------------------------------- */
-static void
+static void __attribute__((format(printf, 1, 0)))
print_debug (const gchar *format, ...)
{
g_autofree char *s = NULL;
diff --git a/src/meson.build b/src/meson.build
index a44cb7c0e9..93b34202bf 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -19,6 +19,7 @@ cinnamon_headers = [
'cinnamon-glsl-effect.h',
'cinnamon-gtk-embed.h',
'cinnamon-global.h',
+ 'cinnamon-mount-operation.h',
'cinnamon-perf-log.h',
'cinnamon-screen.h',
'cinnamon-screenshot.h',
@@ -53,6 +54,7 @@ cinnamon_sources = [
'cinnamon-global.c',
'cinnamon-keyring-prompt.c',
'cinnamon-keyring-prompt.h',
+ 'cinnamon-mount-operation.c',
'cinnamon-perf-log.c',
'cinnamon-polkit-authentication-agent.c',
'cinnamon-polkit-authentication-agent.h',